Android中主线程为什么不会因为Looper.loop()里的死循环卡死?

先说结论:主线程在没有消息的时候是阻塞的。主线程没有卡死,简单来说是因为有其他线程通过handler发送消息唤醒主线程。阻塞并不是卡死,阻塞可以简单理解为让出CPU,进入休眠状态,卡死就是ANR了。

Android中的主线程:就是Zygote进程通过fock自身创建的应用程序进程。

我们下面先看一个Android Application启动的简单过程。详细过程可以参考从点击桌面应用图标到MainActivity的onResume过程分析

先来一张图

Android主线程不会卡死的原因.jpg

步骤如下

  1. App进程使用ActivityManagerService的代理对象ActivityManagerProxy通过Binder的方式进行进程间通信,通知system_server进程的ActivityManagerService要启动进程。

  2. system_server进程的ActivityManagerService收到通知后,使用ApplicationThread的代理对象ApplicationThreadProxy通过Binder的方式进行进程间通信,通知App进程的ApplicationThread对象启动Application。

  3. App进程的ApplicationThread对象收到通知后,通过handler向ActivityThread发送消息来启动Application。

应用程序的入口是ActivityThread类的main方法,该方法运行在主线程中。注意
ActivityThread并不是一个Thread。

public final class ActivityThread extends ClientTransactionHandler {

    //注释3处,初始化mAppThread对象
    final ApplicationThread mAppThread = new ApplicationThread();
    //注释4处,获取当前线程的Looper对象
    final Looper mLooper = Looper.myLooper();
    //注释5处,构建一个 H对象mH
    final H mH = new H();

    public static void main(String[] args) {
         //... 
        //注释1处,准备主线程的Looper
        Looper.prepareMainLooper();

        //...
        //注释2处,初始化 ActivityThread对象
        ActivityThread thread = new ActivityThread();
        //注释6处,连接当前进程和Application
        thread.attach(false, startSeq);
        //主线程的handler
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //
        //主线程的消息队列开始循环
        Looper.loop();
    }

}

在注释1处,先准备主线程的Looper。然后在注释2处,初始化 ActivityThread对象。
在注释2处ActivityThread对象对象初始化完毕以后,注释3,4,5,处的对象都被赋值了。

在注释3处,构建了一个ApplicationThread类的实例mAppThread。ApplicationThread类是ActivityThread的内部类。

private class ApplicationThread extends IApplicationThread.Stub {
    //...
}

ApplicationThread实现了IApplicationThread.Stub,可以用于进程间通过Binder进行通信。

这里我有个问题,ApplicationThread是运行在应用的主线程中的吗?

答:ApplicationThread是运行在应用的Binder线程池中的。

注释4处,获取当前线程的Looper对象。

注释5处,构建一个 H对象mH。H是ActivityThread的内部类,继承了Handler类。用来发送消息。

class H extends Handler {
    
}

我们接下来看一下注释6处的ActivityThread的attach方法,在这个方法中会启动Application。

private void attach(boolean system, long startSeq) {
  //...
  if (!system) {
        //...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        //获取ActivityManagerService对象
        final IActivityManager mgr = ActivityManager.getService();
        try {
            //注释1处,调用ActivityManagerService的attachApplication方法
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
           //...
        } 
    //...
}

在上面的方法的注释1处,可以看出来,主线程是通过ApplicationThread和ActivityManagerService进行通信的。(ActivityManagerService运行在SystemServer进程)

ActivityManagerService的attachApplication方法

@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
    synchronized (this) {
        //...
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);
        //...
    }
}

ActivityManagerService的attachApplicationLocked方法

private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
    //...
    //注释1处,调用ApplicationThread的bindApplication方法
    thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.persistent,
                        new Configuration(getGlobalConfiguration()), app.compat,
                        getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, isAutofillCompatEnabled);

    //...
    // 要启动的Activity等待在这个进程中运行
    if (normalMode) {
        try {
            //注释2处,调用ActivityStackSupervisor的attachApplicationLocked方法
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } 
    }
    //...
   return true;
}

在注释1处,调用了ApplicationThread的bindApplication方法

public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, boolean autofillCompatibilityEnabled) {

      //注释1处,调用ActivityThread的sendMessage方法
      sendMessage(H.BIND_APPLICATION, data);
}

ActivityThread的sendMessage方法

void sendMessage(int what, Object obj) {
    sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    Message msg = Message.obtain();
    msg.what = what;
    msg.obj = obj;
    msg.arg1 = arg1;
    msg.arg2 = arg2;
    if (async) {
        msg.setAsynchronous(true);
    }
    mH.sendMessage(msg);
}

在ApplicationThread的bindApplication方法的注释1处,调用ActivityThread的sendMessage方法发送消息,注意此时我们是在ApplicationThread所在的线程(并不是主线程)发送消息的。但是因为mH是在主线程初始化的,所以mH关联的是主线程的消息队列,所以消息最终会添加到主线程的消息队列中。

我们看一下H的handleMessage方法,此方法是在主线程中调用的。

case BIND_APPLICATION:
    AppBindData data = (AppBindData)msg.obj;
    //1. 调用handleBindApplication方法
    handleBindApplication(data);
break;

ActivityThread的handleBindApplication方法

private void handleBindApplication(AppBindData data) {
  //...
  //设置应用的名字
  Process.setArgV0(data.processName);
  //创建Instrumentation对象
  mInstrumentation = new Instrumentation();
  Application app;
  //注释1处
  app = data.info.makeApplication(data.restrictedBackupMode, null);
  mInitialApplication = app;
 //...
 //注释2处启动app
 mInstrumentation.callApplicationOnCreate(app);
 //...
}

注释1处,data.info是一个LoadedApk类型的对象,我们看一下LoadedApk的makeApplication方法。

 public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;
        //获取appClass
        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            java.lang.ClassLoader cl = getClassLoader();
            //在这里创建了应用的context
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            //注释1处,创建Application对象
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } 
        mActivityThread.mAllApplications.add(app);
        //把app赋值给mApplication
        mApplication = app;
        //我们在这里传入的instrumentation是null
        if (instrumentation != null) {
            try {
                instrumentation.callApplicationOnCreate(app);
            } 
        }

        //...
        return app;
    }

在makeApplication方法中的注释1处,调用了Instrumentation的newApplication方法。

public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        //创建了Application对象
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        //注释1处,调用Application的attach方法。之后我们就可以获取应用的context了。
        app.attach(context);
        return app;
    }

在handleBindApplication方法的注释2处调用了Instrumentation的callApplicationOnCreate方法

public void callApplicationOnCreate(Application app) {
        //到这里app已经启动了
        app.onCreate();
    }

到这里Application的onCreate方法已经被调用了,应用已经启动了。

通过上面的分析,我们可以知道我们的应用进程和其他进程通信是通过ApplicationThread。应用进程之间通信是通过Handler,Handler在其他线程将消息发送到主线程,唤醒了主线程。

总结一下:主线程在没有消息的时候是阻塞的。主线程没有卡死,简单来说是因为ApplicationThread通过handler发送消息唤醒主线程。ApplicationThread是运行在应用的Binder线程池中的。阻塞并不是卡死,阻塞可以简单理解为让出CPU,进入休眠状态,卡死就是ANR了。

参考链接

  1. Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
  2. Android中为什么主线程不会因为Looper.loop()里的死循环阻塞?
  3. 从点击桌面应用图标到MainActivity的onResume过程分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342