源码角度讲解子线程创建Handler报错的原因
来源:互联网 发布:知凡生物 编辑:程序博客网 时间:2024/05/18 01:28
1. 前言
众所周知,在android中,非UI线程中是不能更新UI的,如果在子线程中做UI相关操作,可能会出现程序崩溃。一般的做法是,创建一个Message对象,Handler发送该message,然后在Handler的handleMessage()方法中做ui相关操作,这样就成功实现了子线程切换到主线程。
其实handler主要有两个功能:
1.刷新UI,(需要用主线程的looper)
2.不用刷新ui,只是处理消息
2.使用方法
1.刷新UI
1)主线程中初始化handler,实现子线程切换到主线程,进行刷新UI
handler1= new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder1",Toast.LENGTH_SHORT).show(); } System.out.println("handler1========="+Thread.currentThread().getName()); super.handleMessage(msg); } }; new Thread(new Runnable() { @Override public void run() { Message message = handler1.obtainMessage(); message.arg1 = 1; handler1.sendMessage(message); } }).start();
2)子线程中初始化handler,实现子线程切换到主线程,刷新UI
new Thread(new Runnable() { @Override public void run() { System.out.println("handler2 Thread========="+Thread.currentThread().getName()); handler2 = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } System.out.println("handler2========="+Thread.currentThread().getName()); super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); } }).start();
运行上面两个代码,打印出线程名称,可以看到,handlermessage确实是在main线程(及主线程)中,可以用来更新UI,运行结果如下所示:
09-23 17:06:48.395 15360-15404/com.example.test.myapplication I/System.out: handler2 Thread=========Thread-5286309-23 17:06:48.410 15360-15360/com.example.test.myapplication I/System.out: handler1=========main09-23 17:06:48.411 15360-15360/com.example.test.myapplication I/System.out: handler2=========main
结论:
1)如果在主线程调不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper,从而实现使用主线程Looper,实现刷新UI的功能;
2)如果在其他线程,也要满足刷新UI的话,要调用Handler handler = new Handler(Looper.getMainLooper()),这样虽然在子线程中初始化handler,但是仍然用的主线程的Looper对象,实现刷新UI的功能;
2.不用刷新ui,只是处理消息
1)如果在主线程中,跟上面一样,用不带参数的hanlder构造方法即可;(不再重复贴代码)
2)如果在其他线程,可以用上面的Looper.getMainLooper()(不再重复贴代码)。
3)如果在其他线程,不用上面的Looper.getMainLooper(),而是在子线程中新建Looper对象,代码如下所示:
new Thread(new Runnable() { @Override public void run() { System.out.println("handler2 Thread========="+Thread.currentThread().getName()); Looper.prepare(); handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } System.out.println("handler2========="+Thread.currentThread().getName()); super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); Looper.loop();; } }).start();
运行上面代码,打印出线程名称,可以看到handlermessage在子线程53027中,从而只能用来通知消息,因为不在主线程中,所以不能刷新UI,运行结果如下所示:
09-23 17:23:01.298 31434-31523/com.example.test.myapplication I/System.out: handler2 Thread=========Thread-5302709-23 17:23:01.305 31434-31523/com.example.test.myapplication I/System.out: handler2=========Thread-5302709-23 17:23:01.318 31434-31434/com.example.test.myapplication I/System.out: handler1=========main
可能有些人在子线程中新建Handler的时候,忘记调用Looper.prepare(),直接跟在主线程中新建Hanlder一样这样写,很多面试官也会问,这样写的话会有什么问题呢:
new Thread(new Runnable() { @Override public void run() { handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); } }).start();
如果是上面的代码,运行程序后,程序会crash,崩溃信息如下所示:
E/AndroidRuntime: FATAL EXCEPTION: Thread-50003 Process: com.example.cyf.myapplication, PID: 2223 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) at com.example.cyf.myapplication.MainActivity$3$1.<init>(MainActivity.java:0) at com.example.cyf.myapplication.MainActivity$3.run(MainActivity.java:56) at java.lang.Thread.run(Thread.java:818)
从错误的解释可以看出:没有调用Looper.prepare(),不能创建handler。所以很简单,我们在创建handler前面加上Looper.prepare(),再运行程序,果然没有错误了。
代码如下所示:
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); Looper.loop(); } }).start();
当然下面这种方式也可以,直接拿主线程的Looper对象。
new Thread(new Runnable() { @Override public void run() { System.out.println("handler2 Thread========="+Thread.currentThread().getName()); handler2 = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } System.out.println("handler2========="+Thread.currentThread().getName()); super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); } }).start();
直接拿主线程的Looper很简单,主线程默认自动创建一个Looper,在子线程中获取到,直接使用。
Looper.prepare();那种方式为什么不报错了呢,面试的时候,也经常提到,但是为什么这样写就可以呢?下面仔细分析一下。
3.源码讲解
1)刚刚异常报在创建handler 的时候,所以我们先看下handler的源码中的构造函数。
public Handler() { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null;}
可以看到第12行出现了刚刚上述的错误信息,很明显mLooper为空的时候,就会抛出如下异常。
Can't create handler inside thread that has not called Looper.prepare()
2)Looper对象什么时候为空,我们看到第10行有looper的获取方法mLooper = Looper.myLooper();可以看出,这个方法获取到的looper对象为空,为啥为空?我们看看Looper.myLooper()中的代码就明白了,如下所示:
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
代码非常少,很容易理解,就是从sThreadLocal对象中取出Looper。(sThreadLocal源码其实就是个泛型数组,源码不贴了,把他想成数组就好了。)sThreadLocal什么时候存在Looper对象呢,及什么时候会set一个Looper到该数组中呢,根据我们调用Looper.prepare()方法就不报错,可以初步判断,应该是Looper.prepare()方法中把looper对象放到sThreadLocal中,为了验证我们的猜想,我们来看下Looper.prepare()的源码:
3)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)); }
从上面的代码可以看出,sThreadLocal如果没有Looper,则新建Looper进去,如果存在,则抛出异常,而且从判空可以看出一个线程最多只能创建一个Looper对象。
4)所以一开始调用Looper.prepare()方法,其实相当于为线程新建了一个Looper放到sThreadLocal中,这样mLooper = Looper.myLooper();则可以从sThreadLocal中获取刚刚创建的Looper,不会导致程序崩溃。
4.其他:
可能会有人说,为什么我在主线程中初始化handler的时候,没有new Looper,为什么没有报异常,相信很多人会听到别人说,主线程默认给我们创建了Looper对象,没有错。
我们看下ActivityThread的源码中的main()方法
public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); AndroidKeyStoreProvider.install(); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); 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(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
我们可以看到调用23行出现了Looper.prepareMainLooper();从上面的分析来看,这个方法就是创建主线程的looper对象,我们来看Looper.prepareMainLooper();源码
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
从上面代码可以看到,会有prepare(false)方法,又回到创建Looper方法中了,及主线程中会默认为我们初始化一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。
5.结论:
1)主线程中可以直接创建Handler对象。
2)子线程中需要先调用Looper.prepare(),然后创建Handler对象。
6.相关代码
package com.example.cyf.myapplication;import android.app.Activity;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.Toast;public class MainActivity extends Activity { private Handler handler1; private Handler handler2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initHandler1(); initHandler2(); } /** * 初始化handler(主线程) */ private void initHandler1() { handler1= new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder1",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; new Thread(new Runnable() { @Override public void run() { Message message = handler1.obtainMessage(); message.arg1 = 1; handler1.sendMessage(message); } }).start(); } /** * 初始化handler(子线程) */ private void initHandler2() { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); Looper.loop(); } }).start(); }}
有错误之处欢迎指正,不断学习不断进步。
- 源码角度讲解子线程创建Handler报错的原因
- 子线程创建Handler
- 创建子线程中的Handler
- android子线程创建handler
- 子线程中创建handler
- Android 子线程创建handler
- 从源码的角度分析Handler机
- 子线程Handler.sendMessage 报错:Attempt to invoke virtual method 'boolean android.os.Handler.sendMessage
- Android子线程创建Handler方法
- 如何在子线程中创建Handler?
- 关于子线程创建handler和onNewIntent()
- 309_子线程创建Handler
- Android子线程创建Handler方法
- 子线程是否可以创建Handler
- 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()
- 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()
- 在子线程中new Handler报错--Can't create handler inside thread that has not called Looper.prepare()
- 源码角度讲解Android消息处理机制(Handler、Looper、MessageQueue与Message)
- html编码规范
- nginx、apache实现代理功能与直接访问node.js站点
- 喵播直播项目详解
- 什么叫工业4.0,这篇接地气的文章终于讲懂了
- 自己实现strcpy,strncpy,strncat,strcmp,strlen的功能
- 源码角度讲解子线程创建Handler报错的原因
- Codeforces-368B-Sereja and Suffixes
- javascript编码规范
- rails 路由
- EDA软件_Protel99se生成gerber文件
- LIGHT OJ 1179 - Josephus Problem【约瑟夫(模板)】
- cancel事件
- 嵌入式培训第二阶段 第一星期 no.1 标准 I / O
- How to install Oh My Zsh on Ubuntu 14