插件化(一)
组件化和插件化的区别:
- 组件化是将一个app分成多个模块,每个模块都是一个组件(module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件,但是最终发布的时候将这些组件合并成一个统一的apk,这就是组件化开发。
- 插件化开发和组件化不同,插件化开发就是将整个app拆分成很多模块,每个模块都是一个apk(组件化的每个模块是一个lib),最终打包的时候将宿主apk和插件apk分开打包,插件apk通过动态下发到宿主apk,这就是插件化。
插件化的好处:
- 宿主和插件分开编译
- 是可以并发开发的。宿主和插件说白了就是apk,开发是互不影响的(只需要宿主给插件一个上下文)。
- 动态更新插件,不需要安装,下载之后就接就可以打开,
- 按需下载模块
- 可以解决方法树的爆棚问题65535
插件化的标准
是不是每个app都能运行在我们的宿主app里面呢?
- 肯定不是每个app都能运行在我们的宿主app里面的,我们必须在宿主app里面设计一套标准,让插件app满足我们这个标准才能够运行。最主要的是插件app因为没有安装,所以是没有上下文的,就需要我们的宿主将上下文传过去
- 插件化的activity也没有生命周期,所以我们的宿主也要对插件的生命周期进行管理,以及资源、layout等。
所以主要是设计一套标准,让插件app能够像安装的那样真正的运行起来
新建model 接口 以及activity 最后让宿主activity和插件都去实现。
通过插桩式来实现加载插件
下图是我简单实现的一个demo 第一个页面是宿主app的 后面试插件的页面
我们先看一下工程的结构:
其实标准里面实际上就是一个接口:
public interface ProxyActivityInterface {
//生命周期的activity
public void attach(Activity proxyActivity);
public void onCreate(Bundle savedInstanceState);
public void onStart();
public void onResume();
public void onPause();
public void onStop();
public void onDestroy();
public void onSaveInstanceState(Bundle outState);
public boolean onTouchEvent(MotionEvent event);
public void onBackPressed();
}
主要就是生命周期方法,我们定义了这些, 到时候宿主app肯定是拿到插件的对象 来调用的这些方法,从而维护了插件的生命周期
再看看我们的插件app是怎么样使用这个标准的,要接入我们的肯定首先 有一个BaseActivity 来实现我们的接口
// 这是插件的基类,所有的activity都要继承这个类,
public class BaseActivity extends Activity implements ProxyActivityInterface {
public Activity that;//这里的that 指的是我们的宿主app,因为插件是没有安装的 是没有上下文的
@Override
public void attach(Activity proxyActivity) {
that = proxyActivity;
}
@Override
public void setContentView(View view) {//最终调用宿主的activity
if (that != null) {
that.setContentView(view);
} else {
super.setContentView(view);
}
}
@Override
public void setContentView(int layoutResID) {
that.setContentView(layoutResID);
}
@Override
public View findViewById(int id) {
return that.findViewById(id);
}
@Override
public Intent getIntent() {
if (that != null) {
return that.getIntent();
}
return super.getIntent();
}
@Override
public ClassLoader getClassLoader() {
return that.getClassLoader();
}
@NonNull
@Override
public LayoutInflater getLayoutInflater() {
return that.getLayoutInflater();
}
@Override
public ApplicationInfo getApplicationInfo() {
return that.getApplicationInfo();
}
@Override
public Window getWindow() {
return that.getWindow();
}
@Override
public WindowManager getWindowManager() {
return that.getWindowManager();
}
@Override
public void startActivity(Intent intent) {
// ProxyActivity --->className
Intent m = new Intent();
m.putExtra("ClassName", intent.getComponent().getClassName());
that.startActivity(m);
}
@Override
public void onCreate(Bundle savedInstanceState) {
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
//从上面代码看出,插件里面的页面只要是和上下文有关的操作,全部都要用that,用宿主的上下文
下面是我们插件的主页面
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这里是启动第二个activity
findViewById(R.id.mBtnStart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(that, SecondActivity.class);
//这里其实调用父类的 最终会调用宿主里面的startActivity方法,下面会对其进行重写
startActivity(intent);
}
});
}
}
上面们的插件和接口以及定义好了 接下来就是好戏了 看看我们的宿主里面是怎么写的
宿主app首页的代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.mBtnLoadPlugin)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadPlugin();
}
});
findViewById(R.id.mBtnStartProxy).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startProxy();
}
});
}
/**
* 加载插件
*/
private void loadPlugin() {
HookManager.getInstance().loadPlugin(this);
Toast.makeText(this, "加载完成", Toast.LENGTH_LONG).show();
}
/**
* 跳转插件
*/
private void startProxy() {
Intent intent = new Intent(this, ProxyActivity.class);//这里就是一个占坑的activity
//这里是拿到我们加载的插件的第一个activity的全类名
intent.putExtra("ClassName", HookManager.getInstance().packageInfo.activities[0].name);
startActivity(intent);
}
Hookmanager的代码 是核心 主要加载我们的插件apk和插件资源的
public class HookManager {
private static final HookManager ourInstance = new HookManager();
private Resources resources;
private DexClassLoader loader;
public PackageInfo packageInfo;
public static HookManager getInstance() {
return ourInstance;
}
private HookManager() {
}
//用来加载插件
public void loadPlugin(Activity activity) {
// 假如这里是从网络获取的插件 我们直接从sd卡获取 然后读取到我们的cache目录
String pluginName = "plugin.apk";
File filesDir = activity.getDir("plugin", activity.MODE_PRIVATE);
String filePath = new File(filesDir, pluginName).getAbsolutePath();
File file = new File(filePath);
if (file.exists()) {
file.delete();
}
FileInputStream is = null;
FileOutputStream os = null;
//读取的目录
try {
is = new FileInputStream(new File(Environment.getExternalStorageDirectory(), pluginName));
//要输入的目录
os = new FileOutputStream(filePath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
int len = 0;
byte[] buffer = new byte[1024];
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
File f = new File(filePath);
if (f.exists()) {
Toast.makeText(activity, "dex overwrite", Toast.LENGTH_SHORT).show();
}
loadPathToPlugin(activity);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
os.close();
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void loadPathToPlugin(Activity activity) {
File filesDir = activity.getDir("plugin", activity.MODE_PRIVATE);
String name = "plugin.apk";
String path = new File(filesDir, name).getAbsolutePath();
//然后我们开始加载我们的apk 使用DexClassLoader
File dexOutDir = activity.getDir("dex", activity.MODE_PRIVATE);
loader = new DexClassLoader(path, dexOutDir.getAbsolutePath(), null, activity.getClassLoader());
//通过PackAgemanager 来获取插件的第一个activity是哪一个
PackageManager packageManager = activity.getPackageManager();
packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
//然后开始加载我们的资源 肯定要使用Resource 但是它是AssetManager创建出来的 就是AssertManager 有一个addAssertPath 这个方法 但是私有的 所有使用反射
Class<?> assetManagerClass = AssetManager.class;
try {
AssetManager assertManagerObj = (AssetManager) assetManagerClass.newInstance();
Method addAssetPathMethod = assetManagerClass.getMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
addAssetPathMethod.invoke(assertManagerObj, path);
//在创建一个Resource
resources = new Resources(assertManagerObj, activity.getResources().getDisplayMetrics(), activity.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
//对外提供插件的classLoader
public ClassLoader getClassLoader() {
return loader;
}
//插件中的Resource
public Resources getResource() {
return resources;
}
}
下面是我们的占坑的activity:
public class ProxyActivity extends AppCompatActivity {
private ProxyActivityInterface pluginObj;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在这里拿到了真实跳转的activity 拿出来 再去启动真实的activity
String className = getIntent().getStringExtra("ClassName");
//通过反射在去启动一个真实的activity 拿到Class对象
try {
Class<?> plugClass = getClassLoader().loadClass(className);
Constructor<?> pluginConstructor = plugClass.getConstructor(new Class[]{});
//因为插件的activity实现了我们的标准
pluginObj = (ProxyActivityInterface) pluginConstructor.newInstance(new Object[]{});
pluginObj.attach(this);//注入上下文
pluginObj.onCreate(new Bundle());//一定要调用onCreate
} catch (Exception e) {
if (e.getClass().getSimpleName() .equals("ClassCastException")){
//我这里是直接拿到异常判断的 ,也可的 拿到上面的plugClass对象判断有没有实现我们的接口
finish();
Toast.makeText(this,"非法页面",Toast.LENGTH_LONG).show();
return;
}
e.printStackTrace();
}
}
]
//为什么要重写这个呢 因为这个是插件内部startactivity调用的 将真正要开启的activity的类名穿过来
//然后取出来,启动我们的占坑的activity 在我们真正要启动的赛进去
@Override
public void startActivity(Intent intent) {
String className1=intent.getStringExtra("ClassName");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("ClassName", className1);
super.startActivity(intent1);
}
//重写classLoader
@Override
public ClassLoader getClassLoader() {
return HookManager.getInstance().getClassLoader();
}
//重写Resource
@Override
public Resources getResources() {
return HookManager.getInstance().getResource();
}
@Override
protected void onStart() {
super.onStart();
pluginObj.onStart();
}
@Override
protected void onDestroy() {
super.onDestroy();
pluginObj.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
pluginObj.onPause();
}
}
至此一个简单的插装式的插件化就完成了,只是完成了activity的跳转,还有service和广播。都是一样的
主要是明白插装式的原理,是通过一个占坑的组件,然后实现同一个接口,来完成生命周期的调用。
上面插件内部的跳转其实也就是在开同一个activity。
总结
- 宿主程序和插件完全独立
- 宿主程序开放部分接口提供插件与之通信
- 但是宿主程序耦合了插件的部分业务逻辑
- 通过插装式可以实现Activity、Service、BroadcastReceiver、ContentProvider 这些组件的代理