Android插件化探索(四)免安装运行Activity(下)

在上一篇中,我们介绍了两种免安装启动Activity的方法。但是那两种方法都有缺陷,必须在AndroidManifest.xml中注册。那么今天,我们来探索其它几种不需要在清单文件中注册的启动方式

静态代理启动activity

通过前几篇的探索我们知道,通过DexClassLoader可以加载类,通过AsserManager可以加载资源。但是Activity确有一个令人苦恼的问题——生命周期。
我们知道宿主中的Activity都有生命周期,那我们可不可以借助宿主Activity来借尸还魂?

首先我们在宿主中定义一个Activity,取名为ProxyActivity,并且在宿主清单中注册用来占坑。这个时候ProxyActivity是有生命周期的,这点毋容置疑。那么我们现在只需把插件中的Activity当做一个普通的类反射调用即可,既然是一个普通类那么setContentView,findViewById自然也没有效果了,所以需要调用ProxyActivity的setContentView。也就是说,其实我们每次启动的Activity是ProxyActivity,布局也是加载到ProxyActivity,findViewById也是从ProxyActivity中寻找,然后在ProxyActivity的各个生命周期被调用的时候反射调用插件中的相应方法。

说的可能有点抽象,还是直接上代码吧,ProxyActivity的代码如下。

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
public class ProxyActivity extends Activity {

public static final String EXTRA_DEX_PATH = "extra_dex_path";
public static final String EXTRA_ACTIVITY_NAME = "extra_activity_name";

private String mClass;
private String mDexPath;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//获取插件dex路径
mDexPath = getIntent().getStringExtra(EXTRA_DEX_PATH);
//要启动的Activity的完整类名
mClass = getIntent().getStringExtra(EXTRA_ACTIVITY_NAME);
//加载资源
loadResources(mDexPath);
//启动插件Activity
performLaunchActivity(savedInstanceState);
}

protected void performLaunchActivity(Bundle savedInstanceState) {
File dexOutputDir = this.getDir("dex", Context.MODE_PRIVATE);
//初始化classloader
DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,
dexOutputDir.getAbsolutePath(), null, ClassLoader.getSystemClassLoader());

//注意:以下只是把插件中的Activity当作一个普通的类进行反射调用
try {
Class<?> localClass = dexClassLoader.loadClass(mClass);
Constructor<?> localConstructor = localClass
.getConstructor();
Object instance = localConstructor.newInstance();//初始化插件Acitivity对象。

//获取插件Activity的setProxy方法,这个方法是我们事先在插件中约定好的
Method setProxy = localClass.getMethod("setProxy",
Activity.class);
setProxy.setAccessible(true);
//调用插件Activity的setProxy方法
setProxy.invoke(instance, this);//将ProxyActivity对象传给插件Activity,用于setContentView等等

//获取插件Activity中的onCreate方法。
Method onCreate = localClass.getDeclaredMethod("onCreate", Bundle.class);
onCreate.setAccessible(true);
//调用插件Activity中的onCreate方法。
onCreate.invoke(instance, savedInstanceState);//将savedInstanceState传给插件
} catch (Exception e) {
e.printStackTrace();
}
}




//替换资源。
private AssetManager mAssetManager;
private Resources.Theme mTheme;
protected void loadResources(String dexPath) {
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, dexPath);
mAssetManager = assetManager;
} catch (Exception e) {
e.printStackTrace();
}
Resources superRes = super.getResources();

mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}


private Resources mResources;
@Override
public AssetManager getAssets() {
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources() {
return mResources == null ? super.getResources() : mResources;
}

}

然后在插件中定义一个BaseActivity。让其他Acitivity实现它即可。

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
public class BaseActivity extends Activity {

public static final String EXTRA_DEX_PATH = "extra_dex_path";
public static final String EXTRA_ACTIVITY_NAME = "extra_activity_name";


protected Activity that; //指向插件Activity



/**
* 将代理Activity传给插件Activity
* @param proxyActivity
*/

public void setProxy(Activity proxyActivity) {
that = proxyActivity;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
//由于插件Activity已经不是真正意义上的Activity了,这里屏蔽掉super.onCreate。
}
//由于插件Activity已经不是真正意义上的Activity了,只能把布局给ProxyActivity来显示
@Override
public void setContentView(int layoutResID) {
that.setContentView(layoutResID);
}
}

接下来,我们的插件中的Activity就可以这么写。

1
2
3
4
5
6
7
8
9
10
public class TestActivity extends BaseActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_test);
}


}

宿主中启动代码,如下。

1
2
3
4
5
6
7
8
9
10
11
12
String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";

//获得包管理器
PackageManager pm = getPackageManager();
PackageInfo packageInfo=pm.getPackageArchiveInfo(path,PackageManager.GET_ACTIVITIES);
String packageName=packageInfo.packageName;

//启动Activity
Intent intent=new Intent(this,ProxyActivity.class);
intent.putExtra(ProxyActivity.EXTRA_DEX_PATH,path);
intent.putExtra(ProxyActivity.EXTRA_ACTIVITY_NAME,packageName+".TestActivity");
startActivity(intent);

经过测试,我们的插件Activity借尸还魂的被启动了。当然上面只反射了onCreate方法。为了使插件Activity具有完整的生命周期,我们还需反射onStart,onResume等等。通常我们会定义一个map来保存,然后再调用即可。

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
 private HashMap<String,Method> mActivityLifecircleMethods=new HashMap<>();
protected void instantiateLifecircleMethods(Class<?> localClass) {

String[] methodNames = new String[] {
"onRestart",
"onStart",
"onResume",
"onPause",
"onStop",
"onDestroy"
};
for (String methodName : methodNames) {
Method method = null;
try {
method = localClass.getDeclaredMethod(methodName);
method.setAccessible(true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
mActivityLifecircleMethods.put(methodName, method);
}
}

@Override
protected void onStart() {
Method method= mActivityLifecircleMethods.get("onStart");
if(method!=null){
try {
method.invoke(mRemoteActivity);
} catch (Exception e) {
e.printStackTrace();
}
}

super.onStart();
}
//.....
//省略了部分源码

但是这种方法有个缺陷,由于插件Activity已经不是真正意义上的Activity了,也就是说原本Activity的findViewById、setContentView和startActivity等等都已经不起作用,只能间接调用ProxyActivity的方法。换言之,如果BaseActivity没有重写setContentView使其指向ProxyActivity,那么其子类将不能使用this语法。全部改用that。that.findViewById,that.setContentView,that.startActivity等等。

替换Instrumentation

上面方法有一个缺陷,由于插件中的Activity已经不是真正意义的Activity,导致其严重依赖that语法。虽然说,比起之前的那些方法已经很好用了。但是,有没有更好用的方法?在介绍其他方法之前,我们来看一下Activity的启动过程。

startActivity源码解读

startActivity开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}

最终会走startActivityForResult方法。

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
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
//Instrumentation执行启动Activity
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);

if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
mStartedActivity = true;
}

cancelInputsAndStartExitTransition(options);

} else {
//内部也是调用Instrumentation的execStartActivity
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {

mParent.startActivityFromChild(this, intent, requestCode);
}
}
}

可以看出,startActivityForResult内部调用了Instrumentation的execStartActivity方法。execStartActivity如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options)
{

IApplicationThread whoThread = (IApplicationThread) contextThread;
//...
//省略了部分源码
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
//调用ActivityManagerNative的startActivity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//检查有没有启动成功,没有就抛出相应异常
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}

内部调用了ActivityManagerNative,getDefault().startActivity,ActivityManagerNative是一个Binder对象,连接着ActivityManagerService。而在ActivityManagerService中最终会调用ActivityStackSupervisor中的startActivityMayWait方法。ActivityStackSupervisor是一个Activity栈管家,其作用不言而喻,即是用来管理Activity的栈的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final int startActivityMayWait(IApplicationThread caller, int callingUid,
String callingPackage, Intent intent, String resolvedType,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
Bundle options, boolean ignoreTargetSecurity, int userId,
IActivityContainer iContainer, TaskRecord inTask)
{


//..
//省略了部分源码
int res = startActivityLocked(caller, intent, resolvedType, aInfo,
voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage,
realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
componentSpecified, null, container, inTask);
//..
//省略了部分源码
}

startActivityMayWait方法内部又调用了startActivityLocked,总之经历了一系列的权限验证和栈管理,最终调用realStartActivityLocked方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
final boolean realStartActivityLocked(ActivityRecord r,
ProcessRecord app, boolean andResume, boolean checkConfig)

throws RemoteException {


//..
//省略了部分源码
//调用ApplicationThread.scheduleLaunchActivity
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

//..
//省略了部分源码
return true;
}

从上面可以看出会调用app.thread.scheduleLaunchActivity,那么app.thread是什么呢?其实是一个客户端Binder对象,即ApplicationThread。是在mInstrumentation.execStartActivity中传递过去的,不记得回头看一下源码。ApplicationThread的相关源码如下。

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
     @Override
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);
//Handler发送消息
sendMessage(H.LAUNCH_ACTIVITY, r);
}

scheduleLaunchActivity中将相关信息包装到ActivityClientRecord然后传到了Handler中,Handler中相关源码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
//处理启动Activity
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
//..
//省略了部分源码

处理启动相关的代码在handleLaunchActivity中。

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

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//..
//省略了部分源码

// Make sure we are running with the most recent config.
handleConfigurationChanged(null, null);

WindowManagerGlobal.initialize();

//启动Activity
Activity a = performLaunchActivity(r, customIntent);

if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);

if (!r.activity.mFinished && r.startsNotResumed) {

try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);

if (r.isPreHoneycomb()) {
r.state = oldState;
}
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPause()");
}

} catch (SuperNotCalledException e) {
throw e;

} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
throw new RuntimeException(
"Unable to pause activity "
+ r.intent.getComponent().toShortString()
+ ": " + e.toString(), e);
}
}
r.paused = true;
}
} else {

try {
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {

}
}
}

