一.Android消息机制
1.Android消息机制主要指的是Handler的运行机制,Handler的运行需要底层MessageQueue和Looper的支撑。MessageQueue以队列的形式对外提供插入和删除的工作,但是它内部的储存结构并不是真正的队列,而是采用单链表的数据结构(单链表在插入和删除上比较有优势)进行存储消息。而Looper可作为消息循环,因为MessageQueue只是一个消息的存储单元,它不能去处理消息,而looper就填补了这个功能,它会以无限循环的形式去查找是否有新的消息,有的话就处理,没有就继续等待。这里还有一个概念,就是ThreadLocal,它的作用是在每个线程中存储数据。它有什么用?例如,Handler在创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部是如何获取到当前线程的Looper的?这就是使用到ThreadLocal了。它可以在不同的线程中互不干扰的储存并提供数据,通过ThreadLocal可以轻松的获取每个线程的Looper。
2.系统为什么不允许在子线程访问UI呢?
这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么系统不对UI控件的访问加上锁机制呢?what?有两个缺点:一.首先加上锁机制会让UI访问的逻辑变得复杂;二.其次 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。鉴于这两个缺点,最简单最高效的就是采用单线程模型来处理UI操作
3.来说说ThreadLocal
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。使用场景:1.当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。2.比如你想在线程里面加一个监听,这样使用ThreadLocal就可以把每个监听器对象都在自己的线程内部存储。
是不是觉得说了等于没有?来个实践就知道是什么意思了。
private ThreadLocal<Boolean>mThreadLocal =new ThreadLocal<Boolean>();
mThreadLocal.set(true);
Log.i(TAG,"Thread-main mThreadLocal="+mThreadLocal.get());
new Thread("Thread1"){
@Override
public void run(){
mThreadLocal.set(false);
Log.i(TAG,"Thread1 mThreadLocal="+mThreadLocal.get());
};}.start();
new Thread("Thread2"){
@Override
public void run(){
mThreadLocal.set(false);
Log.i(TAG,"Thread2 mThreadLocal="+mThreadLocal.get());
};}.start();
输出结果:
Thread-main mThreadLocal=true;Thread1 mThreadLocal=false;
Thread2 mThreadLocal=null;
虽然是同一个ThreadLocal对象,但是取到的值却不一样。ThreadLocal之所以能够这样,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数组中根据当前ThreadLocal的所以去查找对应的value;
ThreadLocal的存储规则:ThreadLocal的值会存储在一个名为table的数组里面,而存储的位置总是为ThreadLocal的reference字段所标识的对象的下一个位置,比如ThreadLocal的reference的对象在table数组的坐标为index,那么ThreadLocal的值就是在索引index+1的位置上。再通过get()方法获取ThreadLocal的值的时候,会先去查找到reference的索引index,然后再返回index+1的值,从而获取到ThreadLocal的值
4.整体Handler机制的过程
Handler.sendMessage()--->MessageQueue.enqueueMessage---->lopper.loop()------>MessageQueue.next()(返回Message对象)----->msg.target.dispatchMessage(Lopper中调用,msg.target指的是handler)------>if(msg.callback!=null)handleCallback(msg)或者if(mCallback!=null)handlerMessage(msg)
handleCallback的逻辑(handler.post执行该函数) : message.callback.run();
handlerMessage的逻辑(handleMessaget执行该函数)
二.Android的线程和线程池
1.采用线程池的原因:线程是操作系统调度的最小单元,同时线程又是一种受限制的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销。当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行,除非线程的数量小于等于CPU的核心数,一般来说值不可能的,而采用线程池,一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。
2.AsyncTask的缺点:
AsyncTask的类必须在主线程中加载
AsyncTask的对象必须在主线程中创建
AsyncTask的execute必须在主线程中调用
不能直接调用onPreExecute(),onPostExecute,doInBackground和onProgressUpdate方法
一个AsyncTask对象只能执行一个,即只能调用一次execute方法,否则异常
3.intentService和HandlerThread
在内部handler执行handleMessage的方法中,当执行完里面的方法时,IntentService就会执行stopSelf。需要注意的是,每执行一次后台任务都必须启动一次IntentService,即进行startService
4. 线程池的分类
1.SingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行>所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池>保证所有任务的执行顺序按照任务的提交顺序执行。
2.FixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3.CachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.ScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
三.Android的图片缓存
1.我们通常会通过BitmapFactory.Option来缩放图片,主要用到的是inSampleSize参数,而官方文档指出,inSampleSize 的取值最好是2的指数
2.三级缓存:存储过程:网络-->(本地存储设备)DiskLruCache-->(内存)LruCache 取的时候: (内存)LruCache -->(本地存储设备)DiskLruCache -->网络
3.LruCache内部采用一个LinkedHashMap以强应用的方式存储外界的缓存对象,使用的是近期最少使用算法,软引用和弱引用在Android高版本已经不再使用,因为系统都会去及时回收,起不到缓存的作用。