通过安装应用的方式实现app插件化
重要提醒:这种方式实现的插件化,需要在插件应用和主应用的manifest.xml中添加一个sharedUserId属性,并且属性值都要相同
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eyesee.loaddex"
android:sharedUserId="@string/sharedUserId">
1,javabean对象,保存查到的插件apk信息
public class PluginBean {
private String pkgLabel = "";
private String pkgName = "";
public PluginBean(String label,String name){
pkgLabel = label;
pkgName = name;
}
public String getPkgLabel() {
return pkgLabel;
}
public void setPkgLabel(String pkgLabel) {
this.pkgLabel = pkgLabel;
}
public String getPkgName() {
return pkgName;
}
public void setPkgName(String pkgName) {
this.pkgName = pkgName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PluginBean that = (PluginBean) o;
return Objects.equals(pkgLabel, that.pkgLabel) &&
Objects.equals(pkgName, that.pkgName);
}
@Override
public int hashCode() {
return Objects.hash(pkgLabel, pkgName);
}
@Override
public String toString() {
return "PluginBean{" +
"pkgLabel='" + pkgLabel + '\'' +
", pkgName='" + pkgName + '\'' +
'}';
}
}
2.查找插件apk信息,获取插件apk中资源文件的class对象
public class PackageUtils {
/**
* 查找手机内所有的插件
* @return 返回一个插件list
*/
public static List<PluginBean> findAllPlugin(Context context){
List<PluginBean> plugins = new ArrayList<>();
PackageManager pm = context.getPackageManager();
List<PackageInfo> packageInfos = pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
for (PackageInfo packageInfo : packageInfos) {
//apk包名
String pkgName = packageInfo.packageName;
//apk的sharedUserId
String sharedUserId = packageInfo.sharedUserId;
//判断apk是否为插件应用,通过sharedUserId,应用包名
if(sharedUserId != null && sharedUserId.equals(context.getResources().getString(R.string.sharedUserId))
&& !pkgName.equals(context.getPackageName())){
//插件apk名称
String label = pm.getApplicationLabel(packageInfo.applicationInfo).toString();
PluginBean bean = new PluginBean(label,pkgName);
plugins.add(bean);
}
}
return plugins;
}
/**
* 加载已安装的apk
* @param pkgName 插件应用包名
* @param pluginContext 插件应用上下文
* @return 对应资源的ID
*/
public static int dynamicLoadApk(String pkgName,Context pluginContext) throws Exception{
//第一个参数为包含dex的apk或jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath(),ClassLoader.getSystemClassLoader());
//方法一,通过自身的加载器反射出mipmap类,进而使用该类的资源
//Class<?> clazz = pathClassLoader.loadClass(pkgName+".R$mipmap");
//方法二 参数:1,类的全类名 2,是否初始化类 3,加载时使用的类加载器
Class<?> clazz = Class.forName(pkgName+".R$mipmap",true,pathClassLoader);
//得到插件apk中内部类mipmap,通过它得到资源ID,查找资源中名称one的图片资源
Field field = clazz.getDeclaredField("one");
int resourceId = field.getInt(R.mipmap.class);
return resourceId;
}
/**
* 图片资源的ID
*/
public static Class<?> getMipmapClazz(String pkgName,Context pluginContext) throws Exception{
//第一个参数为包含dex的apk或jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath(),ClassLoader.getSystemClassLoader());
//方法一,通过自身的加载器反射出mipmap类,进而使用该类的资源
//Class<?> clazz = pathClassLoader.loadClass(pkgName+".R$mipmap");
//方法二 参数:1,类的全类名 2,是否初始化类 3,加载时使用的类加载器
Class<?> clazz = Class.forName(pkgName+".R$mipmap",true,pathClassLoader);
return clazz;
}
/**
* string字符串资源ID
* */
public static Class<?> getStringClazz(String pkgName,Context pluginContext) throws Exception{
//第一个参数为包含dex的apk或jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader = new PathClassLoader(pluginContext.getPackageResourcePath(),ClassLoader.getSystemClassLoader());
//方法一,通过自身的加载器反射出mipmap类,进而使用该类的资源
//Class<?> clazz = pathClassLoader.loadClass(pkgName+".R$mipmap");
//方法二 参数:1,类的全类名 2,是否初始化类 3,加载时使用的类加载器
Class<?> clazz = Class.forName(pkgName+".R$string",true,pathClassLoader);
return clazz;
}
}
3.使用插件中的资源
private void checkLoadApk(Context context) {
List<PluginBean> allPlugin = PackageUtils.findAllPlugin(context);
if(allPlugin != null && !allPlugin.isEmpty()){
for (PluginBean bean : allPlugin) {
HashMap<String,String> map = new HashMap<>();
//包名
map.put("pkgName",bean.getPkgName());
datas.add(map);
}
resource(datas,context);
} else {
Toast.makeText(context,"没有找到插件,请先下载",Toast.LENGTH_SHORT).show();;
}
}
private void resource(List<HashMap<String, String>> datas,Context context) {
String pkgName = "";
if(!datas.isEmpty()){
for (HashMap<String, String> data : datas) {
pkgName = data.get("pkgName");
}
}
//为插件app创建一个上下文,仅适合获取已经安装的插件apk
try {
Context pluginContext = context.createPackageContext(pkgName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE);
int id = PackageUtils.dynamicLoadApk(pkgName, pluginContext);
drawable = pluginContext.getResources().getDrawable(id);
Class<?> stringClazz = PackageUtils.getStringClazz(pkgName, pluginContext);
//得到插件apk中内部类mipmap,通过它得到资源ID
Field field1 = stringClazz.getDeclaredField("text1");
Field field2 = stringClazz.getDeclaredField("text2");
Field field3 = stringClazz.getDeclaredField("text3");
int resourceId1 = field1.getInt(R.string.class);
int resourceId2 = field2.getInt(R.string.class);
int resourceId3 = field3.getInt(R.string.class);
text_1 = pluginContext.getResources().getString(resourceId1);
text_2 = pluginContext.getResources().getString(resourceId2);
text_3 = pluginContext.getResources().getString(resourceId3);
}catch (Exception e){
e.printStackTrace();
Log.d("cai",e.getMessage());
}
}