相关文章推荐
豁达的便当  ·  思源字体免费下载和在线预览-字体天下·  1 年前    · 
忧郁的麻辣香锅  ·  https://zhuanlan.zhihu ...·  1 年前    · 
风度翩翩的匕首  ·  是谁偷了平儿的手镯(红楼梦》·  1 年前    · 
慷慨大方的烤红薯  ·  润肤霜可以擦脸吗_润肤霜和面霜的区别_凡士林 ...·  2 年前    · 
腼腆的机器猫  ·  川普稅改與股東財富: ...·  2 年前    · 
小百科  ›  Android的动态加载插件开发者社区
intent activity android动态加载 string
曾经爱过的电影票
2 年前
作者头像
包子388321
0 篇文章

Android的动态加载插件

前往专栏
腾讯云
开发者社区
文档 意见反馈 控制台
首页
学习
活动
专区
工具
TVP
文章/答案/技术大牛
发布
首页
学习
活动
专区
工具
TVP
返回腾讯云官网
社区首页 > 专栏 > 包子的书架 > Android的动态加载插件

Android的动态加载插件

作者头像
包子388321
发布 于 2020-06-16 16:31:44
1.3K 0
发布 于 2020-06-16 16:31:44
举报

Android的动态加载插件apk

分析

动态加载主要分为加载使用插件的资源和管理插件的Activity、service、BroadcastReceiver的功能

1.插件的资源加载

我们都知道要获Res下的文件,需要用Resource对象,但是apk是未安装的,宿主并没有对应的resId,因此获取资源需要进行反编译,反编译需要对应的插件的包名,就是反编译R资源。 贴代码,举个例子: 插件管理器类

/**
 * Description:插件管理器
 * @author chenby
 * @create 2019/6/13 14: 34
public class PluginManager {
  private static final String DEX_CACHE_PATH = "dex_cache";
   * dex的缓存目录
  private String dexCachePath;
  private final HashMap<String, PluginPackage> pluginMap = new HashMap<>();
  private PluginManager() {
   * 静态内部类
  private static class SingleTonHolder {
    private static final PluginManager INSTANCE = new PluginManager();
   * 单例模式
  public static final PluginManager getInstance() {
    return PluginManager.SingleTonHolder.INSTANCE;
  private void initDexCacheDirectory(Context context) {
    File dexFile = context.getDir(DEX_CACHE_PATH, Context.MODE_PRIVATE);
    if (!dexFile.exists()) {
      dexFile.mkdir();
    dexCachePath = dexFile.getAbsolutePath();
   * 加载某个目录下的插件
  public void loadPlugins(Context context, String pluginFolder) {
    initDexCacheDirectory(context);
    File file = new File(pluginFolder);
    File[] plugins = file.listFiles();
    if (plugins == null || plugins.length == 0) {
      return;
    for (File plugin : plugins) {
      if (plugin != null) {
        loadPlugin(context, plugin.getAbsolutePath());
   * 加载对应的某个插件
   * @param context
   * @param pluginPath
  public void loadPlugin(Context context, String pluginPath) {
    PackageInfo dexPackageInfo = getPackageInfo(context, pluginPath);
    if (dexPackageInfo != null) {
      //获取插件apk的包名
      String dexPackageName = dexPackageInfo.packageName;
      //获取插件apk对应的AssertManager对象
      AssetManager dexAssertManager = getAssetManager(pluginPath);
      //获取插件apk的资源对象
      Resources dexResource = getResource(context, dexAssertManager);
      //获取插件apk的类加载器
      DexClassLoader dexClassLoader = getDexClassLoader(context, pluginPath);
      PluginPackage pluginPackage = new PluginPackage(dexPackageName, dexClassLoader,
          dexAssertManager, dexResource, dexPackageInfo);
      pluginMap.put(dexPackageName, pluginPackage);
   * 获取插件包
   * @param packageName 对应的插件包名
   * @return
  public PluginPackage getPluginPackage(String packageName) {
    return pluginMap.get(packageName);
   * 获取插件apk对应的包信息
  private PackageInfo getPackageInfo(Context context, String pluginPath) {
    //取得PackageManager引用
    PackageManager manager = context.getPackageManager();
    //通过apk包文件路径获取到这个包的信息, (检索在包归档文件中定义的应用程序包的总体信息)
    PackageInfo dexPackageArchiveInfo = manager.getPackageArchiveInfo(pluginPath,
        PackageManager.GET_ACTIVITIES);
    return dexPackageArchiveInfo;
   * 获取插件apk对应的AssetManager对象
   * @param pluginPath 插件的路径
  private AssetManager getAssetManager(String pluginPath) {
    try {
      // 创建AssetManager实例 通过反射获取AssetManager 用来加载外面的资源包
      AssetManager assetManager = AssetManager.class.newInstance();
      Class cls = AssetManager.class;
      Method method = cls.getMethod("addAssetPath", String.class);
      // 反射设置资源加载路径
      method.invoke(assetManager, pluginPath);
      return assetManager;
    } catch (Exception e) {
      e.printStackTrace();
    return null;
   * 构造出插件apk对应的Resource对象
   * @param assetManager {@link #getAssetManager(String)}
  private Resources getResource(Context context, AssetManager assetManager) {
    Resources resources = new Resources(assetManager,
        context.getResources().getDisplayMetrics(),
        context.getResources().getConfiguration());
    return resources;
   * 构造出插件apk对应的DexClassLoader对象
   * @param pluginPath 插件的路径
  private DexClassLoader getDexClassLoader(Context context, String pluginPath) {
    DexClassLoader dexClassLoader = new DexClassLoader(pluginPath, dexCachePath, null,
        context.getClassLoader());
    return dexClassLoader;
}

插件包信息的实体类

public class PluginPackage {
   * 插件的包名
  private String dexPackageName;
   * 插件的Dex的类加载器
  private DexClassLoader dexClassLoader;
   * 插件的AssetManager对象
  private AssetManager dexAssetManager;
   * 插件的资源对象
  private Resources dexResource;
   * 插件的包信息
  private PackageInfo dexPackageInfo;
  public PluginPackage(String dexPackageName, DexClassLoader dexClassLoader,
      AssetManager dexAssetManager, Resources dexResource,
      PackageInfo dexPackageInfo) {
    this.dexPackageName = dexPackageName;
    this.dexClassLoader = dexClassLoader;
    this.dexAssetManager = dexAssetManager;
    this.dexResource = dexResource;
    this.dexPackageInfo = dexPackageInfo;
  public String getDexPackageName() {
    return dexPackageName;
  public DexClassLoader getDexClassLoader() {
    return dexClassLoader;
  public AssetManager getDexAssetManager() {
    return dexAssetManager;
  public Resources getDexResource() {
    return dexResource;
  public PackageInfo getDexPackageInfo() {
    return dexPackageInfo;
}

资源管理器类 这边的资源文件采用的是className = packageName + ".R$" + type 反编译资源类

package com.jason.dyload.manager;
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 android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
 * Description:资源加载管理器(用来加载外部apk包的资源管理器)
 * @author chenby
 * @create 2019/5/30 15: 45
public class ResourceLoadManager {
  private static final String RESOURCE_TYPE_DRAWABLE = "drawable";// 图片
  private static final String RESOURCE_TYPE_STRING = "string";// 文字
  private static final String RESOURCE_TYPE_COLOR = "color";// 颜色
  private Context context;
  private Resources dexResources;
  private String dexPackageName;
  private DexClassLoader dexClassLoader;
  private ResourceLoadManager() {
   * 静态内部类
  private static class SingleTonHolder {
    private static final ResourceLoadManager INSTANCE = new ResourceLoadManager();
   * 单例模式
   * @return
  public static final ResourceLoadManager getInstance() {
    return SingleTonHolder.INSTANCE;
  public void init(Context context, PluginPackage pluginPackage) {
    this.context = context.getApplicationContext();
    dexPackageName = pluginPackage.getDexPackageName();
    dexClassLoader = pluginPackage.getDexClassLoader();
    dexResources = pluginPackage.getDexResource();
  public Resources getDexResources() {
    return dexResources;
  public DexClassLoader getDexClassLoader() {
    return dexClassLoader;
  public String getDexPackageName() {
    return dexPackageName;
   * 获取颜色
   * @param resId 资源id
  public int getColor(int resId) {
    if (dexResources == null) {
      return ContextCompat.getColor(context, resId);
    String resName = dexResources.getResourceEntryName(resId);
    int outResId = dexResources.getIdentifier(resName, RESOURCE_TYPE_COLOR, dexPackageName);
    if (outResId == 0) {
      return ContextCompat.getColor(context, resId);
    return dexResources.getColor(outResId);
   * 获取drawable资源
   * @param resName 资源名称
  public int getColor(String resName) {
    int outResId = getResourceID(dexPackageName, RESOURCE_TYPE_COLOR, resName);
    return dexResources.getColor(outResId);
   * 获取drawable资源
   * @param resId 资源id
  public Drawable getDrawable(int resId) {//获取图片
    if (dexResources == null) {
      return ContextCompat.getDrawable(context, resId);
    String resName = dexResources.getResourceEntryName(resId);
    int outResId = dexResources.getIdentifier(resName, RESOURCE_TYPE_DRAWABLE, dexPackageName);
    if (outResId == 0) {
      return ContextCompat.getDrawable(context, resId);
    return dexResources.getDrawable(outResId);
   * 获取drawable资源
   * @param resName 资源名称
  public Drawable getDrawable(String resName) {
    int outResId = getResourceID(dexPackageName, RESOURCE_TYPE_DRAWABLE, resName);
    return dexResources.getDrawable(outResId);
   * 获取未安装资源String
   * @param resId 资源id
  public String getString(int resId) {
    if (dexResources == null) {
      return context.getString(resId);
    String resName = dexResources.getResourceEntryName(resId);
    int outResId = dexResources.getIdentifier(resName, RESOURCE_TYPE_STRING, dexPackageName);
    if (outResId == 0) {
      return context.getString(resId);
    return dexResources.getString(outResId);
   * 获取drawable资源
   * @param resName 资源名称
  public String getString(String resName) {
    int outResId = getResourceID(dexPackageName, RESOURCE_TYPE_STRING, resName);
    return dexResources.getString(outResId);
   * 获取未安装资源的ID
   * @param packageName 包名
   * @param type        资源类型
   * @param fieldName   资源名
   * @return
  public int getResourceID(String packageName, String type, String fieldName) {
    int resID = 0;
    String rClassName = packageName + ".R$" + type;
    try {
      Class cls = dexClassLoader.loadClass(rClassName);
      resID = (Integer) cls.getField(fieldName).get(null);
    } catch (Exception e) {
      e.printStackTrace();
    return resID;
}

2.插件的Activity管理,这边只做了native页面的管理

定义插件和宿主共同的接口,放在单独的module,让宿主和插件的module同时引用

import android.app.Activity;
import android.os.Bundle;
 * Description:PluginInterface
 * @author chenby
 * @create 2019/5/31 11: 18
public interface PluginInterface {
  void onPluginCreate(Bundle saveInstance);
  void attachContext(Activity context);
  void onPluginStart();
  void onPluginResume();
  void onPluginRestart();
  void onPluginDestroy();
  void onPluginStop();
  void onPluginPause();
}

宿主的实现

代理Activity 用来管理插件的Activity的生命周期 , 说白了就是把一个有生命周期的空activity,套上一个没有生命周期的activity上,通过反编译的形式获取到插件activity的类对象

package com.jason.dyload;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import com.jason.dyload.manager.ResourceLoadManager;
import com.jason.pluginlib.PluginInterface;
 * Description:ProxyActivity
 * @author chenby
 * @create 2019/5/31 11: 22
public class ProxyActivity extends Activity {
  private PluginInterface pluginInterface;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //拿到要启动的Activity
    String className = getIntent().getStringExtra("className");
    //String packageName = getIntent().getStringExtra("packageName");
    try {
      //加载该Activity的字节码对象
      Class<?> aClass = ResourceLoadManager.getInstance().getDexClassLoader().loadClass(className);
      //创建该Activity的示例
      Object newInstance = aClass.newInstance();
      //程序健壮性检查
      if (newInstance instanceof PluginInterface) {
        pluginInterface = (PluginInterface) newInstance;
        //将代理Activity的实例传递给三方Activity
        pluginInterface.attachContext(this);
        //创建bundle用来与三方apk传输数据
        Bundle bundle = new Bundle();
        //调用三方Activity的onCreate,
        pluginInterface.onPluginCreate(bundle);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (InstantiationException e) {
      e.printStackTrace();
    } catch (IllegalAccessException e) {
      e.printStackTrace();
   * 注意:三方调用拿到对应加载的三方Resources
  @Override
  public Resources getResources() {
    return ResourceLoadManager.getInstance().getDexResources();
  @Override
  public void startActivity(Intent intent) {
    String targetClassName = intent.getComponent().getClassName();
    String packageName = ResourceLoadManager.getInstance().getDexPackageName();
    intent = new Intent(this, ProxyActivity.class);
    intent.putExtra("className", targetClassName);
    intent.putExtra("packageName", packageName);
    super.startActivity(intent);
  @Override
  public void onStart() {
    if(pluginInterface != null) {
      pluginInterface.onPluginStart();
    super.onStart();
  @Override
  public void onResume() {
    if(pluginInterface != null) {
      pluginInterface.onPluginResume();
    super.onResume();
  @Override
  public void onRestart() {
    if(pluginInterface != null) {
      pluginInterface.onPluginRestart();
    super.onRestart();
  @Override
  public void onDestroy() {
    if(pluginInterface != null) {
      pluginInterface.onPluginDestroy();
    super.onDestroy();
  @Override
  public void onStop() {
    if(pluginInterface != null) {
      pluginInterface.onPluginStop();
    super.onStop();
  @Override
  public void onPause() {
    if(pluginInterface != null) {
      pluginInterface.onPluginPause();
    super.onPause();
}

插件的实现

基类

public class BaseActivity extends Activity implements PluginInterface {
  protected Activity thisContext;
  @Override
  public void setContentView(int layoutResID) {
    if(thisContext != null) {
      thisContext.setContentView(layoutResID);
    }else {
      setContentView(layoutResID);
  @Override
  public void setContentView(View view) {
    if(thisContext != null) {
      thisContext.setContentView(view);
    }else {
      setContentView(view);
  @Override
  public LayoutInflater getLayoutInflater() {
    if(thisContext != null) {
      return thisContext.getLayoutInflater();
    }else {
      return super.getLayoutInflater();
  @Override
  public Window getWindow() {
    if(thisContext != null) {
      return thisContext.getWindow();
    }else {
      return super.getWindow();
  @Override
  public View findViewById(int id) {
    if(thisContext != null) {
      return thisContext.findViewById(id);
    }else {
      return super.findViewById(id);
  @Override
  public ClassLoader getClassLoader() {
    if(thisContext != null) {
      return thisContext.getClassLoader();
    }else {
      return super.getClassLoader();
  @Override
  public WindowManager getWindowManager() {
    return thisContext.getWindowManager();
  @Override
  public ApplicationInfo getApplicationInfo() {
    return thisContext.getApplicationInfo();
  @Override
  public void finish() {
    thisContext.finish();
  public void onBackPressed() {
    thisContext.onBackPressed();
  @Override
  public void startActivity(Intent intent) {
    thisContext.startActivity(intent);
  @Override
  public void onPluginCreate(Bundle saveInstance) {
  @Override
  public void attachContext(Activity context) {
    thisContext = context;
  @Override
  public void onPluginStart() {
  @Override
  public void onPluginResume() {
  @Override
  public void onPluginRestart() {
  @Override
  public void onPluginDestroy() {
  @Override
  public void onPluginStop() {
  @Override
  public void onPluginPause() {
}

实现类

public class PluginMainActivity extends BaseActivity implements View.OnClickListener {
  @Override
  public void onPluginCreate(Bundle saveInstance) {
    super.onPluginCreate(saveInstance);
    setContentView(R.layout.activity_plugin_main);
    findViewById(R.id.btn).setOnClickListener(this);
  @Override
  public void onClick(View v) {
    Intent intent = new Intent(thisContext, SecondActivity.class);
    intent.putExtra("packageName", "com.jason.plugin");
    startActivity(intent);
}

插件的第二个页面

public class SecondActivity extends BaseActivity {
    @Override
    public void onPluginCreate(Bundle saveInstance) {
        super.onPluginCreate(saveInstance);
        Toast.makeText(thisContext, "SecondActivity show", Toast.LENGTH_SHORT).show();
        setContentView(R.layout.activity_second);
        Log.i("chenby","thisContext == null: "+(thisContext == null));
}

宿主调用activity的页面和引用资源

package com.jason.dyload;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.LoaderManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.jason.dyload.manager.PluginManager;
import com.jason.dyload.manager.ResourceLoadManager;
import java.io.File;
public class MainActivity extends AppCompatActivity {
  private static int REQ_PERMISSION_CODE = 1001;
  private static final String[] PERMISSIONS = { Manifest.permission.WRITE_EXTERNAL_STORAGE };
  private TextView showTv;
  private ImageView showIv;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    showTv = findViewById(R.id.tv_show);
    showIv = findViewById(R.id.iv_show);
    checkAndRequestPermissions();
   * 权限检测以及申请
  private void checkAndRequestPermissions() {
    if (hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
      //File apkFile = new File(Environment.getExternalStorageDirectory(), "plugin.apk");
      //PluginManager.getInstance().loadPlugin(this, apkFile.getAbsolutePath());
      String pluginFolder = Environment.getExternalStorageDirectory() + "/dyLoad";
      PluginManager.getInstance().loadPlugins(this, pluginFolder);
      //初始化插件资源
      ResourceLoadManager.getInstance()
          .init(this, PluginManager.getInstance().getPluginPackage("com.jason.plugin"));
    } else {
      ActivityCompat.requestPermissions(this, PERMISSIONS, REQ_PERMISSION_CODE);
   * 权限判断
  private boolean hasPermission(String permissionName) {
    return ActivityCompat.checkSelfPermission(this, permissionName)
        == PackageManager.PERMISSION_GRANTED;
  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
      @NonNull int[] grantResults) {
    if (requestCode == REQ_PERMISSION_CODE) {
      checkAndRequestPermissions();
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  public void onLoadInternal(View view) {
    showIv.setImageResource(R.drawable.girl1);
    showTv.setText(getResources().getString(R.string.test_demo));
    showTv.setTextColor(getResources().getColor(R.color.yellow));
    showTv.setBackgroundDrawable(getResources().getDrawable(R.drawable.bg));
  public void onLoadExternal(View view) {
    ResourceLoadManager.getInstance()
        .init(this, PluginManager.getInstance().getPluginPackage("com.jason.plugin"));
    showIv.setImageDrawable(ResourceLoadManager.getInstance().getDrawable("girl"));
    showTv.setText(ResourceLoadManager.getInstance().getString("test_one"));
    showTv.setTextColor(ResourceLoadManager.getInstance().getColor("black"));
    showTv.setBackgroundDrawable(ResourceLoadManager.getInstance().getDrawable("bg"));
  public void onLoadExternalPage(View view) {
    ResourceLoadManager.getInstance()
        .init(this, PluginManager.getInstance().getPluginPackage("com.jason.plugin"));
    Intent intent = new Intent(this, ProxyActivity.class);
    String packageName = "com.jason.plugin";
    String otherApkMainActivityName = PluginManager.getInstance()
        .getPluginPackage(packageName)
        .getDexPackageInfo().activities[0].name;
    intent.putExtra("className", otherApkMainActivityName);
    intent.putExtra("packageName", packageName);
    startActivity(intent);
  public void onLoadExternal2Page(View view) {
    ResourceLoadManager.getInstance()
        .init(this, PluginManager.getInstance().getPluginPackage("com.jason.plugin2"));
    Intent intent = new Intent(this, ProxyActivity2.class);
    String packageName = "com.jason.plugin2";
    String otherApkMainActivityName = PluginManager.getInstance()
        .getPluginPackage(packageName)
        .getDexPackageInfo().activities[0].name;
    intent.putExtra("className", otherApkMainActivityName);
 
推荐文章
豁达的便当  ·  思源字体免费下载和在线预览-字体天下
1 年前
忧郁的麻辣香锅  ·  https://zhuanlan.zhihu.com/p/378695089
1 年前
风度翩翩的匕首  ·  是谁偷了平儿的手镯(红楼梦》
1 年前
慷慨大方的烤红薯  ·  润肤霜可以擦脸吗_润肤霜和面霜的区别_凡士林倍护润肤霜可以擦脸 ...
2 年前
腼腆的机器猫  ·  川普稅改與股東財富: 考量股票回購與企業社會責任效果__臺灣博碩士 ...
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
小百科 - 百科知识指南
© 2024 ~ 沪ICP备11025650号