Android 插件化 02 —— 提线木偶
回顾:
上一篇通过一个小实验,验证了容器项目能够通过一些偏门的方法获取插件apk的资源,并且显示插件简单的Activity界面。
但插件作为一个独立的应用,不会像Demo那样只有一个简单的界面。每个实际应用的Activity都会涉及大量的控件、事件响应以及交互操作等等。作为插件的Container,实在没必要去关心插件功能上的具体实现细节,它只要做一个规则的制定者就行了。
就如总理说的那样:让政府的归政府,让市场的归市场……
展望:
先来看我们写Android应用的一般规律(Activity方面的):
onCreate()方法一般布局并初始化控件,添加事件监听等;
onDestory()方法一般用于释放一些资源;
onPause()、onResume()用于前后台切换响应等,Activity中几乎所有行为都与其生命周期息息相关。
如果Container掌控了插件Activity生命周期的各个方法,那么插件就会像提线木偶一样,为其所用。
也就是说,Container —— PluginActivity中,关于生命周期的方法大致应该是这样的:
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//...
//读取apk资源
loadResources(apkPath);
//...
//调用插件PluginDemoActivity对象的onCreate()
//...
}
@Override
protected void onDestroy()
{
super.onDestroy();
//调用插件PluginDemoActivity对象的onDestroy()
//...
}
@Override
protected void onPause()
{
super.onPause();
//调用插件PluginDemoActivity对象的onPause()
//...
}
@Override
protected void onResume()
{
super.onResume();
//调用插件PluginDemoActivity对象的onResume()
//...
}
@Override
protected void onStart()
{
super.onStart();
//调用插件PluginDemoActivity对象的onStart()
//...
}
@Override
protected void onStop()
{
super.onStop();
//调用插件PluginDemoActivity对象的onStop()
//...
}
涉及到的问题:
如何获得插件Activity的对象,或者说是如何加载另一个apk中的class
DexClassLoader是一个类加载器,可以用来从.jar和.apk文件中加载class。可以用来加载执行没有和应用程序一起安装的那部分代码。
public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent);
参数:
dexPath:apk路径
optimizedDirectory:解压后的.dex文件的存储路径,这个路径强烈建议使用应用程序的私有路径,不要放在sdcard上,否则代码容易被注入攻击。
libraryPath:系统库的存放路径,可以为空。
parent:父亲加载器,一般为context.getClassLoader(),使用当前上下文的类加载器。
使用,上PluginActivity的代码:
public class PluginActivity extends Activity
{
protected AssetManager mAssetManager;
protected Resources mResources;
protected Theme mTheme;
private Class<?> mPluginDemoClass;
private Object mPluginActivity;
//生命周期的每个方法
private Method method_onStart;
private Method method_onPause;
private Method method_onResume;
private Method method_onStop;
private Method method_onDestroy;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
try
{
//得到插件apk路径
String dir = Environment.getExternalStorageDirectory().toString();
String apkPath = dir + "/PluginDemo.apk"; //apk存放路径
if (!new File(apkPath).exists())
{
return;
}
//读取apk资源
loadResources(apkPath);
//得到DexClassLoader
File dexOutputDir = getDir("dex", 0);
ClassLoader localClassLoader = ClassLoader.getSystemClassLoader();
DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(), null, localClassLoader);
//动态加载插件Activity
mPluginDemoClass = loader.loadClass("com.test.plugindemo.MainActivity");
Constructor<?> localConstructor = mPluginDemoClass.getConstructor(new Class[]{});
mPluginActivity = localConstructor.newInstance(new Object[]{});
//将代理对象设置给插件Activity
//因为插件Activity对象需要使用过的资源都在当前Activity中,所以需要把容器Activity对象传过去
Method setProxy = mPluginDemoClass.getMethod("setProxy", new Class[]{Activity.class});
setProxy.setAccessible(true);
setProxy.invoke(mPluginActivity, new Object[]{this});
//存储每个生命周期的方法
method_onStart = mPluginDemoClass.getDeclaredMethod("onStart");
method_onStart.setAccessible(true);
method_onPause = mPluginDemoClass.getDeclaredMethod("onPause");
method_onPause.setAccessible(true);
method_onResume = mPluginDemoClass.getDeclaredMethod("onResume");
method_onResume.setAccessible(true);
method_onStop = mPluginDemoClass.getDeclaredMethod("onStop");
method_onStop.setAccessible(true);
method_onDestroy = mPluginDemoClass.getDeclaredMethod("onDestroy");
method_onDestroy.setAccessible(true);
//调用它的onCreate方法
Method onCreate = mPluginDemoClass.getDeclaredMethod("onCreate", new Class[]{Bundle.class});
onCreate.setAccessible(true);
onCreate.invoke(mPluginActivity, new Object[]{new Bundle()});
this.setContentView(R.layout.activity_main);
}
catch (Exception e)
{
System.out.println(e);
}
}
protected void loadResources(String apkPath)
{
try
{
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkPath);
mAssetManager = assetManager;
}
catch (Exception e)
{
e.printStackTrace();
}
Resources superRes = super.getResources();
superRes.getDisplayMetrics();
superRes.getConfiguration();
mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
mTheme = mResources.newTheme();
mTheme.setTo(super.getTheme());
}
@Override
protected void onDestroy()
{
super.onDestroy();
try
{
method_onDestroy.invoke(mPluginActivity, new Object[]{});
}
catch (Exception e)
{
System.out.println(e);
}
}
@Override
protected void onPause()
{
super.onPause();
try
{
method_onPause.invoke(mPluginActivity, new Object[]{});
}
catch (Exception e)
{
System.out.println(e);
}
}
@Override
protected void onResume()
{
super.onResume();
try
{
method_onResume.invoke(mPluginActivity, new Object[]{});
}
catch (Exception e)
{
System.out.println(e);
}
}
@Override
protected void onStart()
{
super.onStart();
try
{
method_onStart.invoke(mPluginActivity, new Object[]{});
}
catch (Exception e)
{
System.out.println(e);
}
}
@Override
protected void onStop()
{
super.onStop();
try
{
method_onStop.invoke(mPluginActivity, new Object[]{});
}
catch (Exception e)
{
System.out.println(e);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public AssetManager getAssets()
{
return mAssetManager == null ? super.getAssets() : mAssetManager;
}
@Override
public Resources getResources()
{
return mResources == null ? super.getResources() : mResources;
}
@Override
public Theme getTheme()
{
return mTheme == null ? super.getTheme() : mTheme;
}
}
容器的代码基本就这些,需要注意的是,在创建出插件的mPluginActivity对象后,需要把容器(或者说是代理)对象设置到对象中,因为所有资源都需要从这里获取。
接下来修改插件工程:
上插件主Activity的代码,为了增加点交互,我在里面增加了一个Button,点击此Button弹出一个AlertDialog
public class MainActivity extends Activity
{
private Button mButton = null;
private Activity mProxyActivity = null; //代理Activity
public void setProxy(Activity proxyActivity)
{
mProxyActivity = proxyActivity;
}
//这里需要注意的是,以下重载的方法中,不能调用super.xxxx了,不然报错,原因也很简单,
//这个Activity实质上不是真正的Activity了,没有生命周期的概念了,调用super的方法肯定报错
@Override
protected void onCreate(Bundle savedInstanceState)
{
// super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
if (v.getId() == R.id.button)
{
new AlertDialog.Builder(mProxyActivity)
.setMessage("show me the money")
.setPositiveButton("ok", null)
.show();
}
}
});
}
@Override
public void setContentView(int layoutResID)
{
// super.setContentView(layoutResID);
mProxyActivity.setContentView(layoutResID);
}
@Override
public View findViewById(int id)
{
// return super.findViewById(id);
return mProxyActivity.findViewById(id);
}
@Override
protected void onDestroy()
{
// super.onDestroy();
}
@Override
protected void onPause()
{
// super.onPause();
}
@Override
protected void onResume()
{
// super.onResume();
}
@Override
protected void onStart()
{
// super.onStart();
}
@Override
protected void onStop()
{
// super.onStop();
}
}
对于插件来说,需要非常注意的就是:使用的资源一定要是来自代理(容器)对象的。
到这里,简单的插件化就基本完成了。同上一篇一样,制作PluginDemo.apk,放到指定目录,运行Container工程,点击加载插件,成功!