一、背景:
前段时间有机会接触了轻应用相关的东西,虽然没有深入去开发,但通过阅读Runtime那边的一些相关文档,大概了解了开发的一个轻应用的流程和项目结构,轻应用不是一个完整的Android应用,因为它对Android四大组件只支持Activity,其实就是一个插件,这个插件是运行在一个叫做Runtime的环境上。首先了解下轻应用的结构:
一共4部分,js部分先忽略。
APK文件:这个APK文件包含了轻应用所有的资源和class(包括activity)。
libs目录:这个目录下可以放入一些动态链接库(so)。
data:这个类似于普通安卓应用下面的data目录,用于存放应用运行时的数据。
看到这里,发现这个轻应用和普通的应用没啥区别,但不用安装就能够调起APK里面的activity并且使用APK里面的资源,就像安装了该APK一样,很神奇。由此我猜想了一下Runtime的大概实现方式,并通过编码实现。
二、切入点ActivityGroup:
Activity是Android系统的一大组件,它的一般是由ActivityManagerService这个系统服务来调起的,这里的前提就是APK必须安装。所以我想轻应用里面的activity并没有作为真正的activity来启动,而是一个假的activity。看到这里你也许有些糊涂了,说明你需要了解下ActivityGroup这个类,这个类虽说已经快被fragment取代了,但它正是猜想Runtime实现方式的起点。ActivityGroup其实也是一个activity,但它内部却可以包含多个子activity,其内部展现的只是子activity的window。其实看下这个类的代码就会发现其子activiy的启动方式和一般activity启动方式是不一样的,是由LocalActivityManager这个类来启动的。看下构造方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
然后看下一个activity如何被LocalActivityManager启动的(省略了部分代码):
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 |
|
我们看到,是通过ActivityThread的startActivityNow来启动的,ActivityThread是一个android应用进程的入口,main方法就在这个类里面。
接下来就是分析startActivityNow这个方法了,这个方法主要进行一些参数准备,准备好后调用performLaunchActivity来实例化activity并调起,关键代码:
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 |
|
我们看到该方法中通过packageInfo中的classloader来load相应的activity类,然后创建一个实例,调用attach来初始化一些成员变量,最后执行activity的部分生命周期,onCreate,onStart,onRestoreInstanceState,onPostCreate。为什么部分呢?因为我只需要这个子activity的window。
看到这里,有人可能会问,我直接load这个activity的class并new出实例,然后手动掉生命周期不就行了?但你漏了关键一步,那就是attach,因为activity默认的onCreate里面会需要attach的参数,而我们写一个activity的时候,每个生命周期方法是必须调用super的相应方法的,如果缺少了这些参数,就会crash。到这里,我们的假activity的启动就完成了。
三、关键点LoadedApk:
上面我们看到activity是由packageInfo里面的classloader来加载的,这个packageInfo是什么类的实例呢?在2.3以前叫做PackageInfo(是ActivityThread里面的内部类,不是SDK里面的PackageInfo),后来改名叫LoadedApk,这个名字就能很好理解它的作用了,描述了一个已经加载了的APK。我们从上面的performLaunchActivity方法中知道了它的获取方法getPackageInfo:
1 2 3 4 5 |
|
继续看:
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 |
|
看到了么?有个mPackages变量:
1 2 |
|
原来一个android应用程序可以加载多个APK,每个APK由一个LoadedApk实例来描述,同时每个LoadedApk中有一个ClassLoader,用来加载APK中的类。具体参见Android源码。
四、概要设计:
经过上面的分析,大概能够理解Rutime的实现方式了:
1、每个轻应用的APK都会被load成LoadedApk实例(具体方法参见ActivityThread getPackageInfoNoCheck),通过反射给其mClassLoader赋值成APK对应的DexClassLoader,并加入到mPackages中。
2、每个APK中的activity作为假的activity来load,并创建一个空的宿主activity来显示。
大概有点类似tomcat加载web应用的结构。
五、实现:
参加 https://github.com/zhaoxuyang/koala--Android-Plugin-Runtime-