超星雅儿跳课Xposed模块开发

目的实现:超星雅儿跳课Xposed模块开发

前言

上大学的时候大多数的本科或者专科学生会在 超星XX选一门课程作为通修课,然后下载app看视频。最恶心的事情是什么?不能拖动进度条和快进 除非你看完了再看第二边。 而我们利用HOOK技术实现快速看完视频。。。

以下内容仅供学习,禁止商业用途。。。
适用超星版本:3.0.2(现在最新的了吧)
以下教程因为涉及大多高校正常教学制度。所以教程加密并且不公开。核心教程和源码地址(已经加密):

我的思路一: 先不管 三七二十一 反编译app 很可惜 加固了。。。脱壳拖了几天最后把自己裤子了睡觉

思路二:抓取数据包 然后分析访问接口的规则 然后自己把自己本地架设成远程访问服务器。

思路三:HOOK

思路一走不通,对于思路二我在想 核心接口都用了某个加密规则加密了吧?因为高考数学没到140所以放弃吧。我最后考虑最后一个。

提示:我原本思路二是解析加密规则麻烦,返现app某个接口没有加密,后来英语四级没时间咯,所以只实现HOOK。。
有人问我 这一步有人问我怎么做。这里提供一个小思路 ,我们电脑访问www.baidu.com ,先会到电脑C:\Windows\System32\drivers\etc\hosts 寻找域名和端口的射影,如果不存在 会像网络的DNS(域名系统)寻找。假设app 访问 www.XXXX.com 你改下射影文件不就好了?(别问我这不是windos下的修改方法,稍微变通下你懂的)然后你反回一段完成此视频的信息,那么你就可以拖动进度条了。

HOOK初探:
这里是基于XPosed编写的。
我的HOOK思路之一:首先查看视频播放界面。DDMS有dump view工具。
然后看到如下界面:
这里写图片描述

看到一个SeekBar,我就想着开发者是不是给SeekBar添加一个监听,判断是不是用户手动拖动的(监听回调有一个参数判断是不是手动滑动的进度条)。
1 如果是用户手动拖动的那么判断是否看过视频,如果没看过,禁止拖动
2 如果不是那么不管

结论:失败。。。。

tip:布局中还有个Seekbar那时候我获取错了,弄了半天

直接反射修改SeekBar 进度。

结论:失败

tip:得到思路 作者应该每次拖动的时候判断 你以上看到哪了,或者是否看完了。

所以。。。。。。 以下思路加密提供

思路和源码(已加密)

Xposed模块开发教程

(译)Xposed模块开发教程

原文地址。这是开发者所写的,可以说是官方开发指南。文章讲述了Xposed的原理,以及怎么开发Xposed框架的模块。头一次翻译技术文档,有错误的话请多包涵。

好了,你想学习怎么为Xposed开发新的模块么?那么读读这篇教程(或者我们可以称他为”泛读短文”)学着怎么去做。这不仅包括“创建这个文件然后插入…”这类的技巧,也包括这些技巧背后的思想。这些思想正是创造价值的步骤以及你真正需要了解你做了什么和为什么这么做的原因。如果你觉得本文“太长,不想读”,那么你可以只看最后的源代码和阅读“使工程成为Xposed模块“部分。但是如果你读了整篇文章你就会有更好的理解。你之后会节省出来阅读这个的时间,因为你不必凭自己弄清楚每件事。

修改主题


你将重新创建在github上可以找到的红色钟表的的例子。它包括将状态栏的钟表变为红色并且加入一个笑脸的功能。我选择这个例子是因为它非常小,而且容易看见所做的修改。并且,它也使用了框架所提供的一些基本方法。

Xposed如何工作


在你开始做出自己的修改之前,你应当大致了解Xposed如何工作(如果觉得这部分无聊可以跳过)。以下就是原理:

有一个叫做”Zygote”的进程,它是android运行环境的核心。每个应用都从一份它的拷贝(“fork”)产生。这个进程在手机启动时由一个叫 /init.rc 的脚本启动。这个进程的启动在 /system/bin/app_process 加载所需要的类和调用初始化方法后完成。

这里就是Xposed发挥用处的地方了。当你安装完框架后,一个扩展过的app_process就会被复制到 /system/bin 下。这个扩展过的启动进程会将一个额外的jar包添加到环境变量,并在特定场合调用里面的方法。比如:当虚拟机创建完成后和Zygote的main方法被调用前。并且在那个方法当中,我们已经是Zygote的一部分,而且能够在它的上下文context中活动。

jar包的位置是 /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar 它的源代码可以在这里找到。查看XposedBridge的类,你能找到main方法。这就是我上文中所写过的,它在每个进程的最开始部分被调用。一些初始化的工作在那里完成,并且我们的模块在那里加载(之后我再讲模块的加载)。

方法的hook/替换


真正使Xpoesed有威力的就是hook方法调用。当你反编译并修改APK时,你能够在任何你想的地方直接修改/替换指令。然而,你事后需要重新编译/给APK签名,并且只能发布整个安装包。使用Xposed能让你放置的hook,你并不能修改程序内部的方法代码(清楚地定义你想要在何处做什么样的修改是不可能的)。然而,你可以在方法调用的前后注入你的代码。这也是java中能够被清楚寻址的最小单位。

XposedBridge 有一个私有的 native 方法叫做 hookMethodNative。这个方法也在扩展后的 app_process 中被实现了。它会将方法类型转为“native”,并把方法的实现与本地的通用方法相连。这意味着,每当被hook的方法调用后,调用者不知道实际调用的是通用的方法。在这个方法中,位于 XposedBridge 的 handleHookedMethod 方法会被调用,并向方法调用传递参数、this指针以及其他东西。之后这个方法负责唤起之前方法调用注册过的回调。上述这些行为能够改变调用的参数、实例/静态变量、唤起其他方法、处理调用结果。。。或者跳过这些东西。它的弹性非常大。

好了,理论讲够了。我们现在创建一个模块吧!

创建工程


一个模块就是一个普通的app,只不过多了一些特殊的文件和元数据。所以在我们创建新的android工程以前,我假设你已经做过这个了。如果没有,官方文档讲的很详细。对于SDK,我选择了4.0.3(API15)。我建议你也使用这个,并且不要立刻开始。你不需要创建Activity,因为我们的修改不需要任何用户界面。回答过了这个问题后,你应该有一个空白的工程项目。

使工程成为Xposed模块


现在我们把工程变成Xposed能加载的东西。我们需要以下几个步骤。

AndroidManifest.xml

Xposed Installer的模块列表搜寻所有有一种特殊元数据标记的应用程序。你可以到 AndroidManifest.xml => Application => Application Nodes (在底部) => Add => Meta Data 下面去创建这个标记。标记名称应该是 xposedmodule ,值应该是 true。给resource留空。重复以上过程创建 xposedminversion (见下文) 和 xposeddescription (你创建的模块的简单描述)。XML文件现在就是这个样子:

<?xml version="1.0" encoding="utf-8"?>
 <manifest  xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.robv.android.xposed.mods.tutorial"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="Easy example which makes the status bar clock red and adds a smiley" />
        <meta-data
            android:name="xposedminversion"
            android:value="30" />
    </application>
</manifest>

XposedBridgeApi.jar

