在上一篇文章Android 面试之必问Java基础一文中,咱们介绍了Java面试的一些常见的基础面试题,下面咱们来介绍Android开发的一些必问知识点。java
正常状况系,Activity会经历以下几个阶段:android
对于生命周期,一般还会问以下的一些问题:git
Activity的启动模式有四种:Standard、SingleTop、SingleTask和SingleInstance。github
在理解Activity的启动流程以前,先让咱们来看一下Android系统启动流程。总的来讲,Android系统启动流程的主要经历init进程 -> Zygote进程 –> SystemServer进程 –> 各类系统服务 –> 应用进程等阶段。面试
参考:
Android系统启动流程之init进程启动
Android系统启动流程之Zygote进程启动
Android系统启动流程之SystemServer进程启动
Android系统启动流程之Launcher进程启动json
Launcher进程启动后,就会调用Activity的启动了。首先,Launcher会调用ActivityTaskManagerService,而后ActivityTaskManagerService会调用ApplicationThread,而后ApplicationThread再经过ActivityThread启动Activity,完整的分析能够参考Android 之 Activity启动流程canvas
Fragment,是Android 3.0(API 11)提出的,为了兼容低版本,support-v4库中也开发了一套Fragment API,最低兼容Android 1.6,若是要在最新的版本中使用Fragment,须要引入AndroidX的包。小程序
相比Activity,Fragment具备以下一些特色:segmentfault
Fragment有以下几个核心的类:数组
Fragment必须是依存于Activity而存在的,所以Activity的生命周期会直接影响到Fragment的生命周期。相比Activity的生命周期,Fragment的生命周期以下所示。
以下图所示。
下面是Activity的生命周期和Fragment的各个生命周期方法的对应关系。
首先,在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"); } }
在建立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; }
Service的启动方式主要有两种,分别是startService和bindService。
其中,StartService使用的是同一个Service,所以onStart()会执行屡次,onCreate()只执行一次,onStartCommand()也会执行屡次。使用bindService启动时,onCreate()与onBind()都只会调用一次。
使用startService启动时是单独开一个服务,与Activity没有任何关系,而bindService方式启动时,Service会和Activity进行绑定,当对应的activity销毁时,对应的Service也会销毁。
下图是startService和bindService两种方式启动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(); } }
bindService启动的服务和调用者之间是典型的Client-Server模式。调用者是client,Service则是Server端。Service只有一个,但绑定到Service上面的Client能够有一个或不少个。bindService启动服务的生命周期与其绑定的client息息相关。
1,首先,在Service的onBind()方法中返回IBinder类型的实例。
2,onBInd()方法返回的IBinder的实例须要可以返回Service实例自己。
如今,因为系统API的限制,一些常见的不被杀死Service方式已通过时,好比下面是以前的一些方式。
调用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参数。
BroadcastReceiver,广播接收者,它是一个系统全局的监听器,用于监听系统全局的Broadcast消息,因此它能够很方便的进行系统组件之间的通讯。BroadcastReceiver属于系统级的监听器,它拥有本身的进程,只要存在与之匹配的Broadcast被以Intent的形式发送出来,BroadcastReceiver就会被激活。
和其余的四大组件同样,BroadcastReceiver也有本身独立的声明周期,可是它又和Activity、Service不一样。当在系统注册一个BroadcastReceiver以后,每次系统以一个Intent的形式发布Broadcast的时候,系统都会建立与之对应的BroadcastReceiver广播接收者实例,并自动触发它的onReceive()方法,当onReceive()方法被执行完成以后,BroadcastReceiver的实例就会被销毁。
从不一样的纬度区分,BroadcastReceiver能够分为不一样的类别。
广播的注册分为静态注册和动态注册。静态注册是在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)
而后,咱们使用sendBroadcast方法发送广播。
Intent().also { intent -> intent.setAction("com.example.broadcast.MY_NOTIFICATION") intent.putExtra("data", "Notice me senpai!") sendBroadcast(intent) }
发送广播的时候,咱们会添加一个发送的标识,那么接收的时候使用这个标识接收便可。接收广播须要继承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() } } } }
ContentProvider是Android四大组件之一,不过平时使用的机会比较少。若是你看过它的底层源码,那么就应该知道ContentProvider是经过Binder进行数据共享。所以,若是咱们须要对第三方应用提供数据,能够考虑使用ContentProvider实现。
Android自己的View体系很是庞大的,若是要彻底弄懂View的原理是很困难的,咱们这里捡一些比较重要的概念来给你们讲解。
Android View自己的绘制流程须要通过measure测量、layout布局、draw绘制三个过程,最终才可以将其绘制出来并展现在用户面前。
首先,咱们看一下Android的MeasureSpec,Android的MeasureSpec分为3中模式,分别是EXACTLY、AT_MOST 和 UNSPECIFIED,含义以下。
相关文章:谈谈对 MeasureSpec 的理解
Android的事件分发由dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent三个方法构成。
在Android系统中,拥有事件传递处理能力的类有如下三种:
在事件分发中,有时候会问:ACTION_CANCEL何时触发,触摸button而后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?
对于这个问题,咱们须要明白如下内容:
Android的MotionEvent事件主要有如下几个:
下面是事件的举例:点击屏幕后松开,事件序列为 DOWN -> UP,点击屏幕滑动松开,事件序列为 DOWN -> MOVE -> ...> MOVE -> UP。同时,getX/getY 返回相对于当前View左上角的坐标,getRawX/getRawY 返回相对于屏幕左上角的坐标。TouchSlop是系统所能识别出的被认为滑动的最小距离,不一样设备值可能不相同,可经过ViewConfiguration.get(getContext()).getScaledTouchSlop() 获取。
首先,来看一下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树的绘制。
Android的Draw过程能够分为六个步骤:
涉及到的代码以下:
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) }
invalidate()与postInvalidate()都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用须要配合handler;而postInvalidate()可在子线程中直接调用。
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做系统结构的基础。
当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的状况下,全部该程序的组件都将在该进程和线程中运行。 同时,Android会为每一个应用程序分配一个单独的LINUX用户。Android会尽可能保留一个正在运行进程,只在内存资源出现不足时,Android会尝试中止一些进程从而释放足够的资源给其余新的进程使用, 也能保证用户正在访问的当前进程有足够的资源去及时地响应用户的事件。
咱们能够将一些组件运行在其余进程中,而且能够为任意的进程添加线程。组件运行在哪一个进程中是在manifest文件里设置的,其中<Activity>,<Service>,<receiver>和<provider>都有一个process属性来指定该组件运行在哪一个进程之中。咱们能够设置这个属性,使得每一个组件运行在它们本身的进程中,或是几个组件共同享用一个进程,或是不共同享用。<application>元素也有一个process属性,用来指定全部的组件的默认属性。
Android中的全部组件都在指定的进程中的主线程中实例化的,对组件的系统调用也是由主线程发出的。每一个实例不会创建新的线程。对系统调用进行响应的方法——例如负责执行用户动做的View.onKeyDown()和组件的生命周期函数——都是运行在这个主线程中的。这意味着当系统调用这个组件时,这个组件不能长时间的阻塞主线程。例如进行网络操做时或是更新UI时,若是运行时间较长,就不能直接在主线程中运行,由于这样会阻塞这个进程中其余的组件,咱们能够将这样的组件分配到新建的线程中或是其余的线程中运行。
按照生命周期的不一样,Android的进程能够分为前台进程、后台进程、可见进程、服务进程和空进程等。
前台进程是用户当前正在使用的进程,一些前台进程能够在任什么时候候都存在,当内存低的时候前台进程也可能被销毁。对于这种状况下,设备会进行内存调度,停止一些前台进程来保持对用户交互的响应。
若是有如下的情形,那么它就是前台进程:
可见进程指的是不包含前台组件,可是会在屏幕上显示一个可见的进程。
若是有以下的一种情形,那就是可见进程:
经过startService() 方法启动的Service,这个Service没有上面的两种进程重要,通常会随着应用的生命周期。
通常来讲,使用 startService() 方法启动的服务且不属于上述两个更高类别进程的就是服务进程。
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。一般会有不少后台进程在运行,所以它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
不含任何活动应用组件的进程。保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间。 为使整体系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程。
首先,进程通常指一个执行单元,在移动设备上就是一个程序或应用,咱们在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属性值有所不一样,它们的区别以下:
不过,开启多进程会引起以下问题,必须引发注意:
对于前两个问题,能够这么理解,在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 } } }
目前,Android中支持的多进程通讯方式主要有如下几种:
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时,通常须要用到如下几个方法:
Window 是一个抽象类,它的具体实现是 PhoneWindow。WindowManager 是外界访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程。Android 中全部的视图都是经过 Window 来呈现,所以 Window 实际是 View 的直接管理者。
依据做用的不一样,Window能够分为以下几种:
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); } }
谈Android的消息机制主要就是Handler机制。
Handler 有两个主要用途:
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."); } }
谈Handler机制时,一般会包含如下三个对象:
Handler 建立的时候会采用当前线程的 Looper 来构造消息循环系统,须要注意的是,线程默认是没有 Looper 的,直接使用 Handler 会报错,若是须要使用 Handler 就必须为线程建立 Looper,由于默认的 UI 主线程,也就是 ActivityThread,ActivityThread 被建立的时候就会初始化 Looper,这也是在主线程中默承认以使用 Handler 的缘由。
ThreadLocal
ThreadLocal 是一个线程内部的数据存储类,经过它能够在指定的线程中存储数据,其余线程则没法获取。Looper、ActivityThread 以及 AMS 中都用到了 ThreadLocal。当不一样线程访问同一个ThreadLocal 的 get方法,ThreadLocal 内部会从各自的线程中取出一个数组,而后再从数组中根据当前 ThreadLcoal 的索引去查找对应的value值,源码以下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ··· public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
MessageQueue
MessageQueue主要包含两个操做:插入和读取。读取操做自己会伴随着删除操做,插入和读取对应的方法分别是 enqueueMessage 和 next。MessageQueue 内部实现并非用的队列,实际上经过一个单链表的数据结构来维护消息列表。next 方法是一个无限循环的方法,若是消息队列中没有消息,那么 next 方法会一直阻塞。当有新消息到来时,next 方法会放回这条消息并将其从单链表中移除,源码以下。
boolean enqueueMessage(Message msg, long when) { ··· synchronized (this) { ··· msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } ··· Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. ··· for (;;) { ··· synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } ··· } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } }
Looper
Looper 会不停地从 MessageQueue 中 查看是否有新消息,若是有新消息就会马上处理,不然会一直阻塞,Looper源码。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
可经过 Looper.prepare() 为当前线程建立一个 Looper,默认状况下,Activity会建立一个Looper对象。
除了 prepare 方法外,Looper 还提供了 prepareMainLooper 方法,主要是给 ActivityThread 建立 Looper 使用,本质也是经过 prepare 方法实现的。因为主线程的 Looper 比较特殊,因此 Looper 提供了一个 getMainLooper 方法来获取主线程的 Looper。
同时,Looper 提供了 quit 和 quitSafely 来退出一个 Looper,两者的区别是:quit 会直接退出 Looper,而 quitSafly 只是设定一个退出标记,而后把消息队列中的已有消息处理完毕后才安全地退出。Looper 退出后,经过 Handler 发送的消息会失败,这个时候 Handler 的 send 方法会返回 false,所以在不须要的时候须要及时终止 Looper。
Handler
Handler 的工做主要包含消息的发送和接收的过程。消息的发送能够经过 post/send 的一系列方法实现,post 最终也是经过send来实现的。
在Android开发中,常常会遇到长列表的问题,所以不少时候,就会涉及到RecyclerView的优化问题。对于列表卡顿的缘由,一般有以下一些:
notifyDataSetChanged
若是数据须要全局刷新时,可使用notifyDataSetChanged;对于增长或减小数据,可使用局部刷新,以下所示。
void onNewDataArrived(List<News> news) { List<News> oldNews = myAdapter.getItems(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldNews, news)); myAdapter.setNews(news); result.dispatchUpdatesTo(myAdapter); }
RecycleView嵌套
在实际开发中,常常会看到竖直滚动的RecycleView嵌套一个横向滚动的RecycleView的场景。因为单个RecycleView都拥有独立的itemView对象池,对于嵌套的状况,能够设置共享对象池,以下。
class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> { RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool(); ... @Override public void onCreateViewHolder(ViewGroup parent, int viewType) { // inflate inner item, find innerRecyclerView by ID… LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL); innerRv.setLayoutManager(innerLLM); innerRv.setRecycledViewPool(mSharedPool); return new OuterAdapter.ViewHolder(innerRv); }
嵌套层级过深
经过Systemtrace工具能够检测Layout的性能,若是耗时太长或者调用次数过多,须要考察一下是否过分使用RelativeLayout或者嵌套多层LinearLayout,每层Layout都会致使其child屡次的measure/layout。
对象分配和垃圾回收
虽然Android 5.0上使用ART来减小GC停顿时间,但仍然会形成卡顿。尽可能避免在循环内建立对象致使GC。要知道,建立对象须要分配内存,而这个时机会检查内存是否足够来决定需不须要进行GC。
除了上面的典型场景外,RecyclerView的优化还须要注意如下几点: