Skip to content

Instantly share code, notes, and snippets.

@xujiaji
Created May 17, 2018 06:12
Show Gist options
  • Select an option

  • Save xujiaji/998dc67ace23acaea02b1108e8f1f001 to your computer and use it in GitHub Desktop.

Select an option

Save xujiaji/998dc67ace23acaea02b1108e8f1f001 to your computer and use it in GitHub Desktop.
apk动态加载的一些方法
/**
* author: xujiaji
* created on: 2018/5/16 15:28
* description: 动态加载Apk工具类
*/
public class DynamicLoadApkUtil
{
public static class PluginBean
{
public final String label;
public final String pakName;
public final int versionCode;
public final String versionName;
public final Drawable icon;
public PluginBean(String label, String pakName)
{
this.label = label;
this.pakName = pakName;
versionCode = -1;
versionName = null;
icon = null;
}
public PluginBean(String label, String pakName, int versionCode, String versionName, Drawable icon)
{
this.label = label;
this.pakName = pakName;
this.versionCode = versionCode;
this.versionName = versionName;
this.icon = icon;
}
}
/**
* 查找手机内所有的插件
*
* @return 返回一个插件List
*/
public static List<PluginBean> findAllInstalledPlugin(Context context, String myShareUserId)
{
List<PluginBean> plugins = new ArrayList<>();
PackageManager pm = context.getPackageManager();
//通过包管理器查找所有已安装的apk文件
List<PackageInfo> packageInfoList = pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
for (PackageInfo info : packageInfoList)
{
//得到当前apk的包名
String pkgName = info.packageName;
//得到当前apk的sharedUserId
String shareUserId = info.sharedUserId;
//判断这个apk是否是我们应用程序的插件
if (shareUserId != null && shareUserId.equals(myShareUserId) && !pkgName.equals(context.getPackageName()))
{
String label = pm.getApplicationLabel(info.applicationInfo).toString();//得到插件apk的名称
PluginBean bean = new PluginBean(label, pkgName);
plugins.add(bean);
}
}
return plugins;
}
/**
* 加载已安装的apk
*
* @param packageName 应用的包名
* @param pluginContext 插件app的上下文
* @return 对应资源的id
*/
public static int getInstalledPluginDrawableResourceId(Context pluginContext, String packageName, String resName) throws Exception
{
//第一个参数为包含dex的apk或者jar的路径,第二个参数为父加载器
PathClassLoader pathClassLoader =
new PathClassLoader(pluginContext.getPackageResourcePath(), ClassLoader.getSystemClassLoader());
Class<?> clazz = pathClassLoader.loadClass(packageName + ".R$drawable");
// //参数:1、类的全名,2、是否初始化类,3、加载时使用的类加载器
// Class<?> clazz = Class.forName(packageName + ".R$drawable", true, pathClassLoader);
//使用上述两种方式都可以,这里我们得到R类中的内部类mipmap,通过它得到对应的图片id,进而给我们使用
Field field = clazz.getDeclaredField(resName);
return field.getInt(R.mipmap.class);
}
/**
* 创建插件app上下文
*
* @param pluginPkgName 插件App包名
*/
public static Context createInstalledPluginContext(Context context, String pluginPkgName) throws PackageManager.NameNotFoundException
{
return context.createPackageContext(pluginPkgName,
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE);
}
/**
* 获取未安装apk的信息
*
* @param apkFilePath apk文件的路径
*/
private PluginBean getUninstallApkInfo(Context context, String apkFilePath)
{
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageArchiveInfo(apkFilePath, PackageManager.GET_ACTIVITIES);
if (pkgInfo != null)
{
ApplicationInfo appInfo = pkgInfo.applicationInfo;
int versionCode = pkgInfo.versionCode;
String versionName = pkgInfo.versionName;//版本号
Drawable icon = pm.getApplicationIcon(appInfo);//图标
String appName = pm.getApplicationLabel(appInfo).toString();//app名称
String pkgName = appInfo.packageName;//包名
return new PluginBean(appName, pkgName, versionCode, versionName, icon);
}
return null;
}
/**
* @param apkFilePath apk文件的路径
* @return 得到对应插件的Resource对象
*/
public static Resources getUninstallPluginResources(Context context, String apkFilePath)
{
try
{
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, apkFilePath);
Resources superRes = context.getResources();
return new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
/**
* 创建dex类加载器(未安装)
*
* @param apkFilePath apk路径
*/
public static DexClassLoader createDexClassLoader(Context context, String apkFilePath)
{
File optimizedDirectoryFile = context.getDir("dex", Context.MODE_PRIVATE);//在应用安装目录下创建一个名为app_dex文件夹目录,如果已经存在则不创建
//参数:1、包含dex的apk文件或jar文件的路径,2、apk、jar解压缩生成dex存储的目录,3、本地library库目录,一般为null,4、父ClassLoader
return new DexClassLoader(apkFilePath, optimizedDirectoryFile.getPath(), null, ClassLoader.getSystemClassLoader());
}
/**
* 加载apk获得内部资源
* @param clazz 资源类class
* @param resources 资源对象
* @param resName 资源名字
*/
public static Drawable getUninstallPluginDrawable(
Class<?> clazz,
Resources resources,
String resName) throws Exception
{
//通过使用apk自己的类加载器,反射出R类中相应的内部类进而获取我们需要的资源id
Field field = clazz.getDeclaredField(resName);//得到名为one的这张图片字段
int resId = field.getInt(R.id.class);//得到图片id
return resources.getDrawable(resId);
}
/**
* 获取R$drawable的class实例
*/
public static Class<?> getUninstallPluginDrawableClass(DexClassLoader classLoader, String apkPackageName) throws Exception
{
return classLoader.loadClass(apkPackageName + ".R$drawable");
}
}
@xujiaji
Copy link
Author

xujiaji commented May 17, 2018

使用

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

//         Example of a call to a native method
        sample_text.text = NativeUtil.stringFromJNI()

        val datas = ArrayList<HashMap<String, String>>()
        val plugins = DynamicLoadApkUtil.findAllInstalledPlugin(this, "com.xujiaji.app")
        if (plugins != null && !plugins.isEmpty())
        {
            for (plugin in plugins)
            {
                val map = HashMap<String, String>()
                map["label"] = plugin.label
                map["pkg"] = plugin.pakName
                datas.add(map)
            }
            sample_text.text = datas.toString()
            showImg(datas[0]["pkg"]!!)
        } else
        {
            Toast.makeText(this, "没有找到插件,请先下载!", Toast.LENGTH_LONG).show()
        }



        val apkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absolutePath + File.separator + "plugin.apk";
        val classLoader = DynamicLoadApkUtil.createDexClassLoader(
                this,
                apkPath)

        val resources = DynamicLoadApkUtil.getUninstallPluginResources(this, apkPath)

        val drawableClass = DynamicLoadApkUtil.getUninstallPluginDrawableClass(classLoader, "com.demofamilies.test_android_plugin")

        val ic_beach = DynamicLoadApkUtil.getUninstallPluginDrawable(drawableClass, resources, "ic_beach")
        img2.setImageDrawable(ic_beach)
    }

    private fun showImg(pluginPkgName: String)
    {
        val pluginContext = DynamicLoadApkUtil.createInstalledPluginContext(this, pluginPkgName)
        val resId = DynamicLoadApkUtil.getInstalledPluginDrawableResourceId(pluginContext, pluginPkgName, "ic_beach")
        img.setImageDrawable(pluginContext.resources.getDrawable(resId))
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment