聊聊Android的静态代理插件框架原理[02]--Activity静态代理之生命周期

前文介绍了Android中如何在应用中加载一个普通未安装的apk文件,也提到了插件化面临的两个问题。一个是组件的生命周期,一个是资源的加载问题。系列的第二篇我们就从如何静态代理一个Activity,从而偷梁换柱,实现『启动一个未在Manifest中申明的Activity』的目标.

Activity静态代理之生命周期

前面提到,Android系统中对四大组件,例如Activity是有限制的,必须在AndroidManifest.xml文件中进行申明,才能启动并运行。
为了解决这个问题,有两种主流的思想,一种是通过Hook手段,在Android系统检查Activity申明的地方想办法Hook,使得未申明的Activity能够正常运行。另一种办法则是今天我们说的,静态代理。

怎么个『静态代理』呢?

思想也很简单,首先我们明确两个对象,一个是宿主(Host),一个是插件(Plugin).

一句话概述就是,用户以为自己启动的是PluginActivity,实际上启动的是HostActivity,这一步偷梁换日正是我们插件框架做的事。

首先我们实现一个HostActivity,并申明到AndroidManifest.xml中。

1
2
3
4
5
6
7
8
9
public class HostBaseActivity extends FragmentActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}

}
1
2
3
4
<activity
android:name=".HostBaseActivity"
android:screenOrientation="portrait"
android:theme="@style/PluginTheme"/>

然后我们需要定义一个『插件Activity』接口,并给一个默认的实现。目的是要求插件开发实现的Activity必须继承我们插件框架的『插件Activity』。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IPluginActivity {

public void onStart();
public void onRestart();
public void onActivityResult(int requestCode, int resultCode, Intent data);
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onCreate(Bundle savedInstanceState);
public void setProxy(Activity proxyActivity, String dexPath);
public void onSaveInstanceState(Bundle outState);
public void onNewIntent(Intent intent);
public void onRestoreInstanceState(Bundle savedInstanceState);
public boolean onTouchEvent(MotionEvent event);
public boolean onKeyUp(int keyCode, KeyEvent event);
public void onWindowAttributesChanged(LayoutParams params);
public void onWindowFocusChanged(boolean hasFocus);
public void onBackPressed();
...
}

然后我们实现IPluginActivity,并作为插件开发Activity的基类。

1
2
3
4
5
6
7
8
9
10
public class PluginBaseActivity implements IPluginActivity {

@Override
public void onCreate(Bundle savedInstanceState) {

}

...

}

看到这,大家可能明白了我们打算做的事,正如我们前面所说的一样,我们打算用HostBaseActivity去替换PluginBaseActivity。也就是HostBaseActivity『代理』PluginBaseActivity。

但是这样的话,我们遇到了一些问题,最主要的有这两点:

  1. PluginBaseActivity目前只是一个类,并没有Activity所具有的生命周期。
  2. PluginBaseActivity被HostBaseActivity代理后,是没有办法直接去加载插件中的资源的。

我们先说第一点,PluginBaseActivity如何才能具有Activity的生命周期呢?很简单,HostBaseActivity在对应的生命周期内,调用一下PluginBaseActivity对应的生命周期的方法就好啦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class HostBaseActivity extends FragmentActivity {

protected PluginBaseActivity mPluginBaseActivity;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPluginBaseActivity.onCreate(savedInstanceState);
...
}

@Override
protected void onStart() {
super.onStart();
mPluginBaseActivity.onStart();
}

...

}

这里只是最简化代码了,实际上HostBaseActivity里是需要通过ClassLoader,通过反射的方式创建一个PluginBaseActivity实例。具体的大家可以参考wangyeming/AndroidPluginFrameDemo中的代码。

其它的方法,比如onActivityResult,onRequestPermissionsResult等方法,根据需要,也可以代理实现。可以看到,我们的PluginBaseActivity从一个冷冰冰的java类,变成了有血有肉,活生生的,有生命周期的安卓Activity类。就像文章封面的图片『提线木偶』一样,PluginBaseActivity只是一个徒有其表的木偶,只有我们给它搭上线(HostBaseActivity调用PluginBaseActivity的接口),才能真正的成为可以活动的小人。

好的,现在我们在插件中可以继承PluginBaseActivity,自定义一个插件的Activity。但这里有一个严峻的问题,我们在插件PluginBaseActivity中,理所应当的可以通过setContentView()等方法加载layout,drawable,string等资源文件。但显然,这些资源是属于插件内的,也就是说,宿主内是无法直接加载这些资源。我们为什么不能直接加载插件apk里面的资源,有没有什么办法可以解决这个问题?下一篇文章,我们一起来研究下插件资源加载的问题。