Android开发艺术探索笔记

前言

看了《Android开发艺术探索》这本书,整理出一小部分知识点,是按照我个人看书习惯整理的,其他内容后续还会再补充。

android:configChanges属性总结

对android:configChanges属性,一般认为有以下几点:

1.不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
2.设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
3.设置Activity的android:configChanges=”orientation|keyboardHidden”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
但是,自从Android 3.2(API 13),在设置Activity的android:configChanges=”orientation|keyboardHidden”后,还是一样会重新调用各个生命周期的。因为screen size也开始跟着设备的横竖切换而改变。所以,在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,如果你想阻止程序在运行时重新加载Activity,除了设置”orientation”,你还必须设置”ScreenSize”。
解决方法:AndroidManifest.xml中设置android:configChanges=”orientation|screenSize”

Activity的启动模式

目前有四种启动模式:standard、singleTop、singleTask和singleInstance。

1.standard:标准模式,这也是系统的默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。
2.singleTop:栈顶复用模式。这是一种单实例模式,在这种模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以取出当前请求的信息。如果新的Activity的实例已存在但不是位于栈顶,那么新Activity仍会重新创建。
3.singleTask:栈内复用模式。这是一种单例模式,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,和singleTop一样,系统也会回调其onNewIntent。singleTask默认具有clearTop的效果,会导致栈内所有在该实例上面的Activity全部出栈。
4.singleInstance:单实例模式。这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中。

当我们用ApplicationContext去启动standard模式的Activity的时候就报错

这是因为standard模式的Activity默认会进入启动它的Activity所属的任务战中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以这就有问题了。解决这个问题的方法是为待启动Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,这个时候待启动Activity实际上是以singleTask模式启动的。

在singleTask启动模式中,多次提到某个Activity所需的任务栈,什么是Activity所需要的任务栈呢?

这要从一个参数说起:TaskAffinity,可以翻译为任务相关性,这个参数标识了一个Activity所需要的任务栈的名字,默认情况下,所有Activity所需要的任务栈的名字为应用的包名。当然,我们可以为每个Activity都单独指定TaskAffinity属性,这个属性值必须不能和包名相同,否则就相当于没有指定。TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配对使用,在其他情况下没有意义。另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调到前台。
当TaskAffinity和singleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中。
当TaskAffinity和allowTaskReparenting结合的时候,这种情况比较复杂,会产生特殊的效果。当一个应用A启动了应用B的某个Activity后,如果这个Activity的allowTaskReparenting属性为true的话,那么当应用B被启动后,此Activity会直接从应用A的任务栈转移到应用B的任务栈中。

给Activity指定启动模式有几种方法?它们的区别是什么?

1.通过AndroidMenifest.xml为Activity指定启动模式

1
2
3
4
5
<activity
android:name=””
android:configChanges=””
android:launchMode=”singleTask”
android:label=”@String/app_name” />

2.通过在Intent中设置标志位来为Activity指定启动模式,比如:

1
2
3
4
Intent intent = new Intent();
intent.setClass(MainActivity.this, SecondActivity.class);
intent.addFlag(Intent.FLAG\_ACTIVITY\_NEW\_TASK);
startActivity(intent);

这两种方式都可以为Activity指定启动模式,但是二者还是有区别的。首先,优先级上第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;其次,上述两种方式在限定范围上有所不同,比如,第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP表示,而第二种方式无法为Activity指定singleInstance模式。

启动Activity的两种方式

启动Activity分为两种,显式调用和隐式调用。
显式调用需要明确地指定被启动对象的组件信息,包络包名和类名。隐式调用通不需要明确指定组件信息。原则上一个Intent不应该既是显式调用又是隐式调用,如果二者共存的话,以显式调用为主。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity。IntentFilter中的过滤信息有action、category、data。只有一个Intent同时匹配action、category和data才算完全匹配,只有完全匹配才能成功启动目标Activity。一个Activity中可以有多个intent-filter,一个Intent只要能匹配任何一组intent-filter即可成功启动对应的Activity。

系统为什么不允许在子线程中访问UI呢?

因为Android的UI控件 不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:首先加上锁机制会让UI访问的逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说则无法获取到数据。
ThreadLocal的应用场景:当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。

为什么通过ThreadLocal可以在不同的线程中维护一套数据副本并且彼此互不干扰?

ThreadLocal的set和get方法可以看出,它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读、写操作仅限于各自线程的内部。

消息队列的工作原理

消息队列在Android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是网消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将器从消息队列中移除。
MessageQueue内部并不是用的队列,实际上它是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中移除。

Looper的工作原理

Looper在Android的消息机制中扮演消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。
在构造方法中它会创建一个MessageQueue即消息队列,然后将当前线程的对象保存起来。
通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。
Looper中还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。Looper还提供了getMainLooper方法,通过它可以再任何地方获取到主线程的Looper。
Looper提供了quit和quitSafely来退出一个Looper,二者的区别是:quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全地退出。
Looper的loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息被标记为退出状态时,它的next方法就会返回null。也就是说Looper必须退出,否则loop方法就会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。

Handler的工作原理

Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段。
创建Handler最常见的方式就是派生一个Handler的子类并重写其handleMessage方法来处理具体的消息。如果我们不想派生子类,就可以通过Callback来实现。Handler handler = new Handler(callback);

主线程的消息循环模型

ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行。
注:H内部定义了一组消息类型,主要包含了四大组件的启动和停止等过程。

AsyncTask、HandlerThread和IntentService三种特殊的线程

AsyncTask它的底层用到了线程池,而HandlerThread和IntentService底层直接使用了线程。
AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService是一个服务,系统对其进行了封装使其可以更方便地执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出。

AsyncTask

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。它封装了线程池和Handler。
AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result这三个泛型参数,其中Params表示参数的类型,Progress表示后台任务的执行进度的类型,而Result则表示后台任务的返回结果的类型。
AsyncTask的4个核心方法:
onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。
doInBackground(Params…params),在线程池中执行,此方法用于执行异步任务。params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute方法。
onProgressUpdate(Progress…values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。
onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台任务的返回值,即doInbackground的返回值。
AsyncTask在具体的使用过程中的一些限制:
AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程。
AsyncTask的对象必须在主线程中创建。
execute方法必须在UI线程调用。
不要在程序中直接调用onPreExecute()、onPostExecute()、doInBackground()和onProgressUpdate()。
一个AsyncTask对象只能执行一次,即只能调用一次execute(),否则会报运行时异常。
在Android1.6之前,AsyncTask是串行执行任务的,Android1.6的时候AsyncTask开始采用线程池处理并行任务,但是从Android3.0开始,为了避免AsyncTask所带来的并发错误,AsyncTask又采用一个线程来串行执行任务。尽管如此,在Android3.0以及后续的版本中,我们仍然可以通过AsyncTask的executeOnExecutor方法来并行地执行任务。
AsyncTask的工作原理
从Android3.0开始,默认情况下AsyncTask是串行执行的。
AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。

HandlerThread

HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现也很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际的使用中就允许在HandlerThread中创建Handler了。
普通Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit或者quitSafely方法来终止线程的执行。

IntentService

IntentService是一种特殊的Service,它继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行完后它会自动停止,同时由于IntentService是服务的原因,这导致它的优先级比单纯的线程要高很多,所以IntentService比较适合执行一些高优先级的后台任务,因为它优先级高不容易被系统杀死。
IntentService封装了HandlerThread和Handler。

1
2
3
4
5
6
7
8
9
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

当IntentService被第一次启动时,它的onCreate方法会被调用,onCreate方法会创建一个HandlerThread,然后使用它的looper来构造一个Handler对象mServiceHandler,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行,从这个角度来看,IntentService也可以用于执行后台任务。每次启动IntentService,它的onStartCommand方法就会调用一次,IntentService在onStartCommand中处理每个后台任务的Intent。

1
2
3
4
5
6
7
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

IntentService仅仅是通过mServiceHanlder发送了一个消息,这个消息会在HandlerThread中被处理。mServiceHandler收到消息之后,会将Intent对象传递给onHandlerIntent方法去处理。注意这个Intent对象的内容和外界的startService(intent)中的intent内容完全是一致的,通过这个Intent对象就可以解析出外界启动IntentService时所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandleIntent方法中就可以对不同的后台任务做处理。当onHandleIntent方法执行结束后,IntentService会通过stopSelf(int startId)而不是stopSelf()来停止服务,那是因为stopself()会立刻停止服务,而这个时候还可能有其他消息未处理,stopself(int startId)则会等待所有的消息都处理完毕后才终止服务。
IntentService的onHandleIntent方法是一个抽象方法,它需要我们在子类中实现,它的作用是从Intent参数中区分具体的任务并执行这些任务。如果目前只存在一个后台任务,那么onHandleIntent方法执行完这个任务后,stopSelf(int startId)就会直接停止服务,如果目前存在多个后台任务,那么当onHandleIntent方法执行完最后一个任务时,stopSelf(int startId)才会直接停止服务。另外,由于每执行一个后台任务就必须启动一次IntentService,而IntentService内部则通过消息的方式向HandlerThread请求执行任务,Handler中的Looper时顺序处理消息的,这就意味着IntentService也是顺序执行后台任务的,当有多个后台任务同时存在的时候,这些后台任务会按照外界发起的顺序排队执行。

Android中的线程池

线程池的优点:
重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞现象。
能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
ThreadPoolExecutor是线程池的真正实现
ThreadPoolExecutor执行任务时大致遵循如下规则:
如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
如果线程池中的线程数量已经达到或超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
如果步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。
AsyncTask对THREAD_POOL_EXECUTOR这个线程池的配置规格如下:
核心线程数等于CPU核心数+1;
线程池的最大线程数为CPU核心数的2倍+1;
核心线程无超时机制,非核心线程在闲置时的超时时间为1秒;
任务队列的容量为128。
Android中最常见的四类具有不同功能特性的线程池分别是:FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。

FixedThreadPool
通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定对的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭了。
CachedThreadPool
通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池。它只有非核心线程,并且其最大线程数为Interger.MAX_VALUE。当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时时长为60秒,超过60秒闲置线程就会被回收。
ScheduledThreadPool
通过Executors的newSheduledThreadPool方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收。
SingleThreadExecutor
通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。

AMS和Binder(未完待续…)