聊一聊站在 Activity 背后的那位巨人-[Android_YangKe]

Android是什么?一种便携式操作系统,基于Java编程语言研发。Java程序的入口是什么?main函数。既然我们知道Java程序的入口是main,Android基于Java研发,那么我们从这里入手肯定没错。

首先,我们打一波怪。

//ActivityThread.java
public static void main(String[] args) {
    //...代码省略...
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();
}

由上面代码片段可以看出ActivityThread是整个Android应用的入口。那么Android应用是如何启动一个页面的呢?这里就会牵扯到Android应用的消息机制:“Handle、Looper、MessageQueue、Message”。这些Api是Android基础、我希望你是了解的,这样理解此篇文章才会得心应手。像如何启动一个Activity、主线程子线程如何切换等等答案都在里面,下面我们扒一扒源码。

//Looper.java
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
         if (sMainLooper != null) {
             throw new IllegalStateException("The main Looper has already been prepared.");
         }
         sMainLooper = myLooper();
   }
}

private static void prepare(boolean quitAllowed) {
     ...代码省略...
     sThreadLocal.set(new Looper(quitAllowed));
}

从函数命名上我们大概就可以看出此函数主要是做Looper使用前的准备工作。内部调用了preparesThreadLocal.set(looper)myLooper()... Google工程师函数命名还是蛮规范的吗,顾名思义。那么Looper是什么?具体内部做了哪些工作 ?一波小怪兽即将来袭~

//Looper.java
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

通过构造函数我们可以看出,Looper内部主要对MessageQueue进行了初始化,也就是我们平时所说的消息队列,用于存放Handler发送的消息。那么Looper到底是什么呢 ? 我们继续打怪。

//Looper.java
public static void loop() {
    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;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        //msg = Message; target =  Handler;
        msg.target.dispatchMessage(msg);
    }
}

通过源码我们可以发现,其实就是从当前线程获取一个Looper对象myLooper(),然后获取消息队列,最后就是不断轮询消息队列,将获取到的Message交给Handler进行处理msg.target.dispatchMessage(msg)。这整段代码就好比汽车的发动机,如果此段代码停止了运行,我们的整个Android应用都会退出。也就是我们日常所提到的Activity、Service、View等等都将不复存在。

Looper是什么?
主线程与子线程之间通信的一个辅助对象,它可以不断的从MessageQueue轮询Message消息,随后将Message与Handler的dispatchMessage函数绑定,最终调用handleMessage函数。

Message是什么?
主线程与子线程通信的消息标识、承载。如果主线程是买家,子线程是卖家,那么Message就是购物车。当买家往购物车里添加了int卖家发货后买家届时收货就能收到int

ok,扯远了,回头我们下看thread.getHandler()

一波小怪物又来了,真是烦...

//ActivityThread.java
final H mH = new H();
final Handler getHandler() { return mH;}
private class H extends Handler {}

哈哈哈,这些小怪物有点撮,看我团灭它们!动次打次、动次打次... 不对,好像来了个Boss!

//ActivityThread.java
private class H extends Handler {
    public static final int LAUNCH_ACTIVITY         = 100;
    public static final int PAUSE_ACTIVITY          = 101;
    public static final int PAUSE_ACTIVITY_FINISHING= 102;
    ... 省略n行类似常量...
    public static final int RECEIVER                = 113;
    public static final int STOP_SERVICE            = 116;
    public static final int INSTALL_PROVIDER        = 145;
}

Android中View无法在子线程中更新UI,相信大多数开发者刚开始做Android的时候都自定义过class继承Handler重写handleMessage(Message msg)来处理主子线程通信问题,实际上很多框架底层都是基于Handler机制实现的。那么问题来了,H在这里起什么作用呢?

小刚:不行啊,这个Boss有点难搞,搞不定。
小明:放开那个Boss让我来!哈哈哈,你大概率刚刚去了趟厕所,这Boss的弱点显而易见吗。LAUNCH_ACTIVITY、STOP_SERVICE顾名思义就是启动Activity、停止Service,很好搞吗。

//ActivityThread.java
public void handleMessage(Message msg) {
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
        } break;
        case RELAUNCH_ACTIVITY: { }
        case PAUSE_ACTIVITY: { }
        ...代码省略...
    }
}

小刚看到了没?!又是熟悉的感觉、熟悉的味道handleMessage(Message msg)。让我来扒开它神秘的面目,打它个落花流水、四脚朝天。动次打次,动次打次~

(ActivityClientRecord) msg.obj顾名思义从Message对象中获取ActivityClientRecord的对象赋值给r,getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo)返回LoadedApk的对象赋值给r.packageInfo。LoadedAPk是什么呢 ?大概翻了下源码指的是当前正在加载的apk也就是我们的Android应用。此对象不是本文重点,这里暂且跳过。美酒虽好,但不要贪杯哦。继续打怪。

//ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    getResources().getConfiguration()来获取此对象
    handleConfigurationChanged(null, null);
    WindowManagerGlobal.initialize();
    Activity a = performLaunchActivity(r, customIntent);
    ...代码省略...
}

看名字相信很容易看出来,这就是今天的大Boss,接下来我们补充弹药,花点时间用心怼它。

handleConfigurationChanged

第一波配角即将来袭。

final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
    mCurDefaultDisplayDpi = config.densityDpi;
    updateDefaultDensity();
    mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
    configDiff = mConfiguration.updateFrom(config);
    config = applyCompatConfiguration(mCurDefaultDisplayDpi);
     ...代码省略...
    final Theme systemTheme = getSystemContext().getTheme();
    if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
         systemTheme.rebase();
    }
}