而真正执行启动Activity的代码在performLaunchActivity中。

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
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {


//..
//省略了部分源码

Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//初始化Activity对象(传入classloader,类名,intent)。
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}

try {
//如果Application未初始化就先初始化Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);


if (activity != null) {
//初始化ContextImpl
Context appContext = createBaseContextForActivity(r, activity);
//初始化标题
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);

if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);//设置主题
}

activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
//调用onCreate
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
//..
//省略了部分源码

return activity;
}

代码有点长。核心的地方在这里mInstrumentation.newActivity,通过Instrumentation来实例化一个Activity。
其实Activity的启动流程我们可以简化一下。

  1. Activity中执行startActivity
  2. Instrumentation执行execStartActivity
  3. AMS进行一系列的权限验证和栈管理。
  4. Instrumentation执行newActivity,实例化Activity。

而且很容易能看出来,启动插件Activity第3步无法通过。
那么现在该怎么办?既然第3步实在瞒不过去,也就是说必须想办法让第3步中的Activity通过验证之后,我们才能动歪脑筋。
既然这样,我们的思路跟ProxyActivity是一样的,搞一个占坑的Activity。要想启动插件中的Activity,最后一定要想办法把它替换成插件Activity。也就是在最后一步动手脚,在Instrumentation执行newActivity时替换成插件Activity。

想法很美好。怎么实现呢?

实现方式

还记得我们在上一篇中替换ClassLoader和合并DexElement吗?那我们就故技重施,替换掉Instrumentation,这样我们就能为所欲为了,想想就有点小激动。修改方法如下。

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
private void hookInstrumentation(String path){
try {

File codeDir=getDir("dex", Context.MODE_PRIVATE);
//创建类加载器,把dex加载到虚拟机中
ClassLoader classLoader = new DexClassLoader(path,codeDir.getAbsolutePath() ,null,
this.getClass().getClassLoader());



//获取ActivityThread的Class
Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
//获取ActivityThread对象
Method currentActivityThreadMethod=activityThreadCls.getMethod("currentActivityThread");
Object currentActivityThread= currentActivityThreadMethod.invoke(null);

// 反射获取Instrumentation
Field mInstrumentationField = activityThreadCls.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
// Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);

//反射修改Instrumentation

Instrumentation hookInstrumentation = new HookInstrumentation(classLoader);
mInstrumentationField.set(currentActivityThread, hookInstrumentation);
}catch (Exception e){
e.printStackTrace();
}

}

可以看出我们替换成了自己需要的HookInstrumentation,HookInstrumentation的代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HookInstrumentation extends Instrumentation {
private ClassLoader mClassLoader;
public HookInstrumentation(ClassLoader classLoader){
this.mClassLoader=classLoader;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {


String cls=intent.getStringExtra(HookUtil.EXTRA_ACTIVITY_NAME);
if(cls!=null){

cl=mClassLoader;//替换Classloader
className = cls//替换className

}


return super.newActivity(cl, className, intent);
}
}

宿主中的启动代码如下。

1
2
3
4
5
6
7
String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";
//修改Instrumentation
hookInstrumentation( path);
//启动Activity
Intent intent=new Intent(this,ProxyActivity.class);
intent.putExtra(HookUtil.EXTRA_ACTIVITY_NAME,"com.maplejaw.hotplugin.PluginActivity");
startActivity(intent1);

测试通过。但是这个方法跟上篇同样存在资源加载问题。上篇我们用了反射修改LoadedApk中的资源目录。但是那种方法的弊端我们也提过了。所以这里换种思路。在插件的Activity中加入loadResources即可,一个Activity一个Context。那我们就修改所有插件Activity的资源指向目录。

1
2
3
4
5
6
@Override  
protected void onCreate(Bundle savedInstanceState) {
String path= Environment.getExternalStorageDirectory().getAbsolutePath()+"/2.apk";
loadResources(path);
super.onCreate(savedInstanceState);
}

当然实际应用中我们不可能每次都去读SD卡,用map之类的保存即可。
经测试,成功启动Activity,当然为了支持插件内部的Activity跳转,我们还需反射修改execStartActivity方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options)
{


//如果是启动插件,则修改intent
wrapIntent(who, intent);

try {
// 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
Method execStartActivity = Instrumentation.class.getDeclaredMethod(
"execStartActivity", Context.class, IBinder.class, IBinder.class,
Activity.class, Intent.class, int.class, Bundle.class);
execStartActivity.setAccessible(true);
return (ActivityResult) execStartActivity.invoke(mBase, who,
contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("do not support!!!" + e.getMessage());
}
}

最后

关于免安装启动Activity的方法探索完了。
其中dynamic-load-apk使用了ProxyActivity这种方式,Small使用了修改Instrumentation方式。


本文源码地址:https://github.com/maplejaw/HotPluginDemo