<Excerpt in index | 首页摘要>
Android 插件化-01
如何加载apk中的资源
<The rest of contents | 余下全文>
概述
当项目庞大到一定的底部,
往往涉及到一定的并行开发。
比较成熟的解决思路有两种:
1:插件化、组件化开发。
2:h5开发
我希望选择的方向是插件化组件化开发。(因为h5的流畅度还是有待提升)
这不是一篇博客能说完的。本篇主要讲如何加载其他apk中的资源文件。
需要了解的预备知识
1、ClassLoader
2、reflect
如何加载###
严格来说会碰到两种apk。(已经安装到了手机上apk,和没有安装到apk上的手机)
同样的Android提供给了我们两种ClassLoader。
1:PathClassLoader(用于已经安装完成的apk)
2:DexClassLoader(用于没有安装的apk)
预备,走
首先,创建一个项目。
项目本身包含一个model->app
我们在创建一个model->plugin(Phone类型)
然后在两个model的AndroidManifest的根节点都加入shareUserId属性
如下:
1 2 3
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.gloomyer.dex" android:sharedUserId="com.gloomyer.myapp">
|
两个model的包名用不同!!但是这个shareUserId必须一致!
关于shareUserId
然后在plugin的mipmap中放入一张图片
然后将plugin运行到我们的手机上。
加载已经安装的apk资源
切换至appmodel下
首先创建一个pojo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class Plugin { public String packName; public String name;
public Plugin(String name, String packName) { this.name = name; this.packName = packName; }
public Plugin() { } }
|
然后我们创建一个PluginManager类
添加如下方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public static List<Plugin> findAllInstalledPlugin(Context mContext) { List<Plugin> plugins = new ArrayList<>(); List<PackageInfo> installedPackages = mContext .getPackageManager() .getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES); for (PackageInfo info : installedPackages) { String packName = info.packageName;
String shareUserId = info.sharedUserId; if (shareUserId != null && "com.gloomyer.myapp".equals(shareUserId) && !mContext.getPackageName().equals(packName)) { String name = mContext .getPackageManager() .getApplicationLabel(info.applicationInfo).toString(); plugins.add(new Plugin(name, packName)); } } return plugins; }
|
通过上面的方法,我们找到了当前安装在手机上的apk.
现在开始利用classloader加载apk并且反射获取图片id!
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public static int dynamicLoadApk(String packName, Context pluginContext, String name) throws Exception { PathClassLoader loader = new PathClassLoader(pluginContext.getPackageResourcePath(), ClassLoader.getSystemClassLoader()); Class<?> clazz = Class.forName(packName + ".R$mipmap", true, loader); Field fideld = clazz.getDeclaredField(name); return fideld.getInt(R.mipmap.class); }
|
然后回到MainActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView mImageView = (ImageView) findViewById(R.id.mImageView); List<Plugin> plugins = PluginManager.findAllInstalledPlugin(this); if (plugins != null && !plugins.isEmpty()) { Plugin plugin = plugins.get(0); Context pluginContext = createPackageContext(plugin.packName, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE); int id = PluginManager.dynamicLoadApk(plugin.packName, pluginContext, "meinv");/ mImageView.setImageDrawable( pluginContext.getResources().getDrawable(id)); }else{ Toast.makeText(this, "请先安装插件!", Toast.LENGTH_SHORT).show(); } }
|
加载未安装apk的资源
首先,卸载之前安装的plugin的app
然后找到plugin的debugapk
在plugin model的 build/outputs/apk/app-debug.apk
然后在这里打开命令行
输入
1 2
| adb push app-debug.apk sdcard/plugin.apk //上传apk到手机sdcard重命名为plugin.apk
|
还是创建一个PluginManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static Plugin findSdCardApk(Context mContext) { PackageManager pm = mContext.getPackageManager(); PackageInfo packageInfo = pm.getPackageArchiveInfo( new File(Environment.getExternalStorageDirectory(), "plugin.apk").getAbsolutePath() , PackageManager.GET_ACTIVITIES); if (packageInfo != null) { String packageName = packageInfo.packageName; String shareUserId = packageInfo.sharedUserId; if ("com.gloomyer.myapp".equals(shareUserId) && !mContext.getPackageName().equals(packageName)) { String name = pm.getApplicationLabel(packageInfo.applicationInfo).toString(); return new Plugin(name, packageName); } } return null; }
|
这里和安装过的apk有些不一样。
安装过的apk我们利用createContext可以创建出它的上下文然后获取资源。
但是没安装过的不行。
所以我们需要手动去创建一个Resources,
然后利用AssetManager的addAssetPath方法,将apk上下文环境添加进运行环境
然后利用这个Resources来读取资源
在PluginManager中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static Resources getUninstallApkRes(Context mContext, Plugin plugin) throws Exception { AssetManager am = AssetManager.class.newInstance(); Method method = AssetManager.class.getMethod("addAssetPath", String.class); method.invoke(am, new File(Environment.getExternalStorageDirectory(), "plugin.apk").getAbsolutePath()); Resources superRes = mContext.getResources(); Resources mResources = new Resources(am, superRes.getDisplayMetrics(), superRes.getConfiguration()); return mResources; }
|
获取了Resources对象,然后我们继续反射获取id。
PluginManager中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static int dynamicLoadApk(Context mContext, Plugin plugin, String name) throws Exception { File outputFile = mContext.getDir("dex", Context.MODE_PRIVATE); File apkFile = new File(Environment.getExternalStorageDirectory(), "plugin.apk"); DexClassLoader loader = new DexClassLoader(apkFile.getAbsolutePath(), outputFile.getAbsolutePath(), null, ClassLoader.getSystemClassLoader()); Class<?> clazz = Class.forName(plugin.packName + ".R$mipmap", true, loader); Field field = clazz.getDeclaredField(name); return field.getInt(R.mipmap.class); }
|
然后回到ManActivity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageView mImageView = (ImageView) findViewById(R.id.mImageView); Plugin plugin = PluginManager.findSdCardApk(this); if (plugin != null) { Resources uninstallApkRes = PluginManager .getUninstallApkRes(MainActivity.this, plugin); int id = PluginManager.dynamicLoadApk(MainActivity.this, plugin, "meinv"); mImageView.setImageDrawable( pluginContext.getResources().getDrawable(id)); }else{ Toast.makeText(this, "请先安装插件!", Toast.LENGTH_SHORT).show(); } }
|
End