Android的插件化简单实现

插件化介绍

百度百科里是这么定义插件的:「 是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统平台下,而不能脱离指定的平台单独运行。」,也就是说,插件可以提供一种动态扩展能力,使得应用程序在运行时加载原本不属于该应用的功能,并且做到动态更新和替换。

在 Android 中插件化 ,顾名思义,就是把一些核心复杂依赖度高的业务模块封装成独立的插件,然后根据不同业务需求进行不同组合,动态进行替换,可对插件进行管理、更新,后期对插件也可进行版本管理等操作。在插件化中有两个概念需要讲解下:

宿主

所谓宿主,就是需要能提供运行环境,给资源调用提供上下文环境,一般也就是我们主 APK ,要运行的应用,它作为应用的主工程所在,实现了一套插件的加载和管理的框架,插件都是依托于宿主的APK而存在的。

插件

插件可以想象成每个独立的功能模块封装为一个小的 APK ,可以通过在线配置和更新实现插件 APK 在宿主 APK 中的上线和下线,以及动态更新等功能。

那么为何要使用插件化技术,它有何优势,能给我们带来什么样好处,这里简单列举了以下几点:

  • 让用户不用重新安装 APK 就能增加原来APK没有的应用功能。
  • 按需加载不同的模块,实现灵活的功能配置。
  • 模块化、解耦合、并行开发、 65535 问题。

 

实现原理

在Android中应用插件化技术,其实也就是动态加载的过程,分为以下几步:

  • 把apk 拷贝到应用 APP 内部。
  • 加载可执行文件,更换静态资源
  • 调用具体的方法执行业务逻辑

Android 项目中,动态加载技术按照加载的可执行文件的不同大致可以分为两种:

  1. 动态加载 .so 库
  2. 动态加载 dex/jar/apk文件(现在动态加载普遍说的是这种)

“基于 ClassLoader 的动态加载 dex/jar/apk 文件”,就是我们指在 Android 中 动态加载由 Java 代码编译而来的 dex 包并执行其中的代码逻辑,这是常规 Android 开发比较少用到的一种技术,目前说的动态加载指的就是这种。

Android 项目中,所有 Java 代码都会被编译成 dex 文件,Android 应用运行时,就是通过执行 dex 文件里的业务代码逻辑来工作的。使用动态加载技术可以在 Android 应用运行时加载外部的 dex 文件,而通过网络下载新的 dex 文件并替换原有的 dex 文件就可以达到不安装新 APK 文件就升级应用(改变代码逻辑)的目的。

所以说,在 Android 中的 ClassLoader 机制主要用来加载 dex 文件,系统提供了两个 API 可供选择:

  • PathClassLoader:只能加载已经安装到 Android 系统中的 APK 文件。因此不符合插件化的需求,不作考虑。
  • DexClassLoader:支持加载外部的 APK、Jar 或者 dex 文件,正好符合文件化的需求,所有的插件化方案都是使用 DexClassloader 来加载插件 APK 中的 .class文件的。

 

代码实现

1.加载插件apk

public class PluginManager {

    private static PluginManager ourInstance = new PluginManager();
    private Context context;
    private PackageInfo pluginPackageArchiveInfo;

    private DexClassLoader pluginDexClassLoader;
    private Resources pluginResources;

    public static int FROM_IN = 0;
    public static int FROM_OUT = 1;

    public PackageInfo getPluginPackageArchiveInfo() {
        return pluginPackageArchiveInfo;
    }

    public static PluginManager getInstance() {
        return ourInstance;
    }

    private PluginManager() {
    }
    public void setContext(Context context) {
        this.context = context.getApplicationContext();
    }

    public void loadApk(String dexPath) {
        pluginDexClassLoader = new DexClassLoader(dexPath, context.getDir("dex", Context.MODE_PRIVATE).getAbsolutePath(), null, context.getClassLoader());

        pluginPackageArchiveInfo = context.getPackageManager().getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);

        AssetManager assets = null;
        try {
            assets = AssetManager.class.newInstance();
            Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assets, dexPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
        pluginResources = new Resources(assets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
//        Log.d("lyll", "pluginResources=" + pluginResources);
//        Log.d("lyll", "pluginDexClassLoader=" + pluginDexClassLoader);
    }
    
    public DexClassLoader getPluginDexClassLoader() {
        return pluginDexClassLoader;
    }

    public Resources getPluginResources() {
        return pluginResources;
    }
    

2.设置代理activity

public class ProxyActivity extends Activity {

    private PluginInterface pluginInterface;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String className = getIntent().getStringExtra("className");
        try {
            Class<?> aClass = PluginManager.getInstance().getPluginDexClassLoader().loadClass(className);
            Object newInstance = aClass.newInstance();
            if (newInstance instanceof PluginInterface) {
                pluginInterface = (PluginInterface) newInstance;
                pluginInterface.attachContext(this);
                Bundle bundle = new Bundle();
                bundle.putInt("from",PluginManager.FROM_OUT);
                pluginInterface.onCreate(bundle);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Resources getResources() {
        return PluginManager.getInstance().getPluginResources();
    }

    @Override
    public void startActivity(Intent intent) {
        Intent newIntent = new Intent(this, ProxyActivity.class);
        newIntent.putExtra("className", intent.getComponent().getClassName());
        super.startActivity(newIntent);
    }

    @Override
    public void onStart() {
        pluginInterface.onStart();
        super.onStart();
    }

    @Override
    public void onResume() {
        pluginInterface.onResume();
        super.onResume();
    }

    @Override
    public void onRestart() {
        pluginInterface.onRestart();
        super.onRestart();
    }

    @Override
    public void onDestroy() {
        pluginInterface.onDestroy();
        super.onDestroy();
    }

    @Override
    public void onStop() {
        pluginInterface.onStop();
        super.onStop();
    }

    @Override
    public void onPause() {
        pluginInterface.onPause();
        super.onPause();
    }

}

其中PluginInterface是插件activity必须实现的接口,插件app里的activity是没有自己的生命周期的,所以要人为给它一个生命周期(这里是代理activity的生命周期)


public interface PluginInterface {
    void onCreate(Bundle saveInstance);

    void setContentView(int layoutResID);

    void attachContext(Activity context);

    void onStart();

    void onResume();

    void onRestart();

    void onDestroy();

    void onStop();

    void onPause();
}

3.然后便是在主app里面将插件app加载进来

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //加载插件app的点击事件
    public void loadApk(View view) {
        //注意:使用运行时权限
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
    }
     //跳转到插件app的点击事件
    public void startApk(View view) {
        Intent intent = new Intent(this, ProxyActivity.class);
        String otherApkMainActivityName = PluginManager.getInstance().getPluginPackageArchiveInfo().activities[0].name;
        intent.putExtra("className", "com.dabaicai.lyl.otherapp.MainActivity");
        startActivity(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PluginManager.getInstance().setContext(this);
//        Log.d("lyll","path="+ Environment.getExternalStorageDirectory().getAbsolutePath()+"/otherapp-debug.apk");
        PluginManager.getInstance().loadApk(Environment.getExternalStorageDirectory().getAbsolutePath()+"/otherapp-debug.apk");
    }
}

4.接着就是插件app里的内容了,其实插件app里面什么都没有,这里就不贴代码了

5.最后别忘记在主app里将代理activity加进去,还有读写的权限

文章最后附上demo的地址:https://github.com/laiyuling424/PluginApp

美滋滋的睡觉去了

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值