内容字号:默认大号超大号

段落设置:段首缩进取消段首缩进

字体设置:切换到微软雅黑切换到宋体

扒一扒站在Activity背后的那个巨人

2018-02-13 15:45 出处:清屏网 人气: 评论(0

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消息模型机制。

分享给小伙伴们:
本文标签: Activity

相关文章

发表评论愿您的每句评论,都能给大家的生活添色彩,带来共鸣,带来思索,带来快乐。

CopyRight © 2015-2016 QingPingShan.com , All Rights Reserved.

清屏网 版权所有 豫ICP备15026204号