接下来,让程序能够找到 XposedBridge 的API。你可以从
这里下载 XposedBridgeApi-<version>.jar 的最新版。把它复制到叫做lib的子文件夹下。右键单击选择Build Path => Add to Build Path。文件名当中的<version>是你在manifest文件的xposedminversion标签所插入的版本。

保证API类没有被包含(但仅仅是参考)在你编译过的APK里,否则你会得到一个IllegalAccessError错误。libs(含有s)文件夹是eclipse自动生成的,不要把API文件放在那里。

模块的实现

现在你可以给你的模块创建一个类了。我的类叫做”Tutorial”,位于de.robv.android.xposed.mods.tutorial这个包中。

package de.robv.android.xposed.mods.tutorial;

public class Tutorial {

}

第一步,我们仅仅生成一些日志表明模块已经加载。一个模块可以有多个入口点。你选择哪个取决于你想修改什么。你可以在安卓系统启动时、在一个app将要启动时、在一个app的资源文件初始化时或其他时候,调用一个函数。

在这个教程靠后面的一部分,你将了解到在一个特定的app中需要做出的修改。那么先让我们了解一下 “让我知道什么时候加载一个新app” 这个入口点。所有入口点都被标记为IXposedMod的子接口。这种情况下,你需要实现 IXposedHookLoadPackage 这个接口。其实它只有一个仅有一个参数的方法。这个方法向被实现的模块提供更多关于运行环境上下文的信息。在我们的例子中,我们用log输出加载的app的名称。

package de.robv.android.xposed.mods.tutorial;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class Tutorial implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("Loaded app: " + lpparam.packageName);
    }
}

这个log方法向标准logcat以及 /data/data/de.robv.android.xposed.installer/log/debug.log(通过Xposed Installer可以轻易访问到)输出信息(tag Xposed)。

assets/xposed_init

现在唯一遗漏的就是提示XposedBridge哪些类包含了入口点。这项工作通过一个叫 xposed_init 的文件完成。在assets文件夹下创建一个新的名叫xposed_init的text文件。在该文件中每行包含一个类的全名。在这个例子中,它是 de.robv.android.xposed.mods.tutorial.Tutorial。

试试看


保存你的文件。以Android Application的方式运行你的程序。因为这是你第一次安装它,在使用前你需要先启用。打开Xposed Installer这个app并确保你安装了xposed框架。之后切换到Modules标签。你应该能在那里找到你的app。在选择框内打钩使得它可用。然后重启。你当然什么变化也看不到,但如果检查log记录,以应该会看见以下的东西:

Loading Xposed (for Zygote)...
Loading modules from   /data/app/de.robv.android.xposed.mods.tutorial-1.apk
Loading class de.robv.android.xposed.mods.tutorial.Tutorial
Loaded app: com.android.systemui
Loaded app: com.android.settings
... (many more apps follow)

瞧!它起作用了。现在你拥有了一个Xposed模块。它能够变得比写一些log更加有用…

探索你的目标并寻找修改它的方式


好了,下面要开始讲的部分也许会非常不同,这取决于你想做什么。如果你之前修改过apk,也许你会知道在这里应当如何思考。总的来说,你需要了解目标的一些实现细节。在本教程中,目标选定为状态栏的时钟。这有助于了解到状态栏以及其他一些东西都是系统UI的一部分。现在让我们在那里开始我们的探索。

可能性1:反汇编。这会告诉你它实际的实现,但是会很难阅读和理解,因为你得到的都是smali格式的东西。可能性2:获得AOSP源代码。比如这里这里。ROM不同代码也很不一样,但在本例中他们的实现是相似的甚至是相同的。我会先看AOSP,然后看看这么做够不够。如果我需要细节,我会看看实际的反汇编的代码。

你可以找找名称中有“clock”的类。其他需要找的是用到的资源和布局。如果你下载官方的AOSP代码,你可以从 frameworks/base/packages/SystemUI 开始找。你会找到好几处“clock”出现的地方。找到有好几种方式实现修改是正常而且真实的。时刻记住你“只能” hook方法。所以你必须另找其他能够插入代码实现功能的地方,要么在方法的前面或是后面,或者是替换掉方法。你应当hook一个尽可能明确的方法,而不是一个被调用成千上万次的用于解决性能问题和非计划中的副作用的方法。

在本例当中,你或许会发现布局 res/layout/status_bar.xml 包含一个指向带有类com.android.systemui.statusbar.policy.Clock的自定义view。现在你脑子里也许会有好多点子。文字的颜色是通过textAppearance属性定义的,所以最干净的更改它的方法就是修改外观的定义。然而,用Xposed框架改变外观属性几乎是不可能的(这需要深入本地代码)。替换状态栏的布局也许是可能的,但对于你试图做出的小小修改来说是杀鸡用牛刀。取而代之的是,看看这个类。有一个叫updateLock的方法,似乎每分钟都调用一次用于更新时间。

final void updateClock() {
    mCalendar.setTimeInMillis(System.currentTimeMillis());
    setText(getSmallTime());
}

这个方法用于修改来说是很好的,因为这是一个足够具体的看似唯一能够修改时钟文字的方法。如果我们在这个方法的每次调用之后都加些修改时钟文字和颜色的东西,应该就能起作用。那么,我们开始做吧。

对于单独修改字体颜色部分,有一种更好的办法。参见“替换资源”中“修改布局”的例子。

使用反射寻找并hook方法


现在我们已经知道了哪些东西?我们在com.android.systemui.statusbar.policy.Clock有一个叫做updateClock的希望干涉的方法。我们在系统UI资源中找到了这个类,所以它仅仅在系统UI进程当中有效。其它一些类属于框架,而且在任何地方都有效。如果我们在 handleLoadPackage 中试图直接获取任何这个类的信息和引用,就会失败。因为处于错误的进程中。所以让我们实现一种仅在某个特定包即将加载时执行特定代码的情况:

public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
    if (!lpparam.packageName.equals("com.android.systemui"))
        return;

    XposedBridge.log("we are in SystemUI!");
}

使用参数,我们能轻松检查是否在正确的包中。一旦我们核实了这一点,我们就通过ClassLoader取得那个包中的也被这个变量引用的类。现在我们可以寻找com.android.systemui.statusbar.policy.Clock这个类以及它的updateClock方法,然后告诉XposedBridge去hook这个方法:

package de.robv.android.xposed.mods.tutorial;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class Tutorial implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals("com.android.systemui"))
            return;

        findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called before the clock was updated by the original method
            }
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called after the clock was updated by the original method
            }
    });
    }
}

findAndHookMethod是一个Helper函数。注意静态导入,如果你像连接中说的一样做了,那就会被自动加入。这个方法使用系统UI包的ClassLoader寻找 Clock 这个类,然后寻找其中的updateClock方法。如果这个方法中有任何参数,你必须事后列出这些参数的类型。有许多方法做这件事,但因为我们的方法没有参数,所以就先跳过这个步骤。至于最后一点,你需要提供一个 XC_MethodHook 类的实现。对于更小的更改,你可以使用匿名类。如果你的代码很多,最好创建普通的类并且只在这里创建实例。helper之后会如之前所说的,做出所有hook这个方法的必要工作。

XC_MethodHook中有两个你可以重写的方法。你可以两个都重写,或者都不重写。但后者的做法显然是没有道理的。这些方法就是beforeHookedMethod 和 afterHookedMethod。不难猜出他们在原有方法之前/后执行。你可以使用“before”方法在方法调用前估计/操纵参数(通过param.args)。甚至阻止调用原来的方法(发送你自己的结果)。“after”方法可以用来做一些基于原来方法的结果的事。你也可以在这个地方操纵结果。当然,你可以在方法调用的前/后添加你自己要执行的代码。

如果你要彻底替换一个方法,去看看子类XC_MethodReplacement。在那里,你只需要重写replaceHookedMethod。

XposedBridge为每个hook过的方法维护一个注册过的回调的表。拥有最高优先级(在hookMethod中被定义)的将被优先调用。原有的方法总是具有最低的优先级。所以如果你hook了一个拥有回调A(高优先级)和回调B(默认优先级)的方法,那么不管被hook的方法是如何被调用的,执行顺序总是这样的:A.before -> B.before -> original method -> B.after -> A.after。所以A可以影响B看到的参数,即把它们传递下去以前大幅度地修改它们。原有的方法的结果会先被B处理,但是A拥有原先调用者最终将得到什么样结果的决定权。

最终步骤:在方法调用之前/后执行你自己的代码

好了,你已经在正确的上下文运行环境中(比如:系统UI进程)有了一个在updateClock方法每次被调用时都会被调用的方法。现在让我们修改一些东西吧。

第一个要检查的:我们有具体的Clock类型的引用么?是的,我们有。这就是param.thisObject参数。所以如果方法通过myClock.updateClock()被调用,那么param.thisObject 就是 myClock。

接下来:我们对clock做什么?Clock类型并不可用,你不能将param.thisObject转换为类(也不要试着这样做)。然而它是从TextView继承而来。所以一旦你把Clock的引用转换为TextView,你可以使用像setText, getText 和 setTextColor之类的方法。我们的改动应该在原有的方法设置了新的时间以后进行。因为在方法被调用前什么都没有做,我们可以空着beforeHookedMethod。没有必要调用(空的)“super”方法。

所以以下是完整的源代码:

package de.robv.android.xposed.mods.tutorial;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import android.graphics.Color;
import android.widget.TextView;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class Tutorial implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals("com.android.systemui"))
            return;

        findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                TextView tv = (TextView) param.thisObject;
                String text = tv.getText().toString();
                tv.setText(text + " :)");
                tv.setTextColor(Color.RED);
            }
        });
    }
}

对结果感到满意


现在启动/安装你的app。因为你第一次启动它时已经在Xposed Installer中把它设置为了可用,你就不需要在做这一步了。重启即可。然而,如果你正在使用这个红色钟表的例子,你也许想禁用它。两者都对它们的updateClock 处理程序使用了默认的优先级。所以你不清楚哪个会胜出(实际上这依赖于处理方法的字符串表示形式,但不要依赖这个方式)。

结论


我知道这个教程很长。但我希望你现在不但能实现一个绿色的表,也能够做出一些完全不同的东西。找到好的被hook的方法是经验的问题,所以先从简单的做起。试着一开始多使用log函数确保每次调用都是你预期的。现在:祝玩得开心!

Android之免清单注册启动Activity

在此立志:我要努力大学毕业进BAT


实习目标:Activity不需要注册在清单即可通过intent启动。

  • 有些文章叫做hook技术。大致内容为监听方法或者的调用或触发,期间修改方法参数或者返回值达到无须需改app源码即可修改app。如Xpose有插件可防止qq撤销消息。

  • 我们今天监听activity的启动然后进行方法修改,期间会用动态代理和大量的反射

Activity启动流程分析 第一章

首先学习activity启动流程:
假设我们界面有个按钮 点击后触发activityonclick方法

//activity
public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

按照我们的理解接下来的事情自然就是界面跳转了。我主要研究的startActivity内部实现的调用过程到启动一个activity流程。
这个方法的实现是在ContextImpl.java 中然后我们再看继承关系图:
这里写图片描述
结果方法继承关系里面并没有ContextImpl.java 。他其实被包裹在ContextWrapper.java

//ContextWrapper.java
public class ContextWrapper extends Context {
    Context mBase;
    ...省略其他代码
 @Override
    public void startActivity(Intent intent) {
        mBase.startActivity(intent);
    }
    ...省略其他代码  
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

可以看到里面有个mBase属性对象。在调用startActivity的时候会调用mBasestartActivity方法。这个mBase就是ContextImpl.java

现在来分析ContextImpl.java
我们看看这个类的startActivity的方法的实现

//ContextImpl.java
    @Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

直接看startActivity(intent, null);方法,前一个方法是用于进程检测的。

 @Override
    public void startActivity(Intent intent, Bundle options) {
      //...省略代码 只看方法的重点
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可以看到此方法调用 mMainThread.getInstrumentation()获取一个对象然后调用对象的方法啊execStartActivity。其中 mMainThread对象的声明为final ActivityThread mMainThread;

mMainThread.getInstrumentation()查看实现如下

//ActivtyThread.java
 public Instrumentation getInstrumentation()
    {
        return mInstrumentation;
    }
  • 1
  • 2
  • 3
  • 4
  • 5

所以我们继续查看Instrumentation 对象的execStartActivity方法

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 //...省略方法其他方法
 int result = ActivityManagerNative.getDefault().startActivity(一堆参数);

//...     
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上面的代码就是传入的参数特别的参数太多。所以用四个字代替。我们看到ActivityManagerNative.getDefault()获取一个对象然后调用startActivity()

ActivityManagerNative.getDefault()方法探究

//ActivityManagerNative.java
  static public IActivityManager getDefault() {
        return gDefault.get();
    }
  • 1
  • 2
  • 3
  • 4

gDefaultActivityManagerNative.java属性对象

声明如下:

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Singleton又是什么?这个是一个单例工具类,当你第一次调用此类的get()会回调使用则自己实现的抽象方法create()进而进行单例操作

此类完整代码:

//Singleton.java
package android.util;
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这类我们以后反射其拿到IActivityManager 大家可以简单可以

大家再回过头看看实现抽象的方法create()

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            //从系统服务管理器获取activityMnagerServer(这是一个系统服务,所以需要AIDL进行进程间通信)
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
           //利用IBinder 对象转化为对应实例化接口
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从上面的例子可以看到,看到ActivityManagerNative.getDefault()返回了远程服务对象IActivityManager 接口。

再回头看看Instrumentation 对象的execStartActivity方法

//Instrumentation.java
 public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
                 //...省略方法其他方法
 int result = ActivityManagerNative.getDefault().startActivity(一堆参数);

//...     
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可以得出以下结论:获取一个远程服务对象接口IActivityManager(并且是单例)然后调用IActivityManagerstartActivity方法。此时会回调到远程服务ActivityManagerServer的真正startActivity接口方法。里面会检查你传入的Intent对象的Activity是否在清单内,如果你的activity不在清单内,就是一个异常.但是我们不需要再看ActivityManagerServer源码,因为它是另一个app进程,所以无法干涉。如果可以干涉的话,Android系统就太不稳定了,肯定会有流氓开发者,让你按上一个app干扰其他app那不是奔了?所以我们无法hook到远程服务,但是IActivityManager我们却是可以用手脚的。因为那是服务端存在客户端一个代理接口对象、(这一块需要大家简单了解下AIDL的知识。如果不理解没关系,只需记住它做些手脚)

看完上面的很乱吧,看下图来屡屡思路:
(大家可以右键保存看大图)
这是个图片
这时候读者一定会问?既然ActivityManagerServer启动activity我们无法干涉那么后面干嘛?

我们仔细看上面的图IActivityManager调用的时候会把要启动的ActivityIntent去给ActivityManagerServer,假设我们此时用动态代理在IActivityManager调用远程服务之前,把在一个在清单文件注册过的ActivityIntent替换不就通过校验了?当然这时候你又会想既然替换通过了验证,但是我们真正要启动的Activity都被替了哪还有什么意义?这里过验证后系统没有真正启动Activity,因为这个ActivityManagerServer最终回调到* *启动的 。这里大家先不要着急,先通过验证再说。

第一个小目标 实现替换

假设我们我们有3个Activity第一个为MainActivity,第二个PlaceholdActivity 第三个为SecondActivity

  • MainActivity 在清单文件注册过,继承哪个Activity都无所谓

  • PlaceholdActivity 在清单文件注册过,继承哪个Activity都无所谓

  • SecondActivity没有清单文件注册过,并且继承Activity。(不要继承不然会失败AppCompatActivity)

其中我们在MainActivity 点击一个按钮触发其onclick方法调用startActivity跳转到SecondActivity

//MainActivity.java 
    public void onClick(View view) {
        Intent intent = new Intent(this,SecondActivity.class);
        startActivity(intent);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

按照常理因为SecondActivity没有在清单文件注册过所以会失败,我们的目的把这个意图换成启动PlaceholdActivity 的意图

步骤1

创建一个Application类名为APP.java(记得在清单文件中关联哦)
重写attachBaseContext方法

  • attachBaseContext方法介绍:
    此方法会在oncreate方法前调用,还记得我们说的ContextWrapperContextImpl吗?再回过头看看吧(application也是继承ContextWrapper的)。
public class ContextWrapper extends Context {
    Context mBase;

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

attachBaseContext是在传入mBase对象赋值的回调的。此时你其实就可以使用上下文了。

此时我们的代码如下:

//APP.java
public class APP extends Application {
    //后面有用,用于保存原目标Activity的Intent的KEY 大家先看看
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们前面分析知道想要在过Activity的清单文件校验,就必须在IActivityManager调用startActivity方法之前进行Intent意图替换。
所以我们直接用动态代理IActivityManagerIActivityManager

首先要动态代理你必须拿到IActivityManager对象。而此对象保存在ActivityManagerNative的属性对象gDefault中。我们调用属性对象gDefault(Singleton类)的get方法或者得到其内部属性对象mInstance

mInstance是用于保存一个单例对象的实例。很混乱?再回头看看Singleton

//Singleton.java
public abstract class Singleton<T> {
      //保存对象实例
    private T mInstance;
    //Singleton对象为空会调用此方法得到实例
    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

好了先获取gDefault对象吧,我们看看ActivityManagerNative对其的声明

//ActivityManagerNative.java
  private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

我们发现是个静态类,所以我们应该笑了,静态类通过反射机制很容易得到对象实例

  • 所以得到如下代码:
public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            Class<?> ams_class = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ams_class.getDeclaredField("gDefault");
            //因为gDefault是private所以用发射机制打破访问限制
            gDefault.setAccessible(true);
            //得到gDefault实例对象 因为是stati属性所以get传入null即可得到
            Object gDefault_instance = gDefault.get(null);
            //单例工具类class
            Class<?> singleton_class = Class.forName("android.util.Singleton");
            //单例工具类的一个属性对象保存的是IActivityManager对象实例
            Field mInstance = singleton_class.getDeclaredField("mInstance");
            //打破封装访问
            mInstance.setAccessible(true);
            //传入gDefault对象实例得到IActivityManager对象实例
            final Object mInstance_instance = mInstance.get(gDefault_instance);
            //动态代理,这个不会我真不知道讲下去。此方法会返回一个实现IActivityManager接口的实例对象
            //拿着这个对象 替换单例工具类gDefault 的mInstance即可实现
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                   // IActivityManager接口方法太多 我们只关心它启动activity的接口方法
                    if (method.getName().equals("startActivity")) {
                        //获取调用此方法传入的参数 我们这里只要Intent替换,所以只需要Intent替换
                        for (int i = 0; i < args.length; i++) {
                            Object arg = args[i];
                            if (arg instanceof Intent) {
                                Intent intent = new Intent();
                                //PlaceholdActivity因为在清单文件注册过所以 创建一个新的Intent替换原来的SecondActivity的意图。后面用到
                                // 并将其保存到新的Intent中
                                ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
                                intent.setComponent(componentName);
                                intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
                                args[i] = intent;
                                return method.invoke(mInstance_instance, args);
                            }
                        }

                    }

                    return method.invoke(mInstance_instance, args);
                }
            });
//         替换单例工具类gDefault 的mInstance实现动态代理
            mInstance.set(gDefault_instance, proxyInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

你运行代码后发现不管startActivity填入什么意图都是启动PlaceholdActivity就证明你成功了

Activity启动流程分析之Handler源码分析 第二章

这里只会简单讲讲不会过多。不然后脱离主旨
Looper循环器,不断从对应线程MassgaeQueue中死循环拿取mesaage然后发送到对应的handler

handler有消息的会调用如下方法:

    //Handler.java
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //如果mCallback 不为空并且处理后返回true 那么不会调用我们自己写handlerMessage处理消息。
            handleMessage(msg);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 我们后面会用此mCallback 就行再次替换Intent

我们知道在学习javase的时候我们程序是从main方法进入,那么安卓的app入门方法在哪?ActivityThread.java中存在的main方法就是

//ActivityThread.java
public static void main(String[] args) {

        //...代码省略
        //Looper类一些初始化,会从ThreadLocal对象拿取当前线程的Looper
        //关于ThreadLocal不做过多讲述,简单就是用线程资源拷贝。
        //假设你有个变量a 你可以拷贝三份到ThreadLocal中不同的线程 
        Looper.prepareMainLooper();
        //...代码省略 
        ActivityThread thread = new ActivityThread();
         //开启一个子线程,又可以叫binder线程用于ams(ActivityManagerServer通AIDL来进程间通信,如果有信息那么从子线程发送message给主线程handler进行处理)
        thread.attach(false);

       //...代码省略
           //开启一个死循环遍历messageQueue(里面存放messager),如果有就,所以前面才开启一个子线程,不然怎么跟AMS通信?
        Looper.loop();

       //...代码省略
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

看看 thread.attach(false);方法

//ActivityThread.java
  private void attach(boolean system) {
        sCurrentActivityThread = this;
          //...忽略代码
      }
  • 1
  • 2
  • 3
  • 4
  • 5

我们只看到方法 把当前线程对象(ActivityThread)保存到sCurrentActivityThread 我们后面会用到所以这里讲解。

我们前面第一章分析中到通过IActivityManager发送startActivity方法到ActivityManagerServer中,然后我们知道知道在ActivityThread类中main方法死循环前开启了个子线程,这个线程会接收ActivityManagerServer回馈的信息,信息封装在Message中然后添加主线程MeassgeQueue中,当主线程的Loop遍历到有信息的时候交给主线程的Handler处理。

  • ActivityManagerServer收到startActivity信息的信息的时候,会发送一个messageHandler处理。其中message.what=LAUNCH_ACTIVITYLAUNCH_ACTIVITY=100

我们看看ActivityHandler在哪

public class AcitvityThread{
    final H mH = new H();
    private class H extends Handler {
    //。。。代码省略
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们在看看mH这个Handler怎么处理private class H extends Handler 的事件

//ActivityThread.java
class H extends Handler{
//.....
public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                   //从msg拿出ActivityClientRecord 对象,里面包含启动activity的Intent
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                            //带着ActivityClientRecord 对象启动activity
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

大致可简单的理解从msg获取ActivityClientRecord 对象,内包含启动Intent启动意图等信息,还记得我们前面保存一个Intent到一个新的Intent上吗?这时候其实你可以从这个Intent取出来了。调用handleLaunchActivity去启动,最后大家想生命周期可以自己往下深究。

//ActivityThread.java
    static final class ActivityClientRecord {
        IBinder token;
        int ident;
        Intent intent;
        String referrer;
        //....
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

先来整理回调流程:
(因为懒省略很多细节)

这里写图片描述

所以我们可以handlermCallback赋值,此时handler回调lancherActivity的时候,我们知道mCallback不为空的时候,先会回调mCallback再根据其返回值在判断要不要对其自己实现的handlerMessagetrue的时候不会调用自己实现的方法)

所以先获取handler对象,而handler对象存在ActivityThread中,再来看看ActivityThread.java对其的声明

//ActivityThread.java
public final class ActivityThread {
 final H mH = new H();
 }
  • 1
  • 2
  • 3
  • 4

因为只mH不是静态类型,所以先要获取ActivityThread 对象才能获取mH实例。
还记得我们前面分析ActivityThreadmain方法吗?
内部调用

public static void main(String[] args) {
//...
 ActivityThread thread = new ActivityThread();
        thread.attach(false);
    //...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

再看看 thread.attach(false);方法

//ActivityThread.java
private void attach(boolean system) {
        sCurrentActivityThread = this;
        //....
        }
  • 1
  • 2
  • 3
  • 4
  • 5

可以看到ActivityThread对象保存在ActivityThread的属性对象sCurrentActivityThread

//ActivityThread.java
public final class ActivityThread {
 //...
  private static ActivityThread sCurrentActivityThread;
//...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

静态属性证明你懂的,你可以利用反射机制获取对象

所以综上代码综合可得:

//APP.java
package com.example.fmy.myapplication;

public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

            //过检验代码省略
            Class<?> activityThread_class = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread_field.setAccessible(true);
            //获取静态属性对象实例
            Object activityThread_instance = sCurrentActivityThread_field.get(null);

            Field mH_field = activityThread_class.getDeclaredField("mH");

            mH_field.setAccessible(true);
            //获取handler对象实例
            Object mH_insance = mH_field.get(activityThread_instance);

            Field mCallback_field = Handler.class.getDeclaredField("mCallback");


            mCallback_field.setAccessible(true);
            //给handler添加mCallback
            mCallback_field.set(mH_insance, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        //得到ActivityClientRecord
                        Object obj = msg.obj;
                        try {
                            //得到Intent对象
                            Field intent_field = obj.getClass().getDeclaredField("intent");
                            intent_field.setAccessible(true);
                            Intent intent = (Intent) intent_field.get(obj);
                            //取出我们前面存在Intent里的原本没有注册在清单文件的Activity的Intent
                            Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);

                            if (target_intent != null) {
                                intent.setComponent(target_intent.getComponent());
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                    return false;

                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

最后整合下过验证的代码如下:

//APP.java
package com.example.fmy.myapplication;

public class APP extends Application {
    private static final String KEY_EXTRA_TARGET_INTENT = "EXTRA_TARGET_INTENT";
    private static final String TAG = "APP";

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        try {
            Class<?> ams_class = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ams_class.getDeclaredField("gDefault");
            //因为gDefault是private所以用发射机制打破访问限制
            gDefault.setAccessible(true);
            //得到gDefault实例对象 因为是stati属性所以get传入null即可得到
            Object gDefault_instance = gDefault.get(null);
            //单例工具类class
            Class<?> singleton_class = Class.forName("android.util.Singleton");
            //单例工具类的一个属性对象保存的是IActivityManager对象实例
            Field mInstance = singleton_class.getDeclaredField("mInstance");
            //打破封装访问
            mInstance.setAccessible(true);
            //传入gDefault对象实例得到IActivityManager对象实例
            final Object mInstance_instance = mInstance.get(gDefault_instance);
            //动态代理,这个不会我真不知道讲下去。此方法会返回一个实现IActivityManager接口的实例对象
            //拿着这个对象 替换单例工具类gDefault 的mInstance即可实现
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), mInstance_instance.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // IActivityManager接口方法太多 我们只关心它启动activity的接口方法
                    if (method.getName().equals("startActivity")) {
                        //获取调用此方法传入的参数 我们这里只要Intent替换,所以只需要Intent替换
                        for (int i = 0; i < args.length; i++) {
                            Object arg = args[i];
                            if (arg instanceof Intent) {
                                Intent intent = new Intent();
                                //PlaceholdActivity因为在清单文件注册过所以 创建一个新的Intent替换原来的SecondActivity的意图。后面用到
                                // 并将其保存到新的Intent中
                                ComponentName componentName = new ComponentName("com.example.fmy.myapplication", PlaceholdActivity.class.getName());
                                intent.setComponent(componentName);
                                intent.putExtra(KEY_EXTRA_TARGET_INTENT, ((Intent) arg));
                                args[i] = intent;
                                return method.invoke(mInstance_instance, args);
                            }
                        }

                    }

                    return method.invoke(mInstance_instance, args);
                }
            });
//         替换单例工具类gDefault 的mInstance实现动态代理
            mInstance.set(gDefault_instance, proxyInstance);

            //过检验结束
            Class<?> activityThread_class = Class.forName("android.app.ActivityThread");
            Field sCurrentActivityThread_field = activityThread_class.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread_field.setAccessible(true);
            //获取静态属性对象实例
            Object activityThread_instance = sCurrentActivityThread_field.get(null);

            Field mH_field = activityThread_class.getDeclaredField("mH");

            mH_field.setAccessible(true);
            //获取handler对象实例
            Object mH_insance = mH_field.get(activityThread_instance);

            Field mCallback_field = Handler.class.getDeclaredField("mCallback");


            mCallback_field.setAccessible(true);
            //给handler添加mCallback
            mCallback_field.set(mH_insance, new Handler.Callback() {

                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        //得到ActivityClientRecord
                        Object obj = msg.obj;
                        try {
                            //得到Intent对象
                            Field intent_field = obj.getClass().getDeclaredField("intent");
                            intent_field.setAccessible(true);
                            Intent intent = (Intent) intent_field.get(obj);
                            //取出我们前面存在Intent里的原本没有注册在清单文件的Activity的Intent
                            Intent target_intent = intent.getParcelableExtra(KEY_EXTRA_TARGET_INTENT);

                            if (target_intent != null) {
                                intent.setComponent(target_intent.getComponent());
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }
                    return false;

                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Override
    public void onCreate() {
        super.onCreate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

GIT源码附上源码连接

KMP next数组讲解

本文只讲解KMP子串keyString(下标用j表示),中和源字符串SoureceString(下标i表示)某次匹配失败后。下次匹配j的取值。
关于KMP算法入门可以看看其他博文(最主要网上关于这个算法写烂了,我这里只记录下我学习不懂的地方)

前言

在学KMP算法的时候一直感觉next数组的实现是让我最头痛的。所以现在回过头写下笔记,方便以后再看。

规则:
1. keyString(子串)的next数组大小为keyString的长度。
2. next第一个元素为-1
3. next数组下标数等于 子串前缀和后缀的匹配数

前缀概念:
子字符串的从第一个元素开始到第x元素 和 字符第y个元素到最后一个字符串相等.(x和y可以相等)

注意 后缀的概念:有个误区 是指子字符串第z个元素的时候 把z最为最后一个元素

eg:
字符串 keyString =“abcabcx”

这里写图片描述

我们可以把.下标为1的元素(即是第二个元素)作为后缀。那么前缀是a。后缀是b
我们把下标为5的元素作为后缀。那么前缀是abc(下标为0,1,2)后缀是abc(下标为 3,4,5)

上面对应next数组
这里写图片描述
当子串到下标为6的位置和源字符串匹配失败的时候,下次匹配直接到下标为3的位置

ok 现在我们开始现实某个字符串next数组的实现,首先写一个方法为get

    //java语言
    // T为子字符串
    // next数组 
    // 当方法结束的时候 next包含对应T的射影数值
    public static void get(String T, int[] next) {
    ....
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

我们先实现前两个规则

规则:
1. keyString(子串)的next数组大小为keyString的长度。
2.  next第一个元素为-1
  • 1
  • 2
  • 3

如下

//默认传入的next数组大小为子字符串大小 所以实现规则2即可
public static void get(String T, int[] next) {

        int i, j;

        j = -1;
        next[0] = -1;
        i = 0;

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后一个规则:

next数组下标数等于 子串前缀和后缀的匹配数.先看一下next完成的案例在说

算法核心也在这此处,再回头看看如下字符串和对应next数组
这里写图片描述

理解next数组的含义
我们看一下‘x’对应next为6
意味着:
1. 字符串从0到5作为前缀。(为什么不到6?因为数组下标从0开始)
2. 从x下标减6开始到x前面一个数作为后缀。
3. 有6个循环数。
这里写图片描述

先看最终代码(可能会疑惑在回溯部分,先暂时看看后面再解释):

 public static void get(String T, int[] next) {

        int i, j;

        j = -1;
        next[0] = -1;
        i = 0;
        while (i < T.length() - 1) {

            if (j == -1 || T.charAt(i) == T.charAt(j)) {

                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];//回溯
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

关于回溯我们举例说明说明

我们假设数组末尾追加一个字母d(下标为10)

这里写图片描述

假设我们的算法遍历到下标为10 数值为x的元素。
此时

j=6. i = 9
  • 1

此时算法 T.chatAt(i) 不等 T.chatAt(j)所以走else路径

 j = next[j];//回溯
  • 1
  • 将j回溯到next[6]的位置,如果此时相等那么 即T.chatAt(i)== T.chatAt(j)。那么就结束了。不然继续回溯。我当时在想为什么j会回溯到next[j]。原因如下,j代表着有多少个后缀和前缀数相等的数量。那么我们将设 下标为10(数值x)的元素等于下标为6(数值a)的元素 。循环到此的时候j=6代表着在下标为9的元素之前有6个相等数。(0,1,2,3,4,5 和 3,4,5,6,7,8)。那么6 和 9 相等就代表着相同后缀有6个。所以我们只需要判断字符串6和9即可(因为此算法如果前面的数字前缀相等那么会建立再次基础上 进行判断【j= 6 就是一个基础,它代表着 前缀的后面一个数 也代表着前缀和后缀相同数】)。

  • 那么假设不相等的情况的解释:
    继续假设运行到
    我们的算法遍历到下标为10 数值为x的元素。
    此时

j=6. i = 9
  • 1

(0,1,2,3,4,5 和 3,4,5,6,7,8前面判断已经相等),但是数组下标6和9不相等 进行j回溯到next[6]的位置,也就是 j = 3 的位置( next[6] = 3),
j代表某前缀的最后一个数的后面一个数。比如说 j =3 那么前缀就是 0 1 2。我们在我们 j =6 i =9 匹配失败后 你可以让 j = j-1继续匹配 T.chatAt(9),但是这是多余的。

假设 数组下标6 和9 匹配失败 进行 j=j-1
T.chatAt(6) != T.chatAt(9);
j = j-1 = 5;
那么
T.chatAt(5) == T.chatAt(9);
也就是if判断条件成立 可得
0 1 2 3 4 5 和 4 5 6 7 8 9相等.
那么可以得以下结论:
T.chatAt(5) == T.chatAt(9);
T.chatAt(5) == T.chatAt(8);
T.chatAt(8) == T.chatAt(9);
这三个不难推理出

继续
T.chatAt(4) == T.chatAt(8);
T.chatAt(9) == T.chatAt(4);
T.chatAt(9) == T.chatAt(5);

也就是 7==8==9 和 1==2==3

最终你将推出 0==1==2==3==4==5 ==7==8==9
显然这会和next数组原有数组数值发生冲突。顾不能直接j= j-1。当然如果数组前缀和后缀所有元素都相等 时候可以推出next[j] = j -1

Handler源码分析

虽然不是自己写的 但是写的很棒所以转载到此

Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。

概述


1、我们先说下什么是Android消息处理机制?

消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。

简单的说:一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

2、Android消息处理机制的工作原理?

打个比方:公司类比App

  • PM 的主要工作是设计产品,写需求文档,改需求,中途改需求,提测前改需求…
  • UI 主要工作是UI设计,交互等。
  • RD 工作我就不说了
  • CEO 不解释。

公司开创之后(App启动),那么CEO开始干活了(主线程【UI线程】启动),这时候CEO开启了无限循环工作狂模式,自己的公司没办法啊(相当于UI主线程转成Looper线程【源码里面有】)CEO招了一名RD(new Handler 实例)并把告诉PM和UI,如果你们有什么任务和需求就让RD(Handler实例)转告给我(CEO)。RD会把PM和UI的需求(Message)一条条记到CEO的备忘录里(MessageQueue)。CEO 无限循环的工作就是不断查看备忘录,看有什么任务要做,有任务就从备忘录一条一条拿出任务来,然后交给这一名RD(Handler 实例)去处理(毕竟CEO 不会写代码 囧…)。当然如果备忘录都做完了,这时候CEO就会去睡觉(线程堵塞【简单理解成线程wait】,让出CPU资源,让其他线程去执行)。但是这个备忘录有个特殊的功能就是没有任务的时候突然插入第一条任务(从无到有)就会有闹钟功能叫醒CEO起床继续处理备忘录。 整个消息处理机制的工作原理基本就是这样的。后面会有源码分析,你再来结合这个场景,会更好理解一些。

这里先给一张Android消息处理机制流程图和具体执行动画,如果看不懂没事,接着往下看(后面会结合Android UI主线程来讲解),然后结合着图和动画一块看更能理解整个机制的实现原理。

3、LooperHandlerMessageQueue,Message作用和存在的意义?

  • Looper
    我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

  • MessageQueue
    MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

  • Handler
    简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就会在主线程处理这个消息。那么怎么拿到主线程 MessageQueue的实例,是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

  • Message
    Message 很简单了,你想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。

源码分析


接下来我们会结合App主线程(UI线程)来讲解,从App启动后一步一步往下走分析整个Android的消息处理机制,首先在ActivityThread类有我们熟悉的main的函数,App启动的代码的入口就在这里,UI线程本来只是一个普通线程,在这里会把UI线程转换成Looper线程,什么是Looper线程,不急往下看就知道了。

public final class ActivityThread {
    public static final void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {    
            sMainThreadHandler = thread.getHandler();
        }
        ......
        Looper.loop();
        ......
    }
}

首先执行的是 Looper.prepareMainLooper() 我们来看下Looper里面的这个方法做了什么?

注:看之前先稍微了解下ThreadLocal是什么?
ThreadLocal: 线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。具体看下ThreadLocal.java 的源码!

public final class Looper {
    // sThreadLocal 是static的变量,可以先简单理解它相当于map,key是线程,value是Looper,
    //那么你只要用当前的线程就能通过sThreadLocal获取当前线程所属的Looper。
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    //主线程(UI线程)的Looper 单独处理,是static类型的,通过下面的方法getMainLooper() 
    //可以方便的获取主线程的Looper。
    private static Looper sMainLooper; 

    //Looper 所属的线程的消息队列
    final MessageQueue mQueue;
    //Looper 所属的线程
    final Thread mThread;

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
         //如果线程的TLS已有数据,则会抛出异常,一个线程只能有一个Looper,prepare不能重复调用。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //往线程的TLS插入数据,简单理解相当于map.put(Thread.currentThread(),new Looper(quitAllowed));
        sThreadLocal.set(new Looper(quitAllowed));
    }

    //实际上是调用  prepare(false),并然后给sMainLooper赋值。
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    //static 方法,方便获取主线程的Looper.
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

    public static @Nullable Looper myLooper() {
        //具体看ThreadLocal类的源码的get方法,
        //简单理解相当于map.get(Thread.currentThread()) 获取当前线程的Looper
        return sThreadLocal.get();
    }
}

看了上面的代码(仔细看下注释),我们发现 Looper.prepareMainLooper()做的事件就是new了一个Looper实例并放入Looper类下面一个static的ThreadLocal<Looper> sThreadLocal静态变量中,同时给sMainLooper赋值,给sMainLooper赋值是为了方便通过Looper.getMainLooper()快速获取主线程的Looper,sMainLooper是主线程的Looper可能获取会比较频繁,避免每次都到 sThreadLocal 去查找获取。

接下来重点是看下Looper的构造函数,看看在new Looper的时候做了什么事?

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

看到没有,这时候给当前线程创建了消息队列MessageQueue,并且让Looper持有MessageQueue的引用。执行完Looper.prepareMainLooper() 之后,主线程从普通线程转成一个Looper线程。目前的主线程线程已经有一个Looper对象和一个消息队列mQueue,引用关系如下图:(主线程可以轻松获取它的Looper,主线程的Looper持有主线程消息队列的引用)

具体如何获取主线程的Looper对象和消息列表呢?

//在主线程中执行
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue
//或者
mLooper=Looper.getMainLooper()

接下来回到ActivityThread 的main函数,执行完Looper.prepareMainLooper() 之后下一句代码是ActivityThread thread = new ActivityThread();这句话就是创建一下ActivityThread对象,这边需要注意的时候ActivityThread并不是一个线程,它并没有继承Thread,而只是一个普通的类public final class ActivityThread{...}ActivityThread的构造函数并没有做什么事只是初始化了资源管理器。

 ActivityThread() {
     mResourcesManager = ResourcesManager.getInstance();
 }

接着往下看下一行代码

ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
thread.attach(false);

thread.attach(false);便会创建一个Binder线程(具体是指ApplicationThread,该Binder线程会通过想 HandlerMessage发送给主线程,之后讲)。我们之前提到主线程最后会进入无限循环当中,如果没有在进入死循环之前创建其他线程,那么待会谁会给主线程发消息呢?,没错就是在这里创建了这个线程,这个线程会接收来自系统服务发送来的一些事件封装了Message并发送给主线程,主线程在无限循环中从队列里拿到这些消息并处理这些消息。(Binder线程发生的消息包括LAUNCH_ACTIVITYPAUSE_ACTIVITY 等等)

继续回到mian 函数的下一句代码Looper.loop() 那么重点来了,我们来看下Looper.loop()的源码:

public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象,获取当前线程的Looper 
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }

    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
    ....

    for (;;) { //主线程开启无限循环模式
        Message msg = queue.next(); //获取队列中的下一条消息,可能会线程阻塞
        if (msg == null) { //没有消息,则退出循环,退出消息循环,那么你的程序也就可以退出了
            return;
        }
        ....
        //分发Message,msg.target 是一个Handler对象,哪个Handler把这个Message发到队列里,
        //这个Message会持有这个Handler的引用,并放到自己的target变量中,这样就可以回调我们重写
        //的handler的handleMessage方法。
        msg.target.dispatchMessage(msg);
        ....
        ....
        msg.recycleUnchecked();  //将Message回收到消息池,下次要用的时候不需要重新创建,obtain()就可以了。
    }
}

上面的代码,大家具体看下注释,这时候主线程(UI线程)执行到这一步就进入了死循环,不断地去拿消息队列里面的消息出来处理?那么问题来了
1、UI线程一直在这个循环里跳不出来,主线程不会因为Looper.loop()里的死循环卡死吗,那还怎么执行其他的操作呢?

  • 在looper启动后,主线程上执行的任何代码都是被looper从消息队列里取出来执行的。也就是说主线程之后都是通过其他线程给它发消息来实现执行其他操作的。生命周期的回调也是如此的,系统服务ActivityManagerService通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程的Binder线程给主线程的消息队列插入一条消息来实现的。

2、主线程是UI线程和用户交互的线程,优先级应该很高,主线程的死循环一直运行是不是会特别消耗CPU资源吗?App进程的其他线程怎么办?

  • 这基本是一个类似生产者消费者的模型,简单说如果在主线程的MessageQueue没有消息时,就会阻塞在loop的queue.next()方法里,这时候主线程会释放CPU资源进入休眠状态,直到有下个消息进来时候就会唤醒主线程,在2.2 版本以前,这套机制是用我们熟悉的线程的wait和notify 来实现的,之后的版本涉及到Linux pipe/epoll机制,通过往pipe管道写端写入数据来唤醒主线程工作。原理类似于I/O,读写是堵塞的,不占用CPU资源。

所以上面代码的重点是queue.next() 的函数,其他的我们就不多说了,我们来看下queue.next()的源码(主要还是看注释):

Message next() 

        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration

        //nextPollTimeoutMillis 表示nativePollOnce方法需要等待nextPollTimeoutMillis 
        //才会返回
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //读取消息,队里里没有消息有可能会堵塞,两种情况该方法才会返回(代码才能往下执行)
            //一种是等到有消息产生就会返回,
            //另一种是当等了nextPollTimeoutMillis时长后,nativePollOnce也会返回
            nativePollOnce(ptr, nextPollTimeoutMillis);
            //nativePollOnce 返回之后才能往下执行
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // 循环找到一条不是异步而且msg.target不为空的message
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                       // 虽然有消息,但是还没有到运行的时候,像我们经常用的postDelay,
                       //计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
                       //表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 获取到消息
                        mBlocked = false;
                       //链表一些操作,获取msg并且删除该节点 
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        //返回拿到的消息
                        return msg;
                    }
                } else {
                    //没有消息,nextPollTimeoutMillis复位
                    nextPollTimeoutMillis = -1;
                }
                .....
                .....

    }

nativePollOnce()很重要,是一个native的函数,在native做了大量的工作,主要涉及到epoll机制的处理(在没有消息处理时阻塞在管道的读端),具体关于native相关的源码本篇文章不涉及,感兴趣的同学可以网上找找,有不少分析得比较深。

分析到这里,从应用启动创建Looper,创建消息队列,到进入loop方法执行无限循环中,那么这一块就告一段落了,主线程已经在死循环里轮询等待消息了,接下来我们就要再看看,系统是怎么发消息给主线程的,主线程是怎么处理这些个消息的?

在准备启动一个Activity的时候,系统服务进程下的ActivityManagerService(简称AMS)线程会通过Binder发送IPC调用给APP进程,App进程接到到调用后,通过App进程下的Binder线程最终调用ActivityThread类下面的scheduleLaunchActivity方法来准备启动Activity,看下scheduleLaunchActivity方法:

注:Binder线程:具体是指ApplicationThread,在App进程中接受系统进程传递过来的信息的线程(在主线程进入死循环之前创建了这个线程)。

  //这个方法不是在主线程调用,是Binder线程下调用的
  public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
  }

把启动一些信息封装成ActivityClientRecord之后,最后一句调用sendMessage(H.LAUNCH_ACTIVITY, r);我们接着往下看:

private 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);
    }

看到没有,最后启动Activity的信息都封装一个Message,但是这里有个问题了,之前在分析main函数的时候,完全没给出往主线程消息队列插入消息的方式,这里有了消息,但是怎么发到主线程的消息队列呢?最后一句又是重点mH.sendMessage(msg); mH 是什么呢?难道是Handler,我们来看下它是什么东西?
我们看了下ActivityThread 的成员变量,发现一句初始化的代码

final H mH = new H();

继续往下看H是什么?

public final class ActivityThread{
     ....
     final H mH = new H();
     ....
     private class H extends Handler {
     ....
     ....
     public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                   .....
         }
         .....
         .....
     }
}

H 果不出其然是Handler,而且是ActivityThread的内部类,看了一下它的handleMessage 方法,LAUNCH_ACTIVITYPAUSE_ACTIVITYRESUME_ACTIVITY…好多好多,H 类帮我们处理了好多声明周期的事情。那么再回到mH.sendMessage(msg)这句代码上,在Binder线程执行mH.sendMessage(msg);,由主线程创建的Handler mH实例发送消息到主线程的消息队列里,消息队列从无到有,主线程堵塞被唤醒,主线程loop拿到消息,并回调mHhandleMessage 方法处理LAUNCH_ACTIVITY 等消息。从而实现Activity的启动。

讲到这里,基本一个启动流程分析完了,大家可能比较想知道的是 mH.sendMessage(msg); 关于Hanlder是怎么把消息发到主线程的消息队列的?我们接下来就讲解下Handler,首先看下Handler的源码!我们先来看看我们经常用的Handler的无参构造函数,实际调用的是Handler(Callback callback, boolean async)构造函数(看注释)

 public Handler() {
        this(null, false);
 }
 public Handler(Callback callback, boolean async) {
        //不是static 发出可能内存泄露的警告!
        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());
            }
        }
        //获取当前线程的Looper,还记得前面讲过 Looper.myLooper()方法了吗?
        //Looper.myLooper()内部实现可以先简单理解成:map.get(Thread.currentThread()) 
        //获取当前线程的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            //当前线程不是Looper 线程,没有调用Looper.prepare()给线程创建Looper对象
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //让Handler 持有当前线程消息队列的引用
        mQueue = mLooper.mQueue;
        //这些callback先不管,主要用于handler的消息发送的回调,优先级是比handlerMessage高,但是不常用
        mCallback = callback;
        mAsynchronous = async;
    }

上面的代码说明了下面几个问题:
1、Handler 对象在哪个线程下构建(Handler的构造函数在哪个线程下调用),那么Handler 就会持有这个线程的Looper引用和这个线程的消息队列的引用。因为持有这个线程的消息队列的引用,意味着这个Handler对象可以在任意其他线程给该线程的消息队列添加消息,也意味着Handler的handlerMessage 肯定也是在该线程执行的。
2、如果该线程不是Looper线程,在这个线程new Handler 就会报错!
3、上面两点综合说明了下面一段很常见的代码:把普通线程转成Looper线程的代码,为什么在Looper.prepare()Looper.loop()中间要创建Handler:

 class LooperThread extends Thread {
       //其他线程可以通过mHandler这个引用给该线程的消息队列添加消息
       public Handler mHandler;
       public void run() {
            Looper.prepare();
            //需要在线程进入死循环之前,创建一个Handler实例供外界线程给自己发消息
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //Handler 对象在这个线程构建,那么handleMessage的方法就在这个线程执行
                }
            };
            Looper.loop();
        }
    }

那么接下来,我们接着往下看Handler的sendMessage(msg)方法,这个方法也是比较重要的,也比较常用,Handler 有很多sendXXXX开头的方法sendMessageAtTimesendEmptyMessageDelayedsendEmptyMessage等等,都是用来给消息队列添加消息的,那么这些方法最终都会调用enqueueMessage来实现消息进入队列:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //这句话很重要,让消息持有当前Handler的引用,在消息被Looper线程轮询到的时候
        //回调handler的handleMessage方法
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //调用MessageQueue 的enqueueMessage 方法把消息放入队列
        return queue.MessageQueue(msg, uptimeMillis);
    }

我们再来看下MessageQueue 的enqueueMessage(msg, uptimeMillis)方法:

    boolean enqueueMessage(Message msg, long when) {
        // msg 必须有target也就是必须有handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
        //插入消息队列的时候需要做同步,因为会有多个线程同时做往这个队列插入消息
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            //when 表示这个消息执行的时间,队列是按照消息执行时间排序的
            //如果handler 调用的是postDelay 那么when=SystemClock.uptimeMillis()+delayMillis
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // p==null 表示当前消息队列没有消息
                msg.next = p;
                mMessages = msg;
                //需要唤醒主线程,如果队列没有元素,主线程会堵塞在管道的读端,这时
                //候队列突然有消息了,就会往管道写入字符,唤醒主线程
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //将消息放到队列的确切位置,队列是按照msg的when 排序的,链表操作自己看咯
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // 如果需要唤醒Looper线程,这里调用native的方法实现epoll机制唤醒线程,我们就不在深入探讨了
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

最后我们再看下Handler 的dispatchMessage方法,这个方法在Looper线程从消息队列拿出来的时候,通过msg.target.dispatchMessage(msg)调用的。

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        //优先调用callback方法
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //最后会回调我们重写的handleMessage 方法
            handleMessage(msg);
        }
    }

到这里,整个Android的消息处理机制Java层内容基本讲解完毕了(欢迎关注我的简书:Kelin)

注:【转载请注明,问题可留言,错误望指出,喜欢可打赏,博客持续更新,欢迎关注】