title: Atlas之启动过程(一)

date: 2017-05-02 15:00:00

概述

Atlas整个启动过程的时序如下图所示,本篇只关注下图中的1-5步。

Atlas之启动过程(一) - 图1

入口

我们都知道,app的入口是application,那么atlas的入口application是什么呢,看一下app模块中manifest中的定义

Atlas之启动过程(一) - 图2

看样子,是DemoApplication

真的是这样的吗?

当然不是

我们看一下反编译后的manifest

Atlas之启动过程(一) - 图3

为什么会是AtlasBridgeApplication呢,manifest中明明写的很清楚 : DemoApplication。

事实上为了接入的方便,Atlas在编译期就替换了入口application。不过,Atlas保证在运行期时会调用DemoApplication的相关方法。

1. AtlasBridgeApplication.attachBaseContext

我们看一下AtlasBridgeApplication中的相关方法

  1. @Override
  2. protected void attachBaseContext(Context base) {
  3. super.attachBaseContext(base);
  4. //...一些逻辑
  5. //1. 构造BridgeApplicationDelegate对象
  6. Class BridgeApplicationDelegateClazz = getBaseContext().getClassLoader().loadClass("android.taobao.atlas.bridge.BridgeApplicationDelegate");
  7. Constructor<?> con = BridgeApplicationDelegateClazz.getConstructor(parTypes);
  8. mBridgeApplicationDelegate = con.newInstance(this,getProcessName(...);
  9. //2. 执行BridgeApplicationDelegate的attachBaseContext方法
  10. Method method = BridgeApplicationDelegateClazz.getDeclaredMethod("attachBaseContext");
  11. method.invoke(mBridgeApplicationDelegate);
  12. }

可以看到,代码虽长,但其实就只干了两件事。

  • 反射并构造BridgeApplicationDelegate的实例
  • 执行BridgeApplicationDelegate的attachBaseContext方法。

先看BridgeApplicationDelegate的构造方法。

  1. //BridgeApplicationDelegate.java
  2. public BridgeApplicationDelegate(Application rawApplication...){
  3. mRawApplication = rawApplication;
  4. PackageManagerDelegate.delegatepackageManager(
  5. rawApplication.getBaseContext()
  6. );
  7. }

没什么内容,直接跟进delegatepackageManager的函数实现

  1. //PackageManagerDelegate.java
  2. public static void delegatepackageManager(Context context){
  3. mBaseContext = context;
  4. //1. 反射pm
  5. PackageManager manager = mBaseContext.getPackageManager();
  6. Class ApplicationPackageManager = Class.forName("android.app.ApplicationPackageManager");
  7. Field field = ApplicationPackageManager.getDeclaredField("mPM");
  8. field.setAccessible(true);
  9. Object rawPm = field.get(manager);
  10. //2. 动态代理pm
  11. Class IPackageManagerClass = Class.forName("android.content.pm.IPackageManager");
  12. mPackageManagerProxyhandler = new PackageManagerProxyhandler(rawPm);
  13. mProxyPm = Proxy.newProxyInstance(mBaseContext.getClassLoader(), new Class[]{IPackageManagerClass}, mPackageManagerProxyhandler);
  14. //3. 替换pm
  15. field.set(manager, mProxyPm);
  16. }

可以看到,这段代码的核心思想就是将系统的pm,替换为我们实现的PackageManagerProxyhandler。我们先不关注PackageManagerProxyhandler的实现,接着看BridgeApplicationDelegate中函数attachBaseContext的实现。

2. BridgeApplicationDelegate. attachBaseContext

BridgeApplicationDelegate.java

  1. public void attachBaseContext(){
  2. //2.1 hook之前准备工作
  3. AtlasHacks.defineAndVerify();
  4. //2.2 回调预留接口
  5. launcher.initBeforeAtlas(mRawApplication.getBaseContext());
  6. //2.3 初始化atlas
  7. Atlas.getInstance().init(mRawApplication, mIsUpdated);
  8. //2.4 处理provider
  9. AtlasHacks.ActivityThread$AppBindData_providers.set(mBoundApplication,null);
  10. }

函数实现大概分为四个部分,也是本篇文章的重点关注部分

  • hook之前的准备工作
  • 回调预留接口
  • 初始化atlas
  • 处理provider

我们也一个来看。

3. AtlasHacks.defineAndVerify

在开始分析之前,我们要知道,Android上动态加载方案,始终都绕不过三个关键的点:

  • 动态加载class
  • 动态加载资源
  • 处理四大组件 能够让动态代码中的四大组件在Android上正常跑起来

为了实现这三个目标,会在系统关键调用的地方进行Hook。比如,为了能够动态加载class,通常都会对classloader上动一些手脚,resource也是类似。

这里多提一句,在四大组件的处理上,atals与插件化有着很大的差异。

  • 插件化的核心思想其实是埋坑机制+借尸还魂 :通过预先在manifest中预留N个组件坑,在runtime时,通过“借尸还魂”实现四大组件的运行。
  • atlas不一样,它是在编译期就已经各个bundle的manifest写入到apk中。所以运行时,bundle中的组件可以和常规声明的组件一样正常运行,不用再做额外的处理。

回过头来,我们看看atlas为了实现上述目的,做了哪些准备工作。在跟进defineAndVerify函数的实现之前,先看一下AtlasHacks类中定义的字段

  1. //AtlasHacks.java
  2. // Classes
  3. public static HackedClass<Object> LoadedApk;
  4. public static HackedClass<Object> ActivityThread;
  5. public static HackedClass<android.content.res.Resources> Resources;
  6. // Fields
  7. public static HackedField<Object, Instrumentation> ActivityThread_mInstrumentation;
  8. public static HackedField<Object, Application> LoadedApk_mApplication;
  9. public static HackedField<Object, Resources> LoadedApk_mResources;
  10. // Methods
  11. public static HackedMethod ActivityThread_currentActivityThread;
  12. public static HackedMethod AssetManager_addAssetPath;
  13. public static HackedMethod Application_attach;
  14. public static HackedField<Object, ClassLoader> LoadedApk_mClassLoader;
  15. // Constructor
  16. public static Hack.HackedConstructor PackageParser_constructor;

给这段代码点个赞,非常的干净和清晰。代码定义了atlas框架所需要hook的所有的类、方法、属性等等字段。

  • 处理资源,hook Resources
  • 处理class,hook LoadedApk_mClassLoader

现在,在看defineAndVerify函数的实现

  1. //AtlasHacks.java
  2. public static boolean defineAndVerify() throws AssertionArrayException {
  3. allClasses();
  4. allConstructors();
  5. allFields();
  6. allMethods();
  7. }

实际上这四个级别的函数调用,对应之前定义的字段,为这些字段进行赋值。

  1. //AtlasHacks.java
  2. public static void allClasses() throws HackAssertionException {
  3. LoadedApk = Hack.into("android.app.LoadedApk");
  4. ActivityThread = Hack.into("android.app.ActivityThread");
  5. Resources = Hack.into(Resources.class);
  6. ActivityManager = Hack.into("android.app.ActivityManager");
  7. //...
  8. }
  9. public static void allFields() throws HackAssertionException {
  10. ActivityThread_mInstrumentation = ActivityThread.field("mInstrumentation").ofType(Instrumentation.class);
  11. ActivityThread_mAllApplications = ActivityThread.field("mAllApplications").ofGenericType(ArrayList.class);
  12. }
  13. //...

这几个函数的代码并不复杂,是对定义的Class、Field、Method和Contructor 这些字段进行初始化赋值工作。

执行到这里时,atlas框架的准备工作完成,接下来,就是整个框架的初始化了。

4 回调预留接口

时序图中的第4步,即BridgeApplicationDelegateattachBaseContext方法中,,做了一个接口回调。

BridgeApplicationDelegate.java

  1. public void attachBaseContext(){
  2. String preLaunchStr = (String) RuntimeVariables.getFrameworkProperty("preLaunch");
  3. AtlasPreLauncher launcher = (AtlasPreLauncher) Class.forName(preLaunchStr).newInstance();
  4. launcher.initBeforeAtlas(mRawApplication.getBaseContext());
  5. //...
  6. }

函数通过“preLaunch”字段读取一个类名,反射该类上的initBeforeAtlas方法。

AtlasPreLauncher实际上是一个接口,供接入者使用。在这个点,atlas还没有开始对系统进行hook,仍然是Android原生态的运行时环境。

那么这个preLaunch的字段在哪里定义的呢,我们反推一下。

RuntimeVariables.java

  1. public static Object getFrameworkProperty(String fieldName){
  2. //...
  3. Field field = FrameworkPropertiesClazz.getDeclaredField(fieldName);
  4. return field.get(FrameworkPropertiesClazz);
  5. }

实现很简单,读取FrameworkProperties上的静态属性字段,接着跟进。

  1. public class FrameworkProperties {
  2. }

什么情况,怎么什么都没有? 所谓反常即为?,直接看反编译后的代码

FrameworkProperties.java

  1. public class FrameworkProperties{
  2. //...
  3. public static String autoStartBundles;
  4. public static String preLaunch;
  5. static{
  6. autoStartBundles = "com.taobao.firstbundle";
  7. preLaunch = "com.taobao.demo.DemoPreLaunch";
  8. }
  9. }

从反编译后的代码可以看到,”prelaunch”对应的内容是com.taobao.demo.DemoPreLaunch,那么这个值是在 什么时候写入又是在哪里配置 的呢?

大家回想一下,在之前Atlas之Gradle配置中提到过,atlas的gradle插件在编译期搞了很多事情,我们看gradle中的设置

  1. atlas{
  2. tBuildConfig {
  3. autoStartBundles = ['com.taobao.firstbundle']
  4. preLaunch = 'com.taobao.demo.DemoPreLaunch'
  5. }
  6. }

FrameworkProperties中的字段完美对应。

这个部分牵涉开发-编译-运行三个阶段,放一张图捋一下关系。

Atlas之启动过程(一) - 图4

5 atlas.init

准备工作做好之后,就是初始化了。

Atlas.java

  1. public void init(Application application,boolean reset) {
  2. //读取配置项
  3. ApplicationInfo appInfo = mRawApplication.get...;
  4. mRealApplicationName = appInfo.metaData.getString("REAL_APPLICATION");
  5. boolean multidexEnable = appInfo.metaData.getBoolean("multidex_enable");
  6. if(multidexEnable){
  7. MultiDex.install(mRawApplication);
  8. }
  9. //...
  10. }

首先是读取manifest中的配置数据multidexEnablemRealApplicationName,这两个数据也是在编译期由atlas插件写到manifest中的。

Atlas之启动过程(一) - 图5

multidexEnable 是true,这个在gradle中可配

mRealApplicationName 实际上是DemoApplication,即app工程在manifest中指定的启动路径。

捋清楚这几个参数之后,往下看Atlas框架初始化实现

Atlas.java

  1. public void init(Application application,boolean reset) {
  2. //...
  3. Atlas.getInstance().init(mRawApplication, mIsUpdated);
  4. }
  5. public void init(Application application,boolean reset) throws AssertionArrayException, Exception {
  6. //...
  7. //1. 换classloader
  8. AndroidHack.injectClassLoader(packageName, newClassLoader);
  9. //2. 换Instrumentatio
  10. AndroidHack.injectInstrumentationHook(new InstrumentationHook(AndroidHack.getInstrumentation(), application.getBaseContext()));
  11. //3. hook ams
  12. try {
  13. ActivityManagerDelegate activityManagerProxy = new ActivityManagerDelegate();
  14. Object gDefault = null;
  15. if(Build.VERSION.SDK_INT>25 || (Build.VERSION.SDK_INT==25&&Build.VERSION.PREVIEW_SDK_INT>0)){
  16. gDefault=AtlasHacks.ActivityManager_IActivityManagerSingleton.get(AtlasHacks.ActivityManager.getmClass());
  17. }else{
  18. gDefault=AtlasHacks.ActivityManagerNative_gDefault.get(AtlasHacks.ActivityManagerNative.getmClass());
  19. }
  20. AtlasHacks.Singleton_mInstance.hijack(gDefault, activityManagerProxy);
  21. }catch(Throwable e){}
  22. //4. hook H
  23. AndroidHack.hackH();
  24. }

这段代码,就是对系统关键节点进行了hook,具体的hook实现这里就不贴出了,如果感兴趣,可以参考demo源码以及田维术的blog,如何hook,以及为什么在这里hook,在文章中都讲的非常清楚。

函数中的hook点

hook点 实例
ActivityThread.mLoadedApk.mClassLoader DelegateClassLoader
ActivityThread.mInstrumentation InstrumentationHook
ActivityManagerNative.gDefault.get ActivityManagerDelegate
android.app.ActivityThread\$H.mCallback ActivityThreadHook

6. 处理provider

在第2步的2.4部分,对provider进行了处理

BridgeApplicationDelegate.java

  1. public void attachBaseContext(){
  2. //...
  3. //2.4 处理provider
  4. Object mBoundApplication = AtlasHacks.ActivityThread_mBoundApplication.get(activityThread);
  5. mBoundApplication_provider = AtlasHacks.ActivityThread$AppBindData_providers.get(mBoundApplication);
  6. if(mBoundApplication_provider!=null && mBoundApplication_provider.size()>0){
  7. AtlasHacks.ActivityThread$AppBindData_providers.set(mBoundApplication,null);
  8. }
  9. }

第4行代码读取provider数据,如果有provider信息的话,则在第6行从系统删除,让系统认为apk并没有申请任何provider。

为什么这么做?回顾一下app启动的流程

Atlas之启动过程(一) - 图6

可以看到,在第4步和第7步两个关键调用之间,第5步调用了函数installContentProviders,跟进去看一下。

  1. private void installContentProviders(Context context, List<ProviderInfo> providers) {
  2. for (ProviderInfo cpi : providers) {
  3. installProvider(context, null, cpi,...);
  4. }
  5. }

收集了所有provider的信息,然后调用installProvider函数

  1. private IActivityManager.ContentProviderHolder installProvider(Context context,IActivityManager.ContentProviderHolder holder, ProviderInfo info,...) {
  2. final java.lang.ClassLoader cl = c.getClassLoader();
  3. localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();
  4. //...
  5. }

可以看到,函数是根据在manifest中登记的provider信息,实例化对象。

那么问题来了,有些provider是存在于bundle中的,主dex中并不存在。如果不处理,在这里程序会因为ClassNotFind崩溃。所以这里要先清除掉provier的信息,延迟加载。


上篇先到这里,各位先喝口水,再来下篇的分析。