Android 面试大全基础篇(校招 + 社招)含答案
2.2 生命周期
Fragment 必须是依存于 Activity 而存在的,因此 Activity 的生命周期会直接影响到 Fragment 的生命周期。相比 Activity 的生命周期,Fragment 的生命周期如下所示。
onAttach():Fragment 和 Activity 相关联时调用。如果不是一定要使用具体的宿主 Activity 对象的话,可以使用这个方法或者 getContext()获取 Context 对象,用于解决 Context 上下文引用的问题。同时还可以在此方法中可以通过 getArguments()获取到需要在 Fragment 创建时需要的参数。
onCreate():Fragment 被创建时调用。
onCreateView():创建 Fragment 的布局。
onActivityCreated():当 Activity 完成 onCreate()时调用。
onStart():当 Fragment 可见时调用。
onResume():当 Fragment 可见且可交互时调用。
onPause():当 Fragment 不可交互但可见时调用。
onStop():当 Fragment 不可见时调用。
onDestroyView():当 Fragment 的 UI 从视图结构中移除时调用。
onDestroy():销毁 Fragment 时调用。
onDetach():当 Fragment 和 Activity 解除关联时调用。
如下图所示。
下面是 Activity 的生命周期和 Fragment 的各个生命周期方法的对应关系。
2.3 与 Activity 传递数据
2.3.1 Fragment 向 Activity 传递数据
首先,在 Fragment 中定义接口,并让 Activity 实现该接口,如下所示。
public interface OnFragmentInteractionListener {
void onItemClick(String str);
}
然后,在 Fragment 的 onAttach()中,将参数 Context 强转为 OnFragmentInteractionListener 对象传递过去。
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
" must implement OnFragmentInteractionListener");
}
}
2.3.2 Activity 向 Fragment 传递数据
在创建 Fragment 的时候,可以通过 setArguments(Bundle bundle)方式将值传递给 Activity,如下所示。
public static Fragment newInstance(String str) {
FragmentTest fragment = new FragmentTest();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle);//设置参数
return fragment;
}
3, Service
3.1 启动方式
Service 的启动方式主要有两种,分别是 startService 和 bindService。
其中,StartService 使用的是同一个 Service,因此 onStart()会执行多次,onCreate()只执行一次,onStartCommand()也会执行多次。使用 bindService 启动时,onCreate()与 onBind()都只会调用一次。
使用 startService 启动时是单独开一个服务,与 Activity 没有任何关系,而 bindService 方式启动时,Service 会和 Activity 进行绑定,当对应的 activity 销毁时,对应的 Service 也会销毁。
3.2 生命周期
下图是 startService 和 bindService 两种方式启动 Service 的示意图。
3.2.1 startService
onCreate():如果 service 没被创建过,调用 startService()后会执行 onCreate()回调;如果 service 已处于运行中,调用 startService()不会执行 onCreate()方法。
onStartCommand():多次执行了 Context 的 startService()方法,那么 Service 的 onStartCommand()方法也会相应的多次调用。
onBind():Service 中的 onBind()方法是抽象方法,Service 类本身就是抽象类,所以 onBind()方法是必须重写的,即使我们用不到。
onDestory():在销毁 Service 的时候该方法。
public class TestOneService extends Service{
@Override
public void onCreate() {
Log.i("Kathy","onCreate - Thread ID = " + Thread.currentThread().getId());
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("Kathy", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().getId());
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i("Kathy", "onBind - Thread ID = " + Thread.currentThread().getId());
return null;
}
@Override
public void onDestroy() {
Log.i("Kathy", "onDestroy - Thread ID = " + Thread.currentThread().getId());
super.onDestroy();
}
}
3.2.2 bindService
bindService 启动的服务和调用者之间是典型的 Client-Server 模式。调用者是 client,Service 则是 Server 端。Service 只有一个,但绑定到 Service 上面的 Client 可以有一个或很多个。bindService 启动服务的生命周期与其绑定的 client 息息相关。
1,首先,在 Service 的 onBind()方法中返回 IBinder 类型的实例。 2,onBInd()方法返回的 IBinder 的实例需要能够返回 Service 实例本身。
3.3 Service 不被杀死
现在,由于系统 API 的限制,一些常见的不被杀死 Service 方式已经过时,比如下面是之前的一些方式。
3.3.1, onStartCommand 方式中,返回 START_STICKY。
调用 Context.startService 方式启动 Service 时,如果 Android 面临内存匮乏,可能会销毁当前运行的 Service,待内存充足时可以重建 Service。而 Service 被 Android 系统强制销毁并再次重建的行为依赖于 Service 的 onStartCommand()方法的返回值,常见的返回值有如下一些。
START_NOT_STICKY:如果返回 START_NOT_STICKY,表示当 Service 运行的进程被 Android 系统强制杀掉之后,不会重新创建该 Service。
START_STICKY:如果返回 START_STICKY,表示 Service 运行的进程被 Android 系统强制杀掉之后,Android 系统会将该 Service 依然设置为 started 状态(即运行状态),但是不再保存 onStartCommand 方法传入的 intent 对象,即获取不到 intent 的相关信息。
START_REDELIVER_INTENT:如果返回 START_REDELIVER_INTENT,表示 Service 运行的进程被 Android 系统强制杀掉之后,与返回 START_STICKY 的情况类似,Android 系统会将再次重新创建该 Service,并执行 onStartCommand 回调方法,但是不同的是,Android 系统会再次将 Service 在被杀掉之前最后一次传入 onStartCommand 方法中的 Intent 再次保留下来并再次传入到重新创建后的 Service 的 onStartCommand 方法中,这样我们就能读取到 intent 参数。
4, BroadcastReceiver
4.1 BroadcastReceiver 是什么
BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的 Broadcast 消息,所以它可以很方便的进行系统组件之间的通信。BroadcastReceiver 属于系统级的监听器,它拥有自己的进程,只要存在与之匹配的 Broadcast 被以 Intent 的形式发送出来,BroadcastReceiver 就会被激活。
和其他的四大组件一样,BroadcastReceiver 也有自己独立的声明周期,但是它又和 Activity、Service 不同。当在系统注册一个 BroadcastReceiver 之后,每次系统以一个 Intent 的形式发布 Broadcast 的时候,系统都会创建与之对应的 BroadcastReceiver 广播接收者实例,并自动触发它的 onReceive()方法,当 onReceive()方法被执行完成之后,BroadcastReceiver 的实例就会被销毁。
从不同的纬度区分,BroadcastReceiver 可以分为不同的类别。
系统广播/非系统广播
全局广播/本地广播
无序广播/有序广播/粘性广播
4.2 基本使用
4.2.1 注册广播
广播的注册分为静态注册和动态注册。静态注册是在 Mainfest 清单文件中进行注册,比如。
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
动态注册是在代码中,使用 registerReceiver 方法代码进行注册,比如。
val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION).apply {
addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED)
}
registerReceiver(br, filter)
4.2.2 发送广播
然后,我们使用 sendBroadcast 方法发送广播。
Intent().also { intent ->
intent.setAction("com.example.broadcast.MY_NOTIFICATION")
intent.putExtra("data", "Notice me senpai!")
sendBroadcast(intent)
}
4.2.3 接收广播
发送广播的时候,我们会添加一个发送的标识,那么接收的时候使用这个标识接收即可。接收广播需要继承 BroadcastReceiver,并重写 onReceive 回调方法接收广播数据。
private const val TAG = "MyBroadcastReceiver"
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
StringBuilder().apply {
append("Action: ${intent.action}\n")
append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
toString().also { log ->
Log.d(TAG, log)
Toast.makeText(context, log, Toast.LENGTH_LONG).show()
}
}
}
}
5, ContentProvider
ContentProvider 是 Android 四大组件之一,不过平时使用的机会比较少。如果你看过它的底层源码,那么就应该知道 ContentProvider 是通过 Binder 进行数据共享。因此,如果我们需要对第三方应用提供数据,可以考虑使用 ContentProvider 实现。
6,Android View 知识点
Android 本身的 View 体系非常庞大的,如果要完全弄懂 View 的原理是很困难的,我们这里捡一些比较重要的概念来给大家讲解。
6.1 测量流程
Android View 本身的绘制流程需要经过 measure 测量、layout 布局、draw 绘制三个过程,最终才能够将其绘制出来并展示在用户面前。
首先,我们看一下 Android 的 MeasureSpec,Android 的 MeasureSpec 分为 3 中模式,分别是 EXACTLY、AT_MOST 和 UNSPECIFIED,含义如下。
MeasureSpec.EXACTLY:精确模式,在这种模式下,尺寸的值是多少组件的长或宽就是多少。
MeasureSpec.AT_MOST:最大模式,由父组件能够给出的最大的空间决定。
MeasureSpec.UNSPECIFIED:未指定模式,当前组件可以随便使用空间,不受限制。
6.2 事件分发
Android 的事件分发由 dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent 三个方法构成。
dispatchTouchEvent:方法返回值为 true,表示事件被当前视图消费掉;返回为 super.dispatchTouchEvent 表示继续分发该事件,返回为 false 表示交给父类的 onTouchEvent 处理。
onInterceptTouchEvent:方法返回值为 true,表示拦截这个事件并交由自身的 onTouchEvent 方法进行消费;返回 false 表示不拦截,需要继续传递给子视图。如果 return super.onInterceptTouchEvent(ev), 事件拦截分两种情况:即一种是有子 View 的情况,另一种是没有子 View 的情况。
如果该 View 存在子 View 且点击到了该子 View,则不拦截,继续分发 给子 View 处理,此时相当于 return false。如果该 View 没有子 View 或者有子 View 但是没有点击中子 View,则交由该 View 的 onTouchEvent 响应,此时相当于 return true。
onTouchEvent:方法返回值为 true 表示当前视图可以处理对应的事件;返回值为 false 表示当前视图不处理这个事件,它会被传递给父视图的 onTouchEvent 方法进行处理。如果 return super.onTouchEvent(ev),事件处理分为两种情况,即自己消费还是还是向上传递。
在 Android 系统中,拥有事件传递处理能力的类有以下三种:
Activity:拥有分发和消费两个方法。
ViewGroup:拥有分发、拦截和消费三个方法。
View:拥有分发、消费两个方法。
在事件分发中,有时候会问:ACTION_CANCEL 什么时候触发,触摸 button 然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?
对于这个问题,我们需要明白以下内容:
一般 ACTION_CANCEL 和 ACTION_UP 都作为 View 一段事件处理的结束。如果在父 View 中拦截 ACTION_UP 或 ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到 ACTION_CANCEL 事件。
如果触摸某个控件,但是又不是在这个控件的区域上抬起,也会出现 ACTION_CANCEL。
ViewGroup 默认不拦截任何事件。ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
View 没有 onInterceptTouchEvent 方法,一旦有点击事件传递给它,onTouchEvent 方法就会被调用。
View 在可点击状态下,onTouchEvent 默认会消耗事件。
ACTION_DOWN 被拦截了,onInterceptTouchEvent 方法执行一次后,就会留下记号(mFirstTouchTarget == null)那么往后的 ACTION_MOVE 和 ACTION_UP 都会拦截。`
6.3 MotionEvent
Android 的 MotionEvent 事件主要有以下几个:
ACTION_DOWN 手指刚接触到屏幕
ACTION_MOVE 手指在屏幕上移动
ACTION_UP 手机从屏幕上松开的一瞬间
ACTION_CANCEL 触摸事件取消
下面是事件的举例:点击屏幕后松开,事件序列为 DOWN -> UP,点击屏幕滑动松开,事件序列为 DOWN -> MOVE -> ...> MOVE -> UP。同时,getX/getY 返回相对于当前 View 左上角的坐标,getRawX/getRawY 返回相对于屏幕左上角的坐标。TouchSlop 是系统所能识别出的被认为滑动的最小距离,不同设备值可能不相同,可通过 ViewConfiguration.get(getContext()).getScaledTouchSlop() 获取。
6.4 Activity、Window、DecorView 之间关系
首先,来看一下 Activity 中 setContentView 的源代码。
public void setContentView(@LayoutRes int layoutResID) {
//将 xml 布局传递到 Window 当中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到, Activity 的 setContentView 实质是将 View 传递到 Window 的 setContentView()方法中, Window 的 setContenView 会在内部调用 installDecor()方法创建 DecorView,代码如下。
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//初始化 DecorView 以及其内部的 content
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...............
} else {
//将 contentView 加载到 DecorVoew 当中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...............
}
private void installDecor() {
...............
if (mDecor == null) {
//实例化 DecorView
mDecor = generateDecor(-1);
...............
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//获取 Content
mContentParent = generateLayout(mDecor);
}
...............
}
protected DecorView generateDecor(int featureId) {
...............
return new DecorView(context, featureId, this, getAttributes());
}
通过 generateDecor()的 new 一个 DecorView,然后调用 generateLayout()获取 DecorView 中 content,最终通过 inflate 将 Activity 视图添加到 DecorView 中的 content 中,但此时 DecorView 还未被添加到 Window 中。添加操作需要借助 ViewRootImpl。
ViewRootImpl 的作用是用来衔接 WindowManager 和 DecorView,在 Activity 被创建后会通过 WindowManager 将 DecorView 添加到 PhoneWindow 中并且创建 ViewRootImpl 实例,随后将 DecorView 与 ViewRootImpl 进行关联,最终通过执行 ViewRootImpl 的 performTraversals()开启整个 View 树的绘制。
6.5 Draw 绘制流程
Android 的 Draw 过程可以分为六个步骤:
首先,绘制 View 的背景;
如果需要的话,保持 canvas 的图层,为 fading 做准备;
然后,绘制 View 的内容;
接着,绘制 View 的子 View;
如果需要的话,绘制 View 的 fading 边缘并恢复图层;
最后,绘制 View 的装饰(例如滚动条等等)。
涉及到的代码如下:
public void draw(Canvas canvas) {
...
// 步骤一:绘制 View 的背景
drawBackground(canvas);
...
// 步骤二:如果需要的话,保持 canvas 的图层,为 fading 做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步骤三:绘制 View 的内容
onDraw(canvas);
...
// 步骤四:绘制 View 的子 View
dispatchDraw(canvas);
...
// 步骤五:如果需要的话,绘制 View 的 fading 边缘并恢复图层
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步骤六:绘制 View 的装饰(例如滚动条等等)
onDrawForeground(canvas)
}
6.6 Requestlayout,onlayout,onDraw,DrawChild 区别与联系
requestLayout():会导致调用 measure()过程 和 layout()过程,将会根据标志位判断是否需要 ondraw。
onLayout():如果该 View 是 ViewGroup 对象,需要实现该方法,对每个子视图进行布局。
onDraw():绘制视图本身 (每个 View 都需要重载该方法,ViewGroup 不需要实现该方法)。
drawChild():去重新回调每个子视图的 draw()方法。
6.7 invalidate() 和 postInvalidate()的区别
invalidate()与 postInvalidate()都用于刷新 View,主要区别是 invalidate()在主线程中调用,若在子线程中使用需要配合 handler;而 postInvalidate()可在子线程中直接调用。
7,Androi
d 进程
7.1 概念
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
当一个程序第一次启动的时候,Android 会启动一个 LINUX 进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。 同时,Android 会为每个应用程序分配一个单独的 LINUX 用户。Android 会尽量保留一个正在运行进程,只在内存资源出现不足时,Android 会尝试停止一些进程从而释放足够的资源给其他新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。
我们可以将一些组件运行在其他进程中,并且可以为任意的进程添加线程。组件运行在哪个进程中是在 manifest 文件里设置的,其中,,和都有一个 process 属性来指定该组件运行在哪个进程之中。我们可以设置这个属性,使得每个组件运行在它们自己的进程中,或是几个组件共同享用一个进程,或是不共同享用。元素也有一个 process 属性,用来指定所有的组件的默认属性。
Android 中的所有组件都在指定的进程中的主线程中实例化的,对组件的系统调用也是由主线程发出的。每个实例不会建立新的线程。对系统调用进行响应的方法——例如负责执行用户动作的 View.onKeyDown()和组件的生命周期函数——都是运行在这个主线程中的。这意味着当系统调用这个组件时,这个组件不能长时间的阻塞主线程。例如进行网络操作时或是更新 UI 时,如果运行时间较长,就不能直接在主线程中运行,因为这样会阻塞这个进程中其他的组件,我们可以将这样的组件分配到新建的线程中或是其他的线程中运行。
7.2 进程生命周期
按照生命周期的不同,Android 的进程可以分为前台进程、后台进程、可见进程、服务进程和空进程等。
前台进程
前台进程是用户当前正在使用的进程,一些前台进程可以在任何时候都存在,当内存低的时候前台进程也可能被销毁。对于这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。
如果有以下的情形,那么它就是前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,后者绑定到用户正在交互的 Activity
托管正在“前台”运行的 Service(服务已调用 startForeground())
托管正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
可见进程
可见进程指的是不包含前台组件,但是会在屏幕上显示一个可见的进程。
如果有如下的一种情形,那就是可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果 re 前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况。
托管绑定到可见(或前台)Activity 的 Service。
服务进程
通过 startService() 方法启动的 Service,这个 Service 没有上面的两种进程重要,一般会随着应用的生命周期。
一般来说,使用 startService() 方法启动的服务且不属于上述两个更高类别进程的就是服务进程。
后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
空进程
不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
7.3 多进程
首先,进程一般指一个执行单元,在移动设备上就是一个程序或应用,我们在 Android 中所说的多进程(IPC)一般指一个应用包含多个进程。之所以要使用多进程有两方面原因:某些模块由于特殊的需求要运行在单独的进程;增加应用可用的内存空间。
在 Android 中开启多进程只有一种方法,就是在 AndroidManifest.xml 中注册 Service、Activity、Receiver、ContentProvider 时指定 android:process 属性,如下所示。
<service
android:name=".MyService"
android:process=":remote">
</service>
<activity
android:name=".MyActivity"
android:process="com.shh.ipctest.remote2">
</activity>
可以看到,MyService 和 MyActivity 指定的 android:process 属性值有所不同,它们的区别如下:
:remote:以:开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.shh.ipctest:remote,同时以:开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。
com.shh.ipctest.remote2:这是完整的命名方式,不会附加包名,其它应用如果和该进程的 ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。
不过,开启多进程会引发如下问题,必须引起注意:
静态成员和单例模式失效
线程同步机制失效
SharedPreferences 可靠性降低
Application 被多次创建
对于前两个问题,可以这么理解,在 Android 中,系统会为每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间,所以同一个类的对象会产生不同的副本,导致共享数据失败,必然也不能实现线程的同步。
由于 SharedPreferences 底层采用读写 XML 的文件的方式实现,多进程并发的的读写很可能导致数据异常。
Application 被多次创建和前两个问题类似,系统在分配多个虚拟机时相当于把同一个应用重新启动多次,必然会导致 Application 多次被创建,为了防止在 Application 中出现无用的重复初始化,可使用进程名来做过滤,只让指定进程的才进行全局初,如下所示。
public class MyApplication extends Application{
@Override
public void onCreate() {
super.onCreate();
String processName = "com.xzh.ipctest";
if (getPackageName().equals(processName)){
// do some init
}
}
}
7.4 多进程通信方式
目前,Android 中支持的多进程通信方式主要有以下几种:
AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用)。
Messenger:支持一对多的串行实时通信, AIDL 的简化版本。
Bundle:四大组件的进程通信方式,只能传输 Bundle 支持的数据类型。
ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据。
BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。
文件共享:在非高并发情况下共享简单的数据。
Socket:通过网络传输数据。
8,序列化
8.1 Parcelable 与 Serializable
Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接在内存中读写。
Serializable 会使用反射,序列化和反序列化过程需要大量 I/O 操作, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操作不需要用反射,数据也存放在 Native 内存中,效率要快很多。
8.2 示例
Serializable 实例:
import java.io.Serializable;
class serializableObject implements Serializable {
String name;
public serializableObject(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Parcelable 实例:
import android.os.Parcel;
import android.os.Parcelable;
class parcleObject implements Parcelable {
private String name;
protected parcleObject(Parcel in) {
this.name = in.readString();
}
public parcleObject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static final Creator<parcleObject> CREATOR = new Creator<parcleObject>() {
@Override
public parcleObject createFromParcel(Parcel in) {
return new parcleObject(in);
}
@Override
public parcleObject[] newArray(int size) {
return new parcleObject[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
}
}
使用 Parcelable 时,一般需要用到以下几个方法:
createFromParcel(Parcel in):从序列化后的对象中创建原始对象。
newArray(int size):创建指定长度的原始对象数组。
User(Parcel in) 从序列化后的对象中创建原始对象。
writeToParcel(Parcel dest, int flags):将当前对象写入序列化结构中,其中 flags 标识有两种值:0 或者 1。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0。
describeContents:返回当前对象的内容描述。如果含有文件描述符,返回 1,否则返回 0,几乎所有情况都返回 0。
9,Window
9.1 基本概念
Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中所有的视图都是通过 Window 来呈现,因此 Window 实际是 View 的直接管理者。
依据作用的不同,Window 可以分为如下几种:
Application Window:对应着一个 Activity;
Sub Window: 不能单独存在,只能附属在父 Window 中,如 Dialog 等;
System Window:需要权限声明,如 Toast 和 系统状态栏等;
9.2 内部机制
Window 是一个抽象的概念,每一个 Window 对应着一个 View 和一个 ViewRootImpl。Window 实际是不存在的,它是以 View 的形式存在。对 Window 的访问必须通过 WindowManager,WindowManager 的实现类是 WindowManagerImpl,源码如下:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerImpl 没有直接实现 Window 的三大操作,而是全部交给 WindowManagerGlobal 处理,WindowManagerGlobal 以工厂的形式向外提供自己的实例,涉及的代码如下:
// 添加
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
···
// 子 Window 的话需要调整一些布局参数
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
···
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 新建一个 ViewRootImpl,并通过其 setView 来更新界面完成 Window 的添加过程
···
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
// 删除
@UnsupportedAppUsage
public void removeView(View view, boolean immediate) {
···
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
···
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
// 更新
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
···
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
10,消息机制
谈 Android 的消息机制主要就是 Handler 机制。
10.1 Handler 机制
Handler 有两个主要用途:
安排 Message 和 runnables 在将来的某个时刻执行;
将要在不同于自己的线程上执行的操作排入队列。(在多个线程并发更新 UI 的同时保证线程安全。)
Android 规定访问 UI 只能在主线程中进行,因为 Android 的 UI 控件不是线程安全的,多线程并发访问会导致 UI 控件处于不可预期的状态。为什么系统不对 UI 控件的访问加上锁机制?缺点有两个:加锁会让 UI 访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率。如果子线程访问 UI,那么程序就会抛出异常。为了保证线程安全,ViewRootImpl 对 UI 操作做了验证,这个验证工作是由 ViewRootImpl 的 checkThread 方法完成。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
评论