一、插件化的来由
随着项目的增大,很容易出现65536/64k的问题,同时为了让多个APP可以并发的开发,插件化就应用而生。
将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk,最终打包的时候将宿主apk和插件apk联合打包。
插件化开发总的来说有以下几点好处:
1、宿主和插件分开编译
2、并发开发
3、动态更新插件
4、按需下载模块
5、方法数或变量数爆棚,突破65536的限制
二、类加载器
Android系统中有两个类加载器分别为PathClassLoader和DexclassLoader。
PathClassLoader:只能加载系统中已经安装过的apk
DexClassLoader:可以加载jar/apk/dex,可以从SD卡中加载未安装的apk
DexClassLoader (String dexPath, String dexOutputDir, String libPath, ClassLoader parent)
dexPath:目标所在的apk或者jar文件的路径,装载器将从路径中寻找指定的目标类。
dexOutputDir:dex文件存放的路径,需要内部路径,比如context.getFilesDir().getAbsolutePath()
libPath:动态库路径(将被添加到app动态库搜索路径列表中)
parent:该装载器的父装载器,一般为当前执行类的装载器 this.getClass().getClassLoader()
三、动态加载资源
由于插件apk没有安装,所以在调用插件的上下文、资源的时候都是没法调用的。
资源的加载是通过AssetManager内的一个方法addAssetPath(String path)。该方法是hide的,不能直接调用。那么对于资源加载就可以使用反射的方式来获取。
1、重写getResources()方法,会回调多次
2、使用反射调用addAssetPath方法
3、解决插件资源与宿主资源的处突
如果使用到的资源,插件和宿主都同时存在,则使用插件的资源;
如果使用到的资源只有插件有,则使用插件的;
如果使用到的资源只有宿主有的,则使用宿主的。
public void setResources(Context context, String path) {
AssetManager assetManager = null;
try {
assetManager = AssetManager.class.newInstance();
Method assetMethod = AssetManager.class.getMethod("addAssetPath",String.class);
assetMethod.invoke(assetManager,path);
} catch (Exception e) {
e.printStackTrace();
}
resources = new Resources(assetManager,context.getResources().getDisplayMetrics(),context.getResources().getConfiguration());
}
四、实现一个简单插件化案例
1、创建一个library项目 ,包含一个接口
public interface AppInterface {
void setContentView(int layoutResID);
<T extends View> T findViewById(int id);
void onCreate(Bundle savedInstanceState);
void onStart();
void onResume();
void onDestroy();
void onPause();
void onSaveInstanceState(Bundle outState);
/**
* 需要宿主app注入给插件app上下文
*/
void attach(Activity activity);
}
2、创建一个插件项目,引用library项目
新建一个BaseActivity基类(实现AppInterface接口),该项目中所有的Activity都要继承该基类
public class BaseActivity extends AppCompatActivity implements AppInterface {
private Activity that;
@Override
public void attach( Activity activity) {
//上下文注入进来了
this.that = activity;
}
public void onCreate(@Nullable Bundle savedInstanceState) {
if (that == null) {
super.onCreate(savedInstanceState);
} else {
}
}
@Override
public void setContentView(int layoutResID) {
if (that == null) {
super.setContentView(layoutResID);
}else{
that.setContentView(layoutResID);
}
}
@Override
public <T extends View> T findViewById(int id) {
if (that == null) {
return super.findViewById(id);
}else{
return that.findViewById(id);
}
}
//重写startActivity实现页面间的跳转
@Override
public void startActivity(Intent intent) {
if(that != null) {
//ProxyActivity --->className
Intent m = new Intent();
//传入要跳转的activity的类名,以便使用反射来获取相应的类
m.putExtra("className", intent.getComponent().getClassName());
that.startActivity(m);
}else {
super.startActivity(intent);
}
}
//重写startService实现activity调用service
@Override
public ComponentName startService(Intent service) {
if(that != null) {
Intent m = new Intent();
m.putExtra("serviceName", service.getComponent().getClassName());
return that.startService(m);
}
return super.startService(service);
}
@Override
public void onSaveInstanceState(Bundle outState) {
}
@Override
public ClassLoader getClassLoader() {
if (that == null) {
return super.getClassLoader();
} else {
return that.getClassLoader();
}
}
@Override
public LayoutInflater getLayoutInflater() {
if (that == null) {
return super.getLayoutInflater();
} else {
return that.getLayoutInflater();
}
}
@Override
public WindowManager getWindowManager() {
if (that == null) {
return super.getWindowManager();
} else {
return that.getWindowManager();
}
}
@Override
public Window getWindow() {
if (that == null) {
return super.getWindow();
} else {
return that.getWindow();
}
}
public void onStart() {
if (that == null) {
super.onStart();
} else {
}
}
public void onDestroy() {
if (that == null) {
super.onDestroy();
} else {
}
}
public void onPause() {
if (that == null) {
super.onPause();
} else {
}
}
public void onResume() {
if (that == null) {
super.onResume();
} else {
}
}
}
创建两个个Activity用于测试跳转,继承该基类,用于测试
public class MainActivity extends BaseActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView img = (ImageView) findViewById(R.id.img);
img.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//由于插件apk没有被安装,所以需要使用代理类的上下文,即that
//Toast.makeText(that, "点击啦", Toast.LENGTH_SHORT).show();
startActivity(new Intent(that,TestActivity.class));
}
});
}
}
public class TestActivity extends BaseActivity{
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
3、创建一个主项目,引用library项目
由于由于插件apk没有被安装,没有办法在清单文件中注册,所以使用代理Acitivity的方式来完成插件apk的调用。代理Acitivity需要继承Activity而不能是AppCompatActivity
<1> 创建一个加载插件apk的类,用于获取apk的信息
public class PluginManager {
//-------1:构建单例类start--------
private static PluginManager instance = new PluginManager();
public static PluginManager getInstance() {
return instance;
}
private Context context;
//插件apk的入口类名
private String entryName;
//-------3:构造classLoader start-------------
private DexClassLoader dexClassLoader;
//-------4:构造resources start--------
private Resources resources;
public void setContext(Context context) {
this.context = context.getApplicationContext();
}
public void loadPath(String path) {
setEntryName(path);
setClassLoader(path);
setResources(path);
}
//-------2:获取插件app入口activity name start--------
private void setEntryName(String path) {
//得到packageManager来获取包信息
PackageManager packageManager = context.getPackageManager();
//参数一是apk的路径,参数二是希望得到的内容
PackageInfo packageInfo = null;
try {
packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
} catch (Exception e) {
e.printStackTrace();
}
//得到插件app的入口activity名称
entryName = packageInfo.activities[0].name;
}
public String getEntryName() {
return entryName;
}
private void setClassLoader(String path) {
//dex的缓存路径,必须是内部存储路径
File dexOutFile = context.getDir("dex", Context.MODE_PRIVATE);
dexClassLoader = new DexClassLoader(path, dexOutFile.getAbsoluteFile().getAbsolutePath(), null, context.getClassLoader());
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public Resources getResources() {
return resources;
}
public void setResources(String path) {
//由于构建resources必须要传入AssetManager,这里先构建一个AssetManager
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, path);
resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}
}
}
<2> 创建代理Activity,并在清单文件中注册该Activity
public class ProxyActivity extends Activity {
/**
* 要跳转的activity的name
*/
private String className = "";
private AppInterface appInterface = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* step1:得到插件app的activity的className
*/
className = getIntent().getStringExtra("className");
/**
* step2:通过反射拿到class,
* 但不能用以下方式
* classLoader.loadClass(className)
* Class.forName(className)
* 因为插件app没有被安装!
* 这里我们调用我们重写过多classLoader
*/
try {
Class activityClass = getClassLoader().loadClass(className);
Constructor constructor = activityClass.getConstructor();
Object instance = constructor.newInstance();
//将获取的实例转为AppInterface
appInterface = (AppInterface) instance;
appInterface.attach(this);
Bundle bundle = new Bundle();
appInterface.onCreate(bundle);
} catch (Exception e) {
e.printStackTrace();
}
}
@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);
}
/**
* 插件apk中调用startService时,相当于调用传入的Activity context.startService方法,这
* 里重写这个方法,相当于绑定一个ProxyService代理插件中的Service,在ProxyService的生命
* 周期方法中调用被代理对象的生命周期方法。
* @param service
* @return
*/
@Override
public ComponentName startService(Intent service) {
String serviceName = service.getStringExtra("serviceName");
Intent intent1 = new Intent(this, ProxyService.class);
intent1.putExtra("serviceName", serviceName);
return super.startService(intent1);
}
/**
* 插件apk中调用bindService时,相当于调用传入的Activity context.bindService方法,这
* 里重写这个方法,相当于绑定一个ProxyService代理插件中的Service,在ProxyService的生命
* 周期方法中调用被代理对象的生命周期方法。
* @param service
* @param conn
* @param flags
* @return
*/
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
String serviceName = service.getStringExtra("serviceName");
Intent intent1 = new Intent(this, ProxyService.class);
intent1.putExtra("serviceName", serviceName);
return super.bindService(intent1, conn, flags);
}
@Override
protected void onStart() {
super.onStart();
appInterface.onStart();
}
@Override
protected void onResume() {
super.onResume();
appInterface.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
appInterface.onDestroy();
}
@Override
public ClassLoader getClassLoader() {
//不用系统的ClassLoader,用dexClassLoader加载
return PluginManager.getInstance().getDexClassLoader();
}
@Override
public Resources getResources() {
//不用系统的resources,自己实现一个resources
return PluginManager.getInstance().getResources();
}
}
在ProxyActivity的所有生命周期方法中都必须要调用被代理类对应的生命周期方法,这样就实现了对被代理类生命周期的控制。
<3> 创建测试类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.click);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
click();
}
});
PluginManager.getInstance().setContext(this);
}
private void click() {
load();
/**
* 点击跳往插件app的activity,一律跳转到PRoxyActivity
*/
Intent intent = new Intent(this, ProxyActivity.class);
intent.putExtra("className", PluginManager.getInstance().getEntryName());
startActivity(intent);
}
//加载插件apk
private void load() {
/**
* 事先放置到SD卡根目录的plugin.apk
* 现实场景中是有服务端下发
*/
File file = new File(Environment.getExternalStorageDirectory().getPath(), "myplugin-debug.apk");
PluginManager.getInstance().loadPath(file.getAbsoluteFile().getPath());
}
}