插件化之DroidPlugin深度hook

     现在app体积越来越大,占的手机内存越来越多,这对于承载几十个app的手机来说无疑是个硬伤。怎么样才能做到既可以满足用户多样化需求,又不明显增大app体积呢?答案是,应用插件化技术。插件化思想概括起来:宿主与插件分离,从结构上解耦,即装即用,宿主动态加载插件,有必要时宿主开放部分接口给插件调用。众多一线互联网公司纷纷提出自家的解决方案,甚至有些个人开发者也开源网站分享了自己源码。其中,360公司基于深度hook思想实现插件化(也有开发者称之为“动态加载”)。接下来主要介绍下DroidPlugin的工作原理与实现过程。

   首先谈下核心部分:定义一个BinderHook继承Hook,重写invoke和onInstall方法。然后ContentService、InputMethodManager、LocationManager、NotificationManager、SearchManager、Sms、Telephony、WindowManager、ServiceManager等20种Service以及Manager继承BinderHook,以供后续动态加载调用。另外提供一个HookFatory工厂来安装所有的Hook。

然后是IPluginManagerImpl类,该类提供了加载插件过程的一系列步骤方法。按照处理逻辑归纳,大致如下图:

 

      还有一个进程管理服务,请主要功能为:

1、系统预定义N个进程。每个进程下有4种launchMode的activity,1个服务,一个ContentProvider。
2、每个插件可以在多个进程中运行,这由插件自己的processName属性决定。
3、插件系统最多可以同时运行N个进程,M个插件(M <= N or M >= N)。
4、多个插件运行在同一个进程中,如果他们的签名相同。(可以通过一个开关来决定。)
5、在运行第M+1个插件时,如果预定义的N个进程被占满,最低优先级的进程会被kill掉。腾出预定义的进程用来运行此插件。


  另外有一个类RunningActivities。它作用是在activity被创建时,以map形式记录四大组件信息;等到activity被销毁时,四大组件信息从map中移除。


接下来是运用DroidPlugin库,加载外部插件,加载成功后启动该插件。需要在application初始化插件帮助类:

    public void onCreate() {
        super.onCreate();
        PluginHelper.getInstance().applicationOnCreate(getBaseContext());
    }

  开启子线程,下载apk插件,下载完毕通知handler开始安装apk:

    private void downloadAPK(final String apkUrl, final String filename, final ApplicationBean app) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        if (mProgressBar.getVisibility() != View.VISIBLE) {
                            mProgressBar.setVisibility(View.VISIBLE);
                        }
                        String sdPath = Environment.getExternalStorageDirectory() + "/";
                        String mSavePath = sdPath + "PluginDownLoad";

                        File dir = new File(mSavePath);
                        boolean isMade = false;
                        if (!dir.exists())
                            isMade = dir.mkdir();
                        Log.d("PluginPresenter", isMade + "");
                        // 下载文件
                        HttpURLConnection conn = (HttpURLConnection) new URL(apkUrl).openConnection();
                        conn.setConnectTimeout(15000);
                        conn.connect();
                        InputStream is = conn.getInputStream();
                        int length = conn.getContentLength();

                        File apkFile = new File(mSavePath, filename + ".apk");
                        FileOutputStream fos = new FileOutputStream(apkFile);

                        int count = 0;
                        byte[] buffer = new byte[2048];
                        while (!mIsCancel) {
                            int read = is.read(buffer);
                            count += read;
                            // 计算进度条的当前位置
                            mProgress = (int) (((float) count / length) * 100);
                            // 更新进度条
                            mUpdateProgressHandler.sendEmptyMessage(DOWNLOADING);
                            // 下载完成
                            if (read < 0) {
                                Message msg = new Message();
                                msg.what = DOWNLOAD_FINISH;
                                msg.getData().putString("key", filename);
                                msg.getData().putSerializable("app", app);
                                mUpdateProgressHandler.sendMessage(msg);
                                break;
                            }
                            fos.write(buffer, 0, read);
                        }
                        fos.close();
                        is.close();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    mUpdateProgressHandler.sendEmptyMessage(DOWNLOAD_ERROR);
                }
            }
        }).start();

    }

