插件化:当项目需求很大时,可以拆分某些独立的业务出来单独编译成一个APK,再通过主APK去调用它,这样如果插件APK需要添加一些新的功能,可以在不修改原有的应用程序情况下,将新添加的功能植入到插件中,这就是所谓的插件化。插件化能大大的降低模块间的耦合性,有利于各模块的独立维护,加快项目的维护更新。
插件化的实现方式目前了解的方式有两种:
1:插桩式——反射
2:hook方式——动态代理
先就第一种方式来展开,第二种方式后续来讲。
插桩式需要解决一下几个问题:
(1)加载未安装的APK
(2) 资源加载
(3)代码加载
接下来就围绕这几个问题来编写代码:
**一、要解决这几个问题,首先就要根据插件apk的路径去获取到相应的DexClassLoad和Resource以及PackageInfo,首先在宿主中编写一个PluginManager **
package com.zzcm.plugin;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
/**
* @author swl
* date 2019/8/8 10:14
*/
public class PluginManager {
private static PluginManager mInstance=new PluginManager();
private DexClassLoader mClassLoader;
private Resources mResources;
private PackageInfo mPackageInfo;
public DexClassLoader getClassLoader() {
return mClassLoader;
}
public Resources getResources() {
return mResources;
}
public PackageInfo getPackageInfo(){
return mPackageInfo;
}
public void setContext(Context context) {
mContext = context;
}
/**
* @param path 插件apk的路径
*/
public void loadPath(String path){
setDexClassload(path);
setResorusce(path);
setPackageInfo(path);
}
private Context mContext;
/**
* 加载插件apk类的DexClassLoad
* @param path 插件apk的路径
*/
private void setDexClassload(String path){
File dex = mContext.getDir("dex", Context.MODE_PRIVATE);
mClassLoader = new DexClassLoader(path, dex.getAbsolutePath(), null, mContext.getClassLoader());
}
/**
* 插件apk的资源资源
* @param path 插件apk的路径
*/
private void setResorusce(String path){
try {
AssetManager manager = AssetManager.class.newInstance();
Method method = manager.getClass().getMethod("addAssetPath", String.class);
method.invoke(manager,path);
mResources = new Resources(manager, mContext.getResources().getDisplayMetrics(), mContext.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
private PluginManager(){
}
public static PluginManager getInstance(){
return mInstance;
}
/**
* 得到插件apk的activity的信息
* @param path 插件apk的路径
*/
private void setPackageInfo(String path) {
//得到packageManager来获取包信息
PackageManager packageManager = mContext.getPackageManager();
//参数一是apk的路径,参数二是希望得到的内容
mPackageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
}
}
二、因为插件apk时未安装到系统的,如何取启动它,因为android对于activity的启动必须是在AndroidManifest注册了的,所以可以用一个占坑的activity去骗过系统,接下来我们定义一套协议(插件所有activity都去实现它)和占坑的activity
定义协议PluginInterface ,把它单独搞成一个library,插件和宿主通过它交互:
package com.zzcm.replugin;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.view.View;
/**
* @author swl
* date 2019/8/8 9:30
*/
public interface PluginInterface {
//把宿主中占坑的activity传过来,所有跟上下文有关系啊都用这个activity
void attach(Activity activity);
void onCreate(Bundle savedInstanceState);
void onResume();
void onStart();
void onStop();
void onPause();
void onDestory();
void startActivity(Intent intent);
void setContentView(@LayoutRes int resId);
<T extends View> T findViewById(@IdRes int id);
}
这里只是定义了一部分,其他可以根据业务去自行定义
宿主中占坑的activity:
package com.zzcm.plugin;
import android.content.Intent;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.zzcm.replugin.PluginInterface;
import java.lang.reflect.Constructor;
public class ProxyActivity extends AppCompatActivity {
private PluginInterface mPlugin;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String className = getIntent().getStringExtra("className");
try {
Class<?> clazz = getClassLoader().loadClass(className);
Constructor<?> constructor = clazz.getConstructor(new Class[]{});
Object instance = constructor.newInstance(new Object[]{});
mPlugin = (PluginInterface) instance;
mPlugin.attach(this);
Bundle bundle=new Bundle();
mPlugin.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onStart() {
super.onStart();
mPlugin.onStart();
}
@Override
protected void onResume() {
super.onResume();
mPlugin.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPlugin.onDestory();
}
@Override
public ClassLoader getClassLoader() {
//不用系统的ClassLoader,用dexClassLoader加载
return PluginManager.getInstance().getClassLoader();
}
@Override
public Resources getResources() {
//不用系统的resources,自己实现一个resources
return PluginManager.getInstance().getResources();
}
public void startActivity(Intent intent){
String className = intent.getStringExtra("className");
Intent intent1 = new Intent(this, ProxyActivity.class);
intent1.putExtra("className", className);
super.startActivity(intent1);
}
}
三、编写插件apk
定义一个BaseActivity实现我们的协议:
package com.zzcm.thirdapp;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import com.zzcm.replugin.PluginInterface;
import java.lang.reflect.Constructor;
/**
* @author swl
* date 2019/8/8 11:57
*/
public class BaseActivity extends AppCompatActivity implements PluginInterface {
protected Activity that;
@Override
public void attach(Activity activity) {
this.that=activity;
}
@Override
public void onCreate(Bundle savedInstanceState) {
}
@Override
public void onResume() {
}
@Override
public void onStart() {
}
@Override
public void onStop() {
}
@Override
public void onPause() {
}
@Override
public void onDestory() {
}
@Override
public void setContentView(@LayoutRes int resId){
that.setContentView(resId);
}
public void startActivity(Intent intent){
Intent intent1 = new Intent();
intent1.putExtra("className",intent.getComponent().getClassName());
that.startActivity(intent1);
}
@Override
public <T extends View> T findViewById(@IdRes int id){
if (that == null) {
return super.findViewById(id);
} else {
return that.findViewById(id);
}
}
}
这里就没有去写全啦。。。
然后写了两个activity
package com.zzcm.thirdapp;
import android.app.Activity;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.zzcm.replugin.PluginInterface;
public class ThirdAppMainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_third_app_main);
//findViewById不能用自身的,切记所有有关上下文的东西都不能用本身,因为是未安装的,拿不到的
findViewById(R.id.image).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(that,SecondActivity.class);
startActivity(intent);
}
});
}
}
package com.zzcm.thirdapp;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.zzcm.replugin.PluginInterface;
public class SecondActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_second);
}
}
四、我们编译一个插件apk,在宿主中进行启动
package com.zzcm.plugin;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PluginManager.getInstance().setContext(this);
}
public void load(View view) {
loadPlugin();
}
//模拟从网络下载插件apk
private void loadPlugin() {
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "thirdapp-debug.apk");
File pluginFile = new File(getFilesDir().getAbsolutePath(), "plugin.apk");
if(pluginFile.exists()){
pluginFile.delete();
}
FileInputStream in;
FileOutputStream out;
try {
in = new FileInputStream(file);
out = new FileOutputStream(pluginFile);
int len;
byte[] bytes = new byte[1024];
while((len=in.read(bytes,0,bytes.length))!=-1){
out.write(bytes,0,len);
}
PluginManager.getInstance().loadPath(pluginFile.getAbsolutePath());
in.close();
out.close();
Toast.makeText(this,"success",Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
//启动插件apk
public void open(View view) {
Intent intent=new Intent(this,ProxyActivity.class);
ActivityInfo activity = PluginManager.getInstance().getPackageInfo().activities[0];
intent.putExtra("className",activity.name);
startActivity(intent);
}
}
最终效果:
源码下载链接