
暂无个人介绍
ThreadLocal 开胃菜 研究过Handler的应该对ThreadLocal比较眼熟的,线程中的Handler对象就是通过ThreadLocal来存放的。初识ThreadLocal的可能被它的名字有所误导,ThreadLocal初一看可能会觉得这是某种线程实现,而实际并非如此。事实上,它是一个全局变量,用来存储对应Thread的本地变量,这也是为什么将其称之为Local。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 比如Handler,当我们在一个线程中创建了一个Handler时,在调用Looper.prepare()时通过ThreadLocal保存了当前线程下的Looper对象,而所有线程的Looper都由一个ThreadLocal来维护,也就是在所有线程中创建的Looper都存放在了一个ThreadLocal中,然后创建Handler将Handler与当前线程Looper关联,当调用Looper.loop()的时候通过myLooper()得到的就是当前线程的Looper,当在其他线程使用Handler来发送消息的时候,其实也就是将对应的Message存储到了对应Handler的MessageQueue中,当Looper去分发消息的时候,就是将当前线程中的Looper对应的MessageQueue中的Message通过Handler的回调返回给了Handler所在的线程。如对Handler不了解的,可参考Handler全面解读 threadlocal_handler.png ThreadLocal模拟 那么,如何来做到区分不同线程中的变量呢?我们这里模拟一个实现ThreadLocal功能的类,原理大致一样。 public class ThrealLocalImitation<T> { private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>(); public synchronized void set(T threadData) { Thread thread = Thread.currentThread(); mSaveValues.put(thread, threadData); } public synchronized T get() { Thread thread = Thread.currentThread(); return (T) mSaveValues.get(thread); } } 如上ThreadLocal模仿类里面,通过全局的Map变量sSaveValues,以Thread为key,value为对应线程需要保存的变量,实现了,每个线程对应保存了一个变量,在不同的线程存储不同的变量,通过get方法就能取回对应的值。 ThreadLocal原理分析 ThreadLocal类通过set、get方法来分别存取变量的,搞懂了这两个方法的功能也就明白ThreadLocal的原理了,所以重点分析这两个方法。 在进入正式的分析之前先来看一个类——ThreadLocalMap 和我们模拟的ThreadLocal稍有所区别,ThreadLocal不是直接通过一个Map来存储Thread和value对应关系的。 在Thread类中,有一个变量ThreadLocal.ThreadLocalMap。 先看下该类的其中一个构造方法 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ThreadLocalMap内部维护一个数组Entry[] table, Entry对应存储了key--value(ThreadLocal---value)。ThreadLocalMap实际上是一个实现了自定义的寻址方式的HashMap。 那么ThreadLocal是如何存储线程本地变量的呢?先给个简单的结论。 每个Thread在生命周期中都会维护着一个ThreadLocalMap,可以看成是一个存储了ThreadLocal(key)---value的HashMap,当ThreadLocal存储value时,先通过当前Thread得到其维护的ThreadLocalMap,然后将其存储到该map中,而获取value时则是先获取到当前线程的ThreadLocalMap,然后通过当前的ThreadLocal,获取到ThreadLocalMap存储的value值。 set()方法 public void set(T value) { Thread t = Thread.currentThread();//获取到当前线程 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } set方法中,首先通过Thread.currentThread()获取到当前的线程,通过当前线程获得其维护的ThreadLocalMap,当map为空时,则为当前Thread创建一个ThreadLocalMap,不为空的话则将ThreadLocal--value存储到map中。 所以一个Thread对应着一个ThreadLocalMap,而一个ThreadLocalMap对应着多个ThreadLocal。 get()方法 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(); } get()方法,同样是先获取到当前Thread,然后获取到当前Thread的ThreadLocalMap,然后根据ThreadLocal自身,通过ThreadLocalMap自身的寻址方式获取到存储ThreadLocal和value的Entry对象,进而得到value。 setInitialValue();是当ThreadLocalMap为空时,可以通过实现ThreadLocal的initialValue()来获得一个默认值,同时该默认值会被存储到线程的ThreadLocalMap中。 内存泄漏 分析到这里,ThreadLocal的原理已经很明朗了。但是一些使用不当的情况出现内存泄漏的风险,所以最后讲解下ThreadLocal会出现的内存泄漏风险,及如何避免。 ThreadLocalMap中存储的Entry为ThreadLocal--value,准确的描述应该是weakReference(ThreadLocal)--value,即,key(ThreadLocal为弱引用),而value则是强引用的,当ThreadLocal为空后,Thread不会再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然还持有value的强引用,导致value需要等待线程生命周期结束才可能被GC回收。当出现一些长时间存在的线程,不断的存储了内存比较大的value,而value实际是不再被使用的,value由于线程没有被回收而不断的堆积,造成了内存泄漏。比如当使用到线程池是,Thread很有可能不会被马上结束,可能会被不断的重复利用。 所以这里引入ThreadLocal的另外一个方法——remove方法 remove()方法 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } //ThreadLocalMap中的remove private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[I]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } 所以在value不再使用时,应该及时调用remove,解除线程对该value的引用。
HandlerThread 在理解了Handler的原理之后,我们知道在一个子线程中创建一个Handler不能缺少了Looper.prepare()和Looper.loop()两个方法,具体的原因这里不再赘述,不熟悉原理的可以先看下另一篇文章Handler的原理解析. 本篇文章主要是讲解HandlerThread的使用的。 首先HandlerThread是继承于Thread类的,所以本质上HandlerThread就是一个线程,接下来就详细的去看一看,这是怎样的一个线程? 首先,先看下它的构造函数: public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread(String name, int priority) { super(name); mPriority = priority; } //很简单,构造函数里只是设置了该Thread的名称和优先级。 既然是线程,那么最重要的当然是run方法来,看完了run方法,相信你也就明白HandlerThread的用途了! @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; } 怎么样,这是不是和我们之前在子线程中创建Handler一样,首先是Looper.prepare(),接着是new Handler(), 最后是Looper.loop()。等等,这里并没有创建Handler啊!别急,我们先一步一步地看看run方法再说为什么没有创建Handler。 首先呢是调用了Looper.prepaer(),该方法为我们的线程创建了一个唯一的Looper和MessageQueue对象,具体的创建过程看另一篇文章Handler的原理解析 接下来有一个同步锁的代码块,里面获取到了创建好的Looper对象将其赋值给当前的mLooper,然后唤醒了锁。注意这里有一个唤醒线程的操作,既然有唤醒锁的操作,那么必定有有个地方使线程处于了阻塞的状态,我们看下出现阻塞的地方。 public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; } 通过查找发现到一个getLooper()的方法,该方法返回了当前线程的mLooper对象,还记得Looper是在哪里进行赋值的吗?在线程的run方法里,所以当线程启动之后才能创建Looper并赋值给mLooper,这里的阻塞就是为了等待Looper的创建成功。同时该方法是用Public修饰的,说明该方法是提供外部调用的,Looper创建成功提供给外部使用。 接着我们回到run方法,Looper和MessageQueue已经创建成功了,接下来就是启动Looper循环了(即Looper.loop()),别忘了只有Looper循环启动后我们才能时刻观察着MessageQueue,只要有Message了才能立马将Message取出来进行分发处理。 在Looper.loop()之前还调用了一个onLooperPrepared()方法,这个方法是干嘛的呢? 看代码可知,只是一个空方法,在使用HandlerThread时重写该方法,方便在Looper轮询消息之前做一些初始化的操作。 /** * Call back method that can be explicitly overridden if needed to execute some * setup before Looper loops. */ protected void onLooperPrepared() { } 最后在对象销毁前,调用下面的方法退出Looper循环 public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false; } public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false; } quit方法实际是调用MessagQueue的removeAllMessagesLocked,移除所有延迟和非延迟的消息, quitSafely方法调用的是removeAllFutureMessagesLocked方法,该方法只清除延迟的消息,非延迟的消息 还是会进行分发处理。 HandlerThread分析完啦,是不是有点蒙,自始至终都没有出现Handler,HandlerThread要怎么用呢? 下面我们就通过一个Demo来说明下HandlerThread是怎么用的? private WorkHandler mHandler; private HandlerThread mHandlerThread; /*Handler存在一个构造函数,传入一个Looper对象,Handler的handleMessage获取的是Looper的MessageQueue中的Message 因此,handleMessage的调用与Looper对象同属于一个线程,这里我们在构造时传入HandlerThread的Looper对象, handleMessage运行于HandlerThread线程(也就是一个子线程),所以Handler虽然是在住线程创建,但是它的 handleMessage接收到消息是在HandlerThread线程,执行下代码可以看到打印出如下log: D/HandlerThreadDemo: HandlerThread/Demo thread receiver the message from thread: main log也说明,message由主线程传递到了HandlerThread中。 */ private class WorkHandler extends Handler { WorkHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); String str = (String) msg.obj; Log.d(TAG, Thread.currentThread().getName() + " thread receiver the message from thread: " + msg.obj); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handlerthread_demo); mHandlerThread = new HandlerThread("HandlerThread/Demo"); mHandlerThread.start(); mHandler = new WorkHandler(mHandlerThread.getLooper()); Message msg = mHandler.obtainMessage(); msg.obj = Thread.currentThread().getName(); mHandler.sendMessage(msg); } 总结: HandlerThread继承于Thread,本质上也是一个线程。 HandlerThread的run方法为本线程创建提供了Looper和MessageQueue对象,并开启了Looper轮询消息。 通过在需要发送Message的线程中创建Handler,为Handler提供来自HandlerThread的Looper对象。Handler则能 将消息发送到HandlerThread上去进行处理。 注意:这里Handler不仅仅能在主线程创建,在子线程同样能够创建,只需要将对应的Looper提供给Handler即可,所以HandlerThread 不仅适用于和主线程通信,同样适用于和其他子线程通信。 最后需要注意的是在我们不需要这个looper线程的时候需要手动停止掉,即调用quit()或者quitSafely()。 最后补充一个在实际开发过程中使用到HandlerThread的场景: 存在多个耗时的任务需要放到开启子线程依次去处理(串行处理任务),首先,HandlerThread是一个子线程, 适合处理耗时的任务,其次,Handler分发消息是通过MessageQueue顶部的Message不断的通过Message的next依次取出 Message,符合任务的按顺序串行处理的要求,所以使用HandlerThread就能完美的解决此需求。
Handler进阶之sendMessage 本文主要进一步的探索Handler,主要介绍下Handler是如何发送消息的? 用过Handler的想必对一下几个方法都不会陌生: sendMessage(Message msg);//立刻发送消息 sendMessageAtTime(Message msg, long atTime);//在某个时间点发送消息 sendMessageDelayed(Message msg, long delayedTime);//在当前时间点延迟一段时间发送消息 以上是三个Handler发送消息的方法,区别在于发送的时间点不一致,但其实三个方法在最终都是执行Handler内的同一个方法,只是在参数上稍有区别: public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } sendMessage(Message msg); //对应 sendMessageDelayed(msg, 0); sendMessageAtTime(Message msg, long atTime); //对应 sendMessageDelayed(msg, atTime) sendMessageDelayed(Message msg, long delayedTime); //对应 sendMessageDelayed(msg, SystemClock.uptimeMillis() + delayedTime) 从上面的代码能够看出,最终的消息发送都是传入了要发送Message对象和对应需要发送Message的时间点。 我们猜想,Handler发送消息难道是根据Message需要发送的时间点设置一个定时器让Message在某个时间点被发送出去? 如果仅此而已的话,你也太小看google的大牛们了。是不是已经有点迫不及待想直到大牛们都是怎么来做的了? 这里将中间调用的几个简单的过程跳过,熟悉Java的一眼就能看懂,直接进入发送消息时最核心的部分, 我们都知道Handler发送消息其实是将Message先放入到了MessageQueue,能看到该文章的我默认大家都已经很熟悉 Handler的基本原理了,如果又不熟悉的,可以参考 Handler消息机制原理全方文解读 此处直接看Message放入MessageQueue的过程: 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; } 重点就在if-else这里,将它们一个一个拆开来看: if (p == null || when == 0 || when < p.when) {//p是当前MessageQueue队首Message // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } 如果当前队列没有其他需要发送的Message;或者当前新添加进来的的Message的时间点为0(即需要立即发送的消息); 或者当前新添加进来的的Message需要发送的时间点小与当前MessageQueue队列头部Message的时间点(即当前添加进来的Message需要在当前MessageQueue队列头部Message之前被发送)时,就会进入到if代码块里面。此时做的事情是将当前新添加的Message插入到了MessageQueue的队首(看不懂是如何插入的可以参考Handler消息机制原理全方文解读, Message 的存储是链表的形式,next相当于链表的尾指针)。 这里还有一个赋值操作(needWake = mBlocked),这里解释下,可以看到代码里也有注释,新的首部,然后如果阻塞了,需要唤醒线程。为什么会有线程的阻塞呢?其实MessageQueue内部的消息是按需要发送的时间点从小到大排列的,后面会分析到,从当前if里的when判断也能看出一二,当队首的Message未到达发送的时间点时,说明其当前所有的消息都未到达发送的时间,上面说过,Handler发送消息并不是通过定时器发送的,所以,当队首Message(最近需要发送的Message)未到达发送时间点时,线程被阻塞,所以这里需要根据线程是否阻塞看是否需要唤醒线程,这样才能使新加入的Message能及时发送出去,不会被阻塞。线程的唤醒是通过native的方法来实现的。 接着来看下else里面的代码: 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; 执行到了else语句里面,说明了当前添加进来的Message是在当前MessqgeQueue队首的Message之后才会被发送的,上边分析if部分代码的时候说过了Message是按需要发送的时间先后排列在MessageQueue中的,这里的for循环实际操作就是找到MessageQueue中比当前添加进来的Message需要发送的时间点大的位置,将Message插入到其前边(实际就是一个链表的插入操作)。 本章分析到这里就先告一段落了,之后会再详细的分析Message从MessageQueue的取出过程,可以先参考Handler消息机制原理全方文解读。 之所以写这篇文章主要是为了解答两个问题。 sendMessageDelayed是如何实现延时发送消息的? sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message? 总结: Handler在发送消息的时候,MessageQueue里的消息是按照发送时间点从小到大排列的, 如果最近的Message未到达发送的时间则阻塞。 新加入的数据会根据时间点的大小判断需要插入的位置,同时还需要判断是否需要唤醒线程去发送当前的队首的消息。
Handler 本文主要详细去解读Android开发中最常使用的Handler,以及使用过程中遇到的各种各样的疑问。 在Android开发的过程中,我们常常会将耗时的一些操作放在子线程(work thread)中去执行,然后将执行的结果告诉UI线程(main thread),熟悉Android的朋友都知道,UI的更新只能通过Main thread来进行。那么这里就涉及到了如何将 子线程的数据传递给main thread呢? Android已经为我们提供了一个消息传递的机制——Handler,来帮助我们将子线程的数据传递给主线程,其实,当熟悉了Handler的原理之后我们知道,Handler不仅仅能将子线程的数据传递给主线程,它能实现任意两个线程的数据传递。 接下来,我们便详细的了解下Handler的原理及其使用。 首先看一下Handler最常规的使用方式: private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MESSAGE_WHAT: Log.d(TAG, "main thread receiver message: " + ((String) msg.obj)); break; } } }; private void sendMessageToMainThreadByWorkThread() { new Thread(){ @Override public void run() { Message message = mHandler.obtainMessage(MESSAGE_WHAT); message.obj = "I am message from work thread"; mHandler.sendMessage(message); } }.start(); } /* * 通常我们在主线程中创建一个Handler, * 然后重写该Handler的handlerMessage方法,可以看到该方法传入了一个参数Message, * 该参数就是我们从其他线程传递过来的信息。 * * 我们在来看下子线程中如何传递的信息,子线程通过Handler的obtainMessage()方法获取到一个Message实例, * 我们来看看Message的几个属性: * Message.what------------------>用来标识信息的int值,通过该值主线程能判断出来自不同地方的信息来源 * Message.arg1/Message.arg2----->Message初始定义的用来传递int类型值的两个变量 * Message.obj------------------->用来传递任何实例化对象 * 最后通过sendMessage将Message发送出去。 * * Handler所在的线程通过handlerMessage方法就能收到具体的信息了,如何判断信息的来源呢?当然是通过what值啦。 * 怎么样很简单吧 */ 文章的开头说过,Handler不仅仅是能过将子线程的数据发送给主线程,它适用于任意两个线程之间的通信。 下面我们来看下两个子线程之间如何进行通信的。 很简单啊,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage发送消息啊! 写程序可不能关说不练啊,我们把代码敲出来看一下! private Handler handler; private void handlerDemoByTwoWorkThread() { Thread hanMeiMeiThread = new Thread() { @Override public void run() { // Looper.prepare(); handler = new Handler() { @Override public void handleMessage(Message msg) { Log.d(TAG, "hanMeiMei receiver message: " + ((String) msg.obj)); Toast.makeText(MainActivity.this, ((String) msg.obj), Toast.LENGTH_SHORT).show(); } }; // Looper.loop(); } }; Thread liLeiThread = new Thread() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Message message = handler.obtainMessage(); message.obj = "Hi MeiMei"; handler.sendMessage(message); } }; hanMeiMeiThread.setName("韩梅梅 Thread"); hanMeiMeiThread.start(); liLeiThread.setName("李雷 Thread"); liLeiThread.start(); /* * 搞定,我们创建了两个Thread,liLeiThread和hanMeiMeiThread两个线程,很熟悉的名字啊! * 跟之前的代码没太大区别hanMeiMeiThread创建了Handler,liLeiThread通过Handler发送了消息。 * 只不过此处我们只发送一个消息,所以没有使用what来进行标记 * 运行看看,我们的李雷能拨通梅梅吗? * 啊哦,出错了 * 05-13 17:08:17.709 20673-20739/? E/AndroidRuntime: FATAL EXCEPTION: 韩梅梅 Thread Process: design.wang.com.designpatterns, PID: 20673 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:200) at android.os.Handler.<init>(Handler.java:114) *Can't create handler inside thread that has not called Looper.prepare() * -----------》它说我们创建的handler没有调用Looper.prepare(); * 好的,我们在实例化Handler之前调用下该方法,看一下。加上是不是没有报错了呢。 * 等等,虽然没有报错,但是hanMeiMeiThread也没有接到消息啊,消息呢?别急。 * 我们在Handler实例化之后加上Looper.loop();看一看,运行一下,是不是收到消息了呢。 * 只是为什么呢? * 接下来我们就去看看Handler是怎么实现的发消息呢,弄清楚了原理,这里的原因也就明了了。 */ } 好了,卖了半天的关子,终于要开始真正的主题了。 首先我们来看下,为什么在子线程里实例化的时候不调用Looper.prepare()就会报错呢? //我们先来看看new Handler();时出错的原因。后续讲解源码分析只贴出关键部分。 //如下是Handler构造函数里抛出上文异常的地方,可以看到,由于mLooper对象为空才抛出的该异常。 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } /* 异常的原因看到了,接下来我们看看Looper.prepare()方法都干了些什么? */ public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } /* 可以看到,该方法在当前thread创建了一个Looper(), ThreadLocal主要用于维护线程的本地变量, */ private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } //而Looper的构造函数里面又为我们创建了一个MessageQueue()对象。 了解到此,我们已经成功引出了Handler机制几个关键的对象了,Looper、MessageQueue、Message。 那么,肯定也有人又产生新的疑问了——为什么在主线程中创建Handler不需要要用Looper.prepare()和Looper.loop()方法呢? 其实不是这样的,App初始化的时候都会执行ActivityThread的main方法,我们可以看看ActivityThread的main()方法都做了什么? Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); /* 真相只有一个,是的在创建主线程的时候Android已经帮我们调用了Looper.prepareMainLooper() 和Looper.loop()方法,所以我们在主线程能直接创建Handler使用。 */ 我们接着来看Handler发送消息的过程: //调用Handler不同参数方法发送Message最终都会调用到该方法 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } sendMessage的关键在于enqueueMessage(),其内部调用了messageQueue的enqueueMessage方法 boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } 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 { 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被存入MessageQueue时是将Message存到了上一个Message.next上, 形成了一个链式的列表,同时也保证了Message列表的时序性。 */ Message的发送实际是放入到了Handler对应线程的MessageQueue中,那么,Message又是如何被取出来的呢? 细心的朋友可能早早就发现了,之前抛出异常的地方讲解了半天的Loop.prepare()方法,一直没有说到Loop.loop()方法。同时,在之前的例子中也看到了,如果不调用Looper.loop()方法,Handler是接受不到消息的,所以我们可以大胆的猜测,消息的获取肯定和它脱不了关系!当然关怀疑还不行,我们还必须找出真相来证明我们的猜想?那还等什么,先看看loop()方法吧。 public static void loop() { //可以看到,在调用Looper.prepare()之前是不能调用该方法的,不然又得抛出异常了 final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } } /* 这里我们看到,mLooper()方法里我们取出了,当前线程的looper对象,然后从looper对象开启了一个死循环 不断地从looper内的MessageQueue中取出Message,只要有Message对象,就会通过Message的target调用 dispatchMessage去分发消息,通过代码可以看出target就是我们创建的handler。我们在继续往下分析Message的分发 */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } /*好了,到这里已经能看清晰了 可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法 */ private static void handleCallback(Message message) { message.callback.run(); } //即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法: runOnUiThread(new Runnable() { @Override public void run() { } }); public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } /* 而如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的, 所以handler的handlerMessage方法的执行也会在主线程中。 */ 到这里,想必你应该清楚如何在不同的线程之间来使用Handler了吧。 最后总结一下: 在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler, 但是只能有一个Looper和一个MessageQueue。 Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个 Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。 Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message, 然后通过handler将消息分发传回handler所在的线程。 最后附上一张自己理解画出来的流程图: 20180513192006823.png Handler补充: 1. Handler在使用过程中,需要注意的问题之一便是内存泄漏问题。 为什么会出现内存泄漏问题呢? 首先Handler使用是用来进行线程间通信的,所以新开启的线程是会持有Handler引用的, 如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。 首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。 同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会造成内存泄漏。 解决的办法: (1). 使用静态内部类+弱引用的方式: 静态内部类不会持有外部类的的引用。 private Handler sHandler = new TestHandler(this); static class TestHandler extends Handler { private WeakReference<Activity> mActivity; TestHandler(Activity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = mActivity.get(); if (activity != null) { //TODO: } } } (2). 在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。 @Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); } 2. 在使用Handler时,通常是通过Handler.obtainMessage()来获取Message对象的,而其内部调用的是Message.obtain()方法,那么问题来了,为什么不直接new一个Message,而是通过Message的静态方法obtain()来得到的呢? 下面就通过代码来一探究竟 public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } 其实在在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。 20180608180422985.jpg 接着看下sPool中缓存的Message是哪里来的? public void recycle() { if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } recycleUnchecked(); } void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } } recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。 recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部) 20180608180812257.jpg 总结: 由此可见,使用obtain获取Message对象是因为Message内部维护了一个数据缓存池,回收的Message不会被立马销毁,而是放入了缓存池, 在获取Message时会先从缓存池中去获取,缓存池为null才会去创建新的Message。 3. Handler sendMessage原理解读。 引入问题! sendMessageDelayed是如何实现延时发送消息的? sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message? 详细分析请移步下篇文章:Handler进阶之sendMessage原理探索
语言区域和国际化 Android 7.0引入了能指定默认类别语言区域的概念,某些API在使用默认的DISPLAY类别语言区域时,仍然使用的是不带参数Locale.getDefault()函数。在Android 8.0,以下的一些函数使用Locale.getDefault(Category.DISPLAY)来代Locale.getDefault(): Currency.getDisplayName(); Currency.getSymbol(); Locale.getDisplayScript(). 当Locale参数指定的displayScript值不可用时,Locale.getDisplayScript(Locale)回退到Locale.getDefault(). 以上的函数调用时传入参数为null时,都会抛出NullPointerException 时区名称的分析方法发生了变化。之前,Android设备使用在启动时取样的系统时钟值,用来分析日期时间的时区名称。所以如果在启动时或者一些少数情况下系统时钟出现错误,会导致分析结果出现问题。现在,一般情况下,分析时区名称时使用ICU和当前的系统时钟值。此种分析方法能提供更准确的结果,使用SimpleDateFormat等类可能会与之前的Android版本得到的结果不同。 3.Android 8.0的ICU库更新到了版本58. 隐私性 Android 8.0对平台的隐私性有关的变更: 改变了标识符的处理方式。在OTA之前安装的Android 8.0的应用,ANDROID_ID的值保持不变,除非在OTA之后卸载了又重新安装,在OTA后在卸载期间要保持ANDROID_ID值保留,可以利用key/value来备份。 2.在Android 8.0上,由应用签署秘钥、用户和设备来确定唯一的ANDROID_ID值,因此在相同的设备上同一用户下运行的具有不同签名应用页不会有相同的Android ID。 签名相同,在OTA到Android 8.0之前未安装的应用,ANDROID_ID的值在软件包卸载或重新安装时就不会发生变化。 系统更新导致软件包签名发生变化,ANDROID_ID的值也不会变。 要借助一个简单、标准的系统实现获利的应用,使用广告ID, 广告 ID 是 Google Play 服务针对广告服务提供的唯一 ID,此 ID 可由用户重置。 查询 net.hostname 系统属性返回的结果为空。 系统属性 net.dns1、net.dns2、net.dns3 和 net.dns4 不再可用。 获取DNS之类的网络的连接信息,具有ACCESS_NETWORK_STATE权限的应用可以注册NetworkRequest 或 NetworkCallback 对象来获取,在Android5.0后才可使用。 废弃Build.SERIAL 。新的获取硬件序列号的的函数为Build.getSerial(),需要READ_PHONE_STATE权限。 LauncherApps API不再允许work profile apps访问primary profile apps的信息,与之前一样,尝试访问没有关联的个人资料会引发SecurityExceptions。当某个用户被配置在托管配置文件中时,LauncherApps API的行为就像同一的配置文件组的其它配置文件中没有安装任何应用一样。 http://blog.csdn.net/wsq_tomato/article/details/79180475
Java设计模式,写漂亮的代码 ————模板方法设计模式 简介: 模板方法设计模式是Java设计模式中很简单、应用非常广泛的的一种设计模式,该模式体现了编程的抽象思想(抽象是所有子类的共性封装), 仅仅使用了Java的继承机制。其实很可能在你写代码的过程中已经使用过了很多次这种模式,只是你还不太清楚那就是模板方法设计模式。 接下来就让我们一起去看看,到底什么是模板方法设计模式呢? 首先看一下描述: 定义一个操作中的算法的框架,而将一些步骤延迟到了子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。 UML类图描述: 下图就是模板方法设计模式的UML类图描述,正如你所见,没错,就是这么简单,一目就能了然。 模板方法模式UML类图 其中AbstractClass叫做抽象模板类,实现了模板方法,定义了算法的骨架,它的方法分为两类: 基本方法:基本方法也叫作基本操作,是由子类实现的方法,并且在模板方法被调用. 模板方法:可以有一个或者几个,一般是一个具体方法,也就是一个框架,实现对基本方法的调度,完成固定的逻辑。 具体类:ConcreteClass:实现抽象类中的抽象方法,完成完整的算法。 下面通过一个简单的例子描述下模板方法模式的应用: 本例子通过模拟手机情景模式的过程,简单描述下模板方法的应用: public abstract class SceneMode { //这里定义了一个抽象类,即模板方法模式的抽象模板类,把子类共有的一些操作抽象到该类中,情景模式普遍就是响铃和震动的操作,这里将通用的流程放到该类中,具体的配置通过其子类来完成 private static final String TAG = "SceneMode"; private static final String DEFAULT_RING = "little Star"; private String mRing = DEFAULT_RING; private boolean isRing; private boolean isVibrating; public void goModel() { Log.d(TAG, "开启模式:"); } //共性方法的抽象,由子类去实现具体的情景模式名称 public abstract String getSceneModelName(); public abstract boolean isRing(); public abstract boolean isVibration(); //钩子函数,外界条件改变,影响了模板函数的执行 public void setRingMusic(String ringName) { this.mRing = ringName; }; private void playRing() { this.isRing = true; Log.d(TAG, "play: " + mRing); } private void vibrating() { this.isVibrating = true; Log.d(TAG, "vibrating"); } private void startRing() { if (isRing()) playRing(); } private void startVibrating() { if (isVibration()) vibrating(); } private void stopRing() { if (isRing) { Log.d(TAG, "停止响铃"); this.isRing = false; } } private void stopVibration() { if (isVibrating) { Log.d(TAG, "停止震动"); this.isVibrating = false; } } public final void called() { //为了防止恶意的操作,一般模板方法都加上final关键字,不允许被重写 Log.d(TAG, "called: 被呼叫"); new Thread() { @Override public void run() { startRing(); startVibrating(); try { Thread.sleep(60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } stopRing(); stopVibration(); } }.start(); } } public class RingSceneMode extends SceneMode{ //子类响铃模式,完成对应的配置,达到重定义父类called的动作 @Override public String getSceneModelName() { return "响铃模式"; } @Override public boolean isRing() { return true; } @Override public boolean isVibration() { return false; } } public class VibrationSceneMode extends SceneMode{ //子类震动模式,完成对应的配置,达到重定义父类called的动作 @Override public String getSceneModelName() { return "振动模式"; } @Override public boolean isRing() { return false; } @Override public boolean isVibration() { return true; } } 如下为场景类,创建不同的模式来完成对应的模式匹配,实现特定模式达到的效果 //1.设置RingMode mSceneMode = new RingSceneMode(); mSceneMode.setRingMusic("lemon tree"); Log.d(TAG, mSceneMode.getSceneModelName()); mSceneMode.called(); //2.设置VibrationMode mSceneMode = new VibrationSceneMode(); Log.d(TAG, mSceneMode.getSceneModelName()); mSceneMode.called(); //3.设置Ring + Vibration Mode mSceneMode = new RingAndVibrationSceneMode(); mSceneMode.setRingMusic("lemon tree"); Log.d(TAG, mSceneMode.getSceneModelName()); mSceneMode.called(); 模板方法模式的优点: 封装不变部分,扩展可变部分; 提取公共部分代码,便于维护; 行为由父类控制,子类实现。 模板方法模式的使用场景 多个子类有公有的方法,并且逻辑基本相同时。 重要复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则有各个子类实现。 重构时, 模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(外界条件改变,影响到模板方法的执行)约束其行为。 参考Github地址:https://github.com/tomato0/TempalatePatterns/tree/master CSDN:http://blog.csdn.net/wsq_tomato/article/details/79518729
Android Ore(Go edition) 简介: Android Go并不是一个独立的操作系统,它只是Android O的一种轻量级配置方案,专为1GB以下内存的机型设计, 在这种设置下,一些消耗大量资源的功能将被关闭,同时预装的应用也是轻量级的,不会消耗大量系统资源和数据流量。Google在会上斌没有说到相关Android Go上的功能的变化,但可以想象到,必定会有部分占用太多资源的非必要的一些功能拿掉,如果在没有功能缺失的情况下,入门级设备的性能就能得到很棒的优化,那么就不会出现Android Go, 而将会是一个非常nice的Android O。而具体的内容,需要等其发布之后才能去一探其究。 Go版本的优化的三个方向: 1. OS 改善了整个平台的内存使用情况,确保app能够在1GB以下内存的设备上高效的运行。据说,Go版本减少了大约一半的存储空间。 2. APP 增加了新的硬件功能常量,能够灵活区分出是为正常还是低内存设备设计的应用软件。 PackageManager.FEATURE_RAM_LOW = "android.hardware.ram.low" //RAM <= 1GB ActivityManager.isLowRamDevice() return true; PackageManager.FEATURE_RAM_NORMAL = "android.hardware.ram.normal" Googel重新设计适合Go版本的应用, 使之占用更少的内存,在低端机上有更好的表现,包括包括YouTube Go、谷歌地图Go、Gmail Go等, 同时, 谷歌还专门为Android Go开发了几个新应用, 比如Google Go和Files Go。Google Go是一款修改版的Google Lite应, 能够让用户更容易的找到流行信息、网站和应用程序,转换Gif动画和文字。而Files Go则是一款文件管理器, 可以让用户管理智能手机的存储空间。 3. Googel Play AndroidOreo Go平台的Play Store商店与普通的Google Play商店所有内容一样,但更适合低存储容量设备。 谷歌额外增加了一个特色应用板块,专门为Android Go设备推荐轻便、好用、适合的应用。通过 Googel Play的一些应用为开发者 提供优化经验和开发指南。 如何针对运行于Go版本的设备优化APP? 为了确保应用能够在Android Go版本上良好的运行,需要参考以下指导意见: 需要在manifest添加声明: <uses-feature android:name="android.hardware.ram.low" android:required="true"/> targetSdkVersion应该是Android最新的版本,Android Go设备只能运行在API 26及其以上的版本。 设备上的应用程序大小应该小于40MB。 应用程序内存使用的PPS(Proportional Set Size)不应该超过50MB, 游戏内存使用的PPS不应该超过150MB。 ——PSS:实际使用的物理内存,按比例分配共享库占用的内存。 应用程序的启动时间最小,应在5s之内。 有效的使用内存(看下一标题)。 补充:通过dumpsys命令可以查看到app的内存信息,如图,可以看到PPS值是:42168 pss1.png 如何有效的使用内存? 好的内存管理才能使APP运行得更稳定、性能更好。 减少低成本设备上的内存占用 动态调整内存的占用,确保不同RAM设备之间的兼容。 利用isLowRamDevice()和getMemoryClass()来确定运行时内存限制,通过这些信息,缩小内存的使用。 比如:在低内存设备上使用低分辨率的图片。 管理应用内存(具体内容可参考Googel开发者平台)。 避免长时间运行的进程 长时间运行的进程驻留在内存中,会降低设备的内存,大多数情况下,APP的运行过程应该是: 通过某个事件唤醒APP;处理数据;关闭。 可以使用 Firebase Cloud Messaging (FCM) 和 GcmNetworkManager来避免长时间运行的后台服务,减少设备的内存压力。 内存使用的检查 利用Android Studio提供的Memory Profiler tool,能够在设备运行时监控内存的使用情况。 可以帮助捕获无意的内存占用增长,使用该工具执行以下操作: 找出不希望的GC事件是否会导致性能问题。 识别无必要地获取或保持分配的对象类型。 确定代码中可能存在问题的地方。 http://blog.csdn.net/wsq_tomato/article/details/78934573