安装过程中,可能会提示“安装失败,插件请求的权限太多”。这说明你没有为该插件预申请它需要的所有权限,请检查并且在manifest.xml文件声明对应权限。这是DroidPlugin不够完美之处,请理解:事物难以做到完美无缺。哪位开发者热爱钻研插件化技术的可以在此基础上改进,或者提出自己的创新解决方案。


  另外,也可能提示“宿主不支持插件的abi环境,可能宿主运行时为64位,但插件只支持32位”。这说明你的手机是64位的CPU架构,但没有为它提供加载so库的路径。此时,你需要在gradle的defultConfig里面列举abi环境:

 ndk {
        abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "mips"
     }
在工程的src—>main—>jniLibs目录下,提供对应的文件夹来加载so库:

        

  等等,如果这样还报abi宿主环境的错误。那么需要看下gradle.properties的配置了。在该文件添加:

    android.useDeprecatedNdk=true


如果进度条顺利走到100%,等待大概2-3秒后更新页面。那么恭喜您,已经安装成功。再次点击插件图标,宿主会启动该app,控制权暂时交给它接管,您可以尽情享受第三方插件带来的乐趣了。执行代码如下所示:

    public void doOpenApp(final AdapterView<?> parent, final int position, String packageName){
        final ApplicationBean applicationBean = ((DroidDragAdapter) parent.getAdapter()).getItem(position);
        if (applicationBean == null) return;
        PackageManager pm = context.getPackageManager();
        Intent intent = pm.getLaunchIntentForPackage(packageName);
        if (intent == null) {
            return;
        }
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);
    }

读到这里,也许大家有疑问:我加载过的插件,不需要了可不可以卸载掉呢?能提出这样问题的读者说明比较细心,答案是可以的。您只需长按该插件图标,弹出对话框询问:“您确定要卸载该应用吗?”点击确定后,开始卸载,实现代码如下:

    //卸载app插件
    public void doUninstall(final ApkItem item) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("提示");
        builder.setMessage("你确定要卸载" + item.title + "吗?");
        builder.setNegativeButton("卸载", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (!PluginManager.getInstance().isConnected()) {
                    Toast.makeText(context, "服务未连接", Toast.LENGTH_SHORT).show();
                } else {
                    try {
                        PluginManager.getInstance().deletePackage(item.packageInfo.packageName, 0);
                        Toast.makeText(context, "卸载完成", Toast.LENGTH_SHORT).show();
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        builder.setNeutralButton("取消", null);
        builder.show();
    }

好了,DroidPlugin加载插件与卸载插件的流程介绍完了。希望对大家有所帮助,如果有什么问题,欢迎交流或者指出。
       

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
获取场景中各点相对于摄象机的距离是计算机视觉系统的重要任务之一.场景中各点相对于摄象机的距离可以用深度图(Depth Map)来表示,即深度图中的每一个像素值表示场景中某一点与摄像机之间的距离.机器视觉系统获取场景深度图技术可分为被动测距传感和主动深度传感两大类.被动测距传感是指视觉系统接收来自场景发射或反射的光能量,形成有关场景光能量分布函数,即灰度图像,然后在这些图像的基础上恢复场景的深度信息.最一般的方法是使用两个相隔一定距离的摄像机同时获取场景图像来生成深度图.与此方法相类似的另一种方法是一个摄象机在不同空间位置上获取两幅或两幅以上图像,通过多幅图像的灰度信息和成象几何来生成深度图.深度信息还可以使用灰度图像的明暗特征、纹理特征、运动特征间接地估算.主动测距传感是指视觉系统首先向场景发射能量,然后接收场景对所发射能量的反射能量.主动测距传感系统也称为测距成象系统(Rangefinder).雷达测距系统和三角测距系统是两种最常用的两种主动测距传感系统.因此,主动测距传感和被动测距传感的主要区别在于视觉系统是否是通过增收自身发射的能量来测距。另外,我们还接触过两个概念:主动视觉和被动视觉。主动视觉是一种理论框架,与主动测距传感完全是两回事。主动视觉主要是研究通过主动地控制摄象机位置、方向、焦距、缩放、光圈、聚散度等参数,或广义地说,通过视觉和行为的结合来获得稳定的、实时的感知。我们将在最后一节介绍主动视觉。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

徐福记456

您的鼓励和肯定是我创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值