此函数主要做了一些配置工作,例:屏幕密度、Bitmap密度、res中资源如何加载、以及Theme样式等。

第二波配角即将来袭~

WindowManagerGlobal.initialize

首先我们看一张图,此图源自百度图片。没找到出处,对不起作者,老铁,请收下我的666~~

Activity模型.jpeg

Google工程师的命名其实蛮讲究,通过名字我们大概可以知道其函数的主要作用。这部分源码我在7.1.1中没有找到,不过看图已经可以很清晰的帮助我们了解Activity结构了。这里不再进行阐述,不要难受!我已经为爱学习的你们准备了小礼物 走你

performLaunchActivity

哇,终于见到终极大Boss了? 怎么办?
Android_YangKe就是我,我就是Android_YangKe,一个团灭广大70、80、90、00后的超级英雄!

//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ComponentName component = r.intent.getComponent();
    if (component == null) {
        component = r.intent.resolveActivity(mInitialApplication.getPackageManager());
        r.intent.setComponent(component);
    }

    if (r.activityInfo.targetActivity != null) {
        component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity);
    }
    ...代码省略...

    Activity activity = null;
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    // mInstrumentation = Instrumentation
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    r.intent.prepareToEnterProcess();
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    //省略代码... 此处主要是初始化window、theme相关配置
    Window window = null;
    if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
        window = r.mPendingRemoveWindow;
        r.mPendingRemoveWindow = null;
        r.mPendingRemoveWindowManager = null;
    }
     activity.attach(appContext, this, getInstrumentation(), r.token,
     r.ident, app, r.intent, r.activityInfo, title, r.parent,
     r.embeddedID, r.lastNonConfigurationInstances, config,
     r.referrer, r.voiceInteractor, window);
     mActivities.put(r.token, r);
   }
  return activity
}

Android_YangKe:菠萝菠萝蜜... 菠萝菠萝蜜... 菠萝菠萝蜜!
大Boss:额、额... 被ko。

这个小怪兽的确不好搞,像ComponentName、ActivityClientRecord是什么玩意?头次见呢。

ComponentName:组件标识。例:Activity、Service、BroadcastReceiver、ContentProvider。这里指Activity。

ActivityClientRecord:Acitvity记录仪,用于记录Activity的配置信息。例:Activity的状态(暂停、停止)屏幕大小、屏幕方向、输入法模式等。

r.packageInfo.makeApplication(false, mInstrumentation)顾名思义就是构建我们的Application,内部同时回调了Application的onCreate函数。限于篇幅这里不再不贴源码。有兴趣的小伙伴可以去LoadedApk.java中查看。

有人问了,说了这么多我还是没有看到Activity的身影啊?mmb。其实我们通过楼上那张Activity模型图,可以看出Activity内部有一个Window。而Window内部有DecoView、最上层是我们平时所set的View。那么问题来了,既然Activity是一个壳,我们再来看看系统是如何封装的。

//Activity.java
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id,  NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) {
 mWindow = new PhoneWindow(this, window);
 mWindow.setWindowControllerCallback(this);
 mWindow.setCallback(this);
 mWindow.setOnWindowDismissedCallback(this);
 mWindow.getLayoutInflater().setPrivateFactory(this);
 if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
     mWindow.setSoftInputMode(info.softInputMode);
 }
 //...代码省略...
}

不出所料,系统在此函数中为Activity构建了一个Window,然后设置了Activity回调,输入法模式等。我们再来看一下这个回调里都有什么。

public interface Callback {
    public boolean dispatchKeyEvent(KeyEvent event);
    public boolean dispatchKeyShortcutEvent(KeyEvent event);
    public boolean dispatchTouchEvent(MotionEvent event);
    public boolean dispatchGenericMotionEvent(MotionEvent event);
    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
    //...代码省略...
    public View onCreatePanelView(int featureId);
    public boolean onCreatePanelMenu(int featureId, Menu menu);
}

相信看到这里你就比较熟悉,其实平时我们重写的View触摸事件、点击事件、onCreate等函数都直接或间接源于此处。就是Activity调用自己的函数、调用Window的函数,最后调用我们重写的函数。

大Boss即将倒下,速度补刀、补枪。

//当我们简单的启动一个Activity,系统在背后为我们所做的工作。
//函数调用链
->startActivity(Intent intent)
->startActivity(intent, null)
->startActivityForResult(intent, -1)
...代码省略...
->startActivityForResult(intent, requestCode, null)
->startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options)
->mMainThread.sendActivityResult( mToken, child.mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData())
->mAppThread.scheduleSendResult(token, list)
->sendMessage(H.SEND_RESULT, res)

当我们调用startActivity时其实就是将一条Message放入MessageQueue,当Looper轮询到Message时便通过消息模型将Message交给Handler处理。

思路整理:

  1. 系统调用Looper.prepareMainLooper()、Looper.loop初始化Android的整套消息模型
  2. Looper.loope死循环轮询MessageQueue,无消息进行阻塞,有消息则处理

如何启动Activity?
初始化消息模型->初始化Window->初始化事件回调-> ... ... ->初始化DecoView->初始化事件回调-> ... ... ->回调我们的生命周期函数。

Android中为什么需要这套消息模型?
ActivityThread是Android应用的起始点。main函数运行在此类中,Android系统为了应用性能发挥极致,我们所的耗时任务一般在子线程中操作,但View操作在子线程中更新不安全,为此引出了Handler消息模型机制。

团灭!

喜欢有帮助的话: 双击、评论、转发,动一动你的小手让更多的人知道!关注 Android_YangKe

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