IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 一、android 占位式插件化开发 activity的通讯 -> 正文阅读

[移动开发]一、android 占位式插件化开发 activity的通讯

1.全部代码

git源码

2.步骤理解

1.占位式插件化理解

1.感觉上就是有运行的apk(平时运行到手机上的apk)跳转到无运行的apk(只有打包出来,都是没有运行到手机上)。这个无运行的apk叫做插件(就是无法执行startActivity,这样就无法实现跳转了,直接跳转就报错了,怎么办呢?)。在实际开发中这个插件(无运行apk)由服务器下载到指定位置,然后app呢就去拿apk,进行更新(判断有没有,这里可能是个数字,我们根据数字与之前拿到的数字进行对比,如果大就更新,既删除然后下载),然后加载里面的类,资源(layout),实现跳转。

在这里插入图片描述
在这里插入图片描述

2.无宿主环境的插件怎么实现跳转?其实就是通过代理的形式来让它有运行环境。这里再创建一个代理的activity,让它去加载插件的activity,并且给它环境,既就是MainActivity ---->ProxyActivity-------->ProxyActivity,ProxyActivity-------->ProxyActivity表示内部跳转。

在这里插入图片描述

2.宿主App MainActivity代码

1.加载插件

其实加载插件就是根据插件apk来加载class和资源(layout等)
创建PluginManager类,是个单例(快捷方式创建),传入参数Activity
加载之前肯定要找到apk的路径,没apk,加载个锤子class,和资源。apk路经如下位置(这个要自己build然后获取插件的apk,别搞到主apk去了)
在这里插入图片描述
PluginManager是个单例,构造方法传入一个参数Context

//加载插件
PluginManager.getInstance(this).loadPlugin();
public void loadPlugin(){
       //获取apk的路径 /storage/emulated/0/p.apk
       File file = new File(Environment.getExternalStorageDirectory() + File.separator + "p.apk");
       //判断文件是否为空
       if (!file.exists()) {
           Log.d(TAG, "loadPlugin: 插件不存在");
           return;
       }
       String pluginPaht = file.getAbsolutePath();// /storage/emulated/0/p.apk
       //加载类
       loadClass(pluginPaht);
       //加载布局
       loadLayout(pluginPaht);
   }
  1. 加载class
//加载类
       loadClass(pluginPaht);

方法
DexClassLoader类来加载类:
第一个参数是apk路径
第二个参数是:缓存目录(应该是用来保存解压的dex文件的目录吧)
第三个参数是:不知道干嘛的,为空就可以了,好像跟C有关
第四个参数是:一般为context.getClassLoader(),父亲加载器

/**
     * 根据apk的路径来进行加载类
     * @param pluginPaht apk的路径 /storage/emulated/0/p.apk
     */
    private void loadClass(String pluginPaht) {
        /**
         * dexClassLoader需要一个缓存目录,最好用应用私有的,不然会被攻击
         * /data/data/当前应用的包名/pDir
         */
        File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
        dexClassLoader = new DexClassLoader(pluginPaht,  fileDir.getAbsolutePath(), null, context.getClassLoader());
    }
  1. 加载layout
//加载布局
       loadLayout(pluginPaht)
/**
     * 根据apk的路径来进行加载布局资源
     * @param pluginPaht apk的路径 /storage/emulated/0/p.apk
     */
    private void loadLayout(String pluginPaht) {
        try {
            //加载资源
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);//他是类类型Class
            addAssetPath.invoke(assetManager,pluginPaht);//插件包路径

            //宿主的资源配置
            Resources r = context.getResources();
            //特殊的resources 加载插件里面的资源 Resources
            //参数2,3 资源配置信息
            resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
2.启动插件里面的activity(实际是跳转到运行activity的代理activity,然后让他来加载代码),这里要传入值,这值跟跳转到哪个插件有直接关系

startPluginActivity是一个按钮的监听

//启动插件里面的Activity
    public void startPluginActivity(View view) {
        //占位   代理Activity

        //获取apk的绝对路径 //  sdcard/p.apk
        File file = new File(Environment.getExternalStorageDirectory(), "p.apk");
        String path = file.getAbsolutePath();
        //获取插件包 里面的Activity
        PackageManager packageManager = getPackageManager();
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
        ActivityInfo activityInfo = packageInfo.activities[0];//插件里的第一个启动的Activity
        Log.d(TAG, "startPluginActivity: "+activityInfo.name);
        //跳转到宿主的代理activity,并且传入要跳转到的插件名
        Intent intent = new Intent(this, ProxyActivity.class);
        intent.putExtra("className",activityInfo.name);
        startActivity(intent);
    }

3.宿主的Proxyactivity,这个被作为插件的activity,然后他去加载插件的代码,这样就是传说中的插件跳转(其实它还是在运行apk里面进行跳转,不同的是内容由插件apk决定)

1.首先肯定得有插件的类和资源吧

这里PluginManager类有对外提供获取方法,而这两个就是apk获取的类与资源
在这里插入图片描述

/**
     * 获取资源方法
     * @return 插件apk的布局资源
     */
    @Override
    public Resources getResources() {
        return PluginManager.getInstance(this).getResources();
    }

    /**
     * 获取类的方法
     * @return 插件apk的类
     */
    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance(this).getDexClassLoader();
    }
2.有类与资源了,然后再加载。根据包名来加载类。思路:根据包名—>加载大概类—>获取构造方法—>获取详细类—>强转为父接口,传this,传bundle
//真正加载插件里面的activity   com.netease.plugin_package.PluginActivity
        String className = getIntent().getStringExtra("className");
        try{
            //根据包名加载大概的类
            Class mPluginActivityClass = getClassLoader().loadClass(className);
            //根据大概类获取构造方法
            Constructor constructor = mPluginActivityClass.getConstructor(new Class[]{});
            //根据构造方法来获取具体的类
            Object mPluginActivity = constructor.newInstance(new Object[]{});
            //强制转换为父类的实现接口
            ActivityInterface activityInterface= (ActivityInterface) mPluginActivity;
            //向插件注入宿主环境
            activityInterface.insertAppContext(this);

            Bundle bundle = new Bundle();
            bundle.putString("appName", "我是宿主传递过来的信息");

            // 执行插件里面的onCreate方法
            activityInterface.onCreate(bundle);

        }catch (Exception e){
            e.printStackTrace();
        }
3.ProxyActivity重写startActivity方法
  1. 为什么要重写呢?因为要实现插件里,activity之间的跳转,这个可不是普通的跳转
    主要是自己跳转到自己,既ProxyActivity跳转到ProxyActivity。然后传入一个包名,用于跳转到哪个插件的Activity。
@Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");

        Intent proxyIntent = new Intent(this, ProxyActivity.class);
        proxyIntent.putExtra("className",className);//包名+TestActivity
        super.startActivity(proxyIntent);
    }

4.ActivityInterface类,其实就是一个公共的Activity接口,他在创建的stander包里,用于传Activity(Context)。插件包和主包都要添加这个依赖module

public interface ActivityInterface {

    void onCreate(Bundle savedInstanceState);

    void onStart();

    void onResume();

    void onDestroy();

    /**
     * 作用:把宿主(app)的环境 给插件
     * @param appActivity
     */
    void insertAppContext(Activity appActivity);
}

在这里插入图片描述

5.插件里的baseActivity类,都是调用传过来的activity里面的方法,这个activity就是代理ProxyActivity。

/**
 * 创建日期:2021/9/28 9:18
 *
 * @author tony.sun
 * 类说明:
 */

public class BaseActivity extends Activity implements ActivityInterface {
    public Activity appActivity;

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    /**
     * 获取对应的上下文,这里是proxyActivity
     * @param appActivity
     */
    @Override
    public void insertAppContext(Activity appActivity) {
        this.appActivity=appActivity;
    }

    /**
     * 找id
     * @param id
     * @return
     */
    public View findViewById(int id){
        return appActivity.findViewById(id);
    }

    /**
     * 跳转
     * @param intent
     */
    public void startActivity(Intent intent){
        Intent intentNew = new Intent();
        intentNew.putExtra("className",intent.getComponent().getClassName());//根据包名跳转
        appActivity.startActivity(intentNew);//这里是调用ProxyActivity里的startActivity
    }

    /**
     * 设置布局resId
     * @param resId
     */
    public void setContentView(int resId) {
        appActivity.setContentView(resId);//这里是调用ProxyActivity里的setContentView
    }
}

6.PluginActivity插件里的类

onCreate,setContentView,findViewById,startActivity这调用的是自定义的onCreate类的方法,不是activity里面的。

public class PluginActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //this会报错,因为它没有宿主条件
        Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();
        findViewById(R.id.bt_start_activity).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(appActivity,TestActivity.class));
            }
        });
    }
}

3.总结反思

1.申请存取权限

2.插件apk的位置要存放对且要build好

在这里插入图片描述

3.

4.

5.

6.

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-09 16:23:57  更:2021-10-09 16:26:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:36:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码