Android 主线程到底是什么、如何抛出ANR
作为 android 者对主线程的操作在开发中是非常频繁的,主线程是非常重要的线程,因为我们所有的UI界面都是通过主线程更新、绘制的。所以我们要足够了解他。从源码的角度看看为何ui必须在主线程更新、直接创建的handler为何就把线程给切换到了主线程呢?、主线程阻塞anr是如何抛出的?
目录
- Android 主线程是在哪里创建的?
- 在android开发中为什么子线程不能更新UI界面?
- 直接创建的Handler接收到的消息是运行在那个线程中的?
- android 主线程是如何捕获ANR异常的?
1.Android 主线程是在哪里创建的?
android的主线程是在 app 进程创建时候就创建了,即当系统 调用 fork()方法之后就会创建一个新的进程和进程中地一个线程称为主线程。
2.在android开发中为什么子线程不能更新UI界面?
代码主要在ViewRootImpl中,从构造方法开始
public ViewRootImpl(Context context, Display display) { // 保存当前创建 ViewRootImpl 对象的线程 mThread = Thread.currentThread();
对象创建完成之后在调用view root 遍历方法/ 更新ui时 都会去检测线程是否是创建ViewRootImpl对象的线程。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检测 checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
其实更新UI界面并不是非的在main线程中,子线程不能更新ui界面的原因是因为当我们进程fork()并启动时创建的地一个线程就是main线程,然后就调用了ActivityThread 中的 main()方法开始loop循环处理handler接受到的消息。application 创建完之后会等待 AMS 回调ApplicationThread 跨进程接口,当收到Launcher activity信号后application就会创建activity然后一直到ViewRootImpl中通知WMS创建窗口并且更新绘制各个子View控件,在ViewRootImpl的构造方法内已经保存了当前创建的 thread 对象,由于ViewRootImpl创建是在main线程中的,所以thread这个对象其实就是main线程。当我们再次调用更新View的方法时例如invalidate()/ requestLayout()等触发更新界面时都会调用checkThread()方法去验证当前的 thread 是不是ViewRootImpl构造方法中保存的那个线程,不是就会抛出异常。android 加检测线程的原因其实是为了防止多线程访问下触发线程安全问题,但是为了降低锁带来的开销和系统的复杂度,就强制限制必须是我们创建 ViewRootImpl 对象的线程。所以如果我们想在子线程中更新UI 其实两种方式
- 我们创建ViewRootImpl时在子线程中创建。
- 反射修改checkThread()方法的结果。
3.直接创建的Handler接收到的消息是运行在那个线程中的?
例如:android ActivityThread 中创建的 Handler没有指定当前的looper对象那他是怎么保证能通知到主线程的?对应的 HandlerThread 中创建的Handler 却指定了一个looper?
// 直接 new handler 调用无参构造方法 Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
直接 new handler 调用无参构造方法
public Handler(Callback callback, boolean async) { // 最终使用的是当前线程的 looper mLooper = Looper.myLooper(); }
最终 Looper.myLooper(); 会到ThreadLocal 中去获取当前线程里面的 Looper 对象,所以直接new 空参构造方法,当前线程切换到的是 Handler 对象被创建的线程中。
因为ActivityThread中先调用了Looper.prepare()这时候会为当前线程创建Looper和MessageQueue对象,再创建的Handler时候当前Handler就会去调用当前的Looper和MessageQueue对象。
HandlerThread 在当前线程中也是先调用了Looper.prepare(),但是Handler的创建是交给调用者调用方法触发的,这个方法可以在任何线程中调用,所以这时候在创建Handler对象时候就需要指定 传入 looper对象。也可以调整一下Handler的创建位置,放到run()方法内调用就不用传looper了,但是run()中创建对象不是好的编程方式。
4.android 主线程是如何捕获ANR异常的?
一个anr的产生主要涉及到一下场景:
- Service Timeout:服务在20s内未执行完成;
- BroadcastQueue Timeout:比如前台广播在10s内执行完成
- ContentProvider Timeout:内容提供者执行超时
- inputDispatching Timeout: 输入事件分发超时5s,包括按键分发事件的超时。
以上四种场景均会导致anr的产生,这里之分析 Service Timeout 的anr,其他场景类似。在service启动的时候会去检测anr,当我们startService() 之后,系统会调到 realStartServiceLocked() 方法
private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... //发送delay消息(SERVICE_TIMEOUT_MSG) bumpServiceExecutingLocked(r, execInFg, "create"); try { ... //最终执行服务的onCreate()方法 app.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); } catch (DeadObjectException e) { mAm.appDiedLocked(app); throw e; } finally { ... }}
bumpServiceExecutingLocked()中
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { ... scheduleServiceTimeoutLocked(r.app);}void scheduleServiceTimeoutLocked(ProcessRecord proc) { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } long now = SystemClock.uptimeMillis(); Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; //当超时后仍没有remove该SERVICE_TIMEOUT_MSG消息,则执行service Timeout流程 mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));}
最后进入当前进程的 ActivityThread 的 handleCreateService(CreateServiceData data)
private void handleCreateService(CreateServiceData data) { ... java.lang.ClassLoader cl = packageInfo.getClassLoader(); Service service = (Service) cl.loadClass(data.info.name).newInstance(); ... try { //创建ContextImpl对象 ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); //创建Application对象 Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); //调用服务onCreate()方法 service.onCreate(); //取消AMS.MainHandler的延时消息 ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (Exception e) { ... } }
Service 的启动创建流程和 activity 的启动创建流程非常相似,当创建完成之后调用service的onCreate()方法、然后调用AMS.MainHandler取消延时消息。如果启动service 和 调用service 的 onCreate() 非常耗时就会抛出anr异常。
更多相关文章
- 调试方法-Unity3D对各个target平台的模拟
- Android 的线程跟线程池
- Android读取工程内嵌资源文件的两种方法
- Android Studio:10分钟教会你做百度地图定位!并解决SDK22中方法报
- Android——Json和Gson分别是什么,以及Json 数据的解析方法
- Android 性能优化——通过线程提高性能
- android开发打印票据或文档的方法:android打印文档的类