Android 插件化框架DroidPlugin

上一次项目迭代中,接触到了插件化框架。

使用场景:我们的app需要集成某一直播app。即在不安装第三方直播app到手机的情况下,点击我们app内部的某一连接能跳转到直播app中,运行里面原有的所有功能。

原理:也就是通过插件化框架,把第三方直播app的apk文件包下载到手机本地,然后在我们的app中,使用插件化框架解压第三方app的代码到我们app的安装目录下,使用classloader加载字节码到另一个进程中,与我们的app进程进行通讯。或许不太对,是个大概的思路。总之,好处就是,不用强制安装第三方app到手机桌面上,可使用第三方app的大部分功能,下载时会一起卸载第三方app的所有文件。

360插件化框架DroidPlugin地址

下面开始集成步骤:

一、下载插件代码,把library以module的形式加入到项目中:

解压下载 的源码,复制..\DroidPlugin-master\project\Libraries\DroidPlugin目录到项目的根目录,如图:

Android 插件化框架DroidPlugin_第1张图片

在主工程的settings.gradle文件中添加DroidPlugin库,如图:

Android 插件化框架DroidPlugin_第2张图片

编译程序。


二、修改插件内部配置:

如果上一步编译不通过,很正常,可能有些地方需要修改。先说插件内部:

Android 插件化框架DroidPlugin_第3张图片

把图中这些lib统一一下名称,都改成libs吧。

Android 插件化框架DroidPlugin_第4张图片

还是刚才那个文件,改成你的包名。现在应该可以编译通过了。

将插件中DroidPlugin/AndroidManifest.xml中的所有provider对应的authorities修改成自己的,其实改成如下即可:

android:authorities="${authorityName}_P01"  , 使用${authorityName}变量,去取build.gradle中的值,便于迁移到其他项目中


并且修改PluginManager.STUB_AUTHORITY_NAME 为你的值:
PluginManager.STUB_AUTHORITY_NAME="com.example.droidplugin_stub"

如果发现程序中是这样的,就不用改了:

public static final String STUB_AUTHORITY_NAME = BuildConfig.AUTHORITY_NAME;


三、修改项目配置:

在自定义的application中相应的方法添加插件初始化代码:

@Override
 public void onCreate() {
     super.onCreate();
     //这里必须在super.onCreate方法之后,顺序不能变
     PluginHelper.getInstance().applicationOnCreate(getBaseContext());
 }
   
 @Override
 protected void attachBaseContext(Context base) {
     PluginHelper.getInstance().applicationAttachBaseContext(base);
     super.attachBaseContext(base);
 }




差不多了。


四、功能代码(官网原话):

1、安装、更新插件,使用如下方法:


 int PluginManager.getInstance().installPackage(String filepath, int flags)
说明:安装插件到插件系统中,filepath为插件apk路径,flags可以设置为0,如果要更新插件,则设置为PackageManagerCompat.INSTALL_REPLACE_EXISTING返回值及其含义请参见PackageManagerCompat类中的相关字段。


2、卸载插件,使用如下方法:


 int PluginManager.getInstance().deletePackage(String packageName,int flags);
说明:从插件系统中卸载某个插件,packageName传插件包名即可,flags传0。


3、启动插件:启动插件的Activity、Service等都和你启动一个以及安装在系统中的app一样,使用系统提供的相关API即可。组件间通讯也是如此。


知道这么回事后,于是整理代码,干成一个工具类:

/**
 * @Author: duke
 * @DateTime: 2017-05-30 21:40
 * @Description: 插件化安装工具类
 */
public class PluginUtils {

    /**
     * 启动插件
     *
     * @param activity
     * @param packageName
     */
    public static void startActivity(Activity activity, String packageName) {
        PackageManager pm = activity.getPackageManager();
        Intent intent = pm.getLaunchIntentForPackage(packageName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        activity.startActivity(intent);
    }

    /**
     * 删除apk
     *
     * @param activity
     * @param packageName
     */
    public static void unInstallApk(Activity activity, String packageName) {
        if (!PluginManager.getInstance().isConnected()) {
            Toast.makeText(activity, "服务未连接", Toast.LENGTH_SHORT).show();
        } else {
            try {
                PluginManager.getInstance().deletePackage(packageName, 0);
                Toast.makeText(activity, "删除完成", Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 应该在线程中安装,此方法仅供测试
     *
     * @param context
     * @param apkPathAndName
     * @param packageName
     */
    public static boolean installApk(Context context, String apkPathAndName, String packageName) {
        if (!PluginManager.getInstance().isConnected()) {
            //installTips(context,"插件服务正在初始化,请稍后再试。。。");
            return false;
        }
        try {
            if (PluginManager.getInstance().getPackageInfo(packageName, 0) != null) {
                //installTips(context,"已经安装了,不能再安装");
                return true;
            } else {
                //如果需要更新插件,则flag 设置为 PackageManagerCompat.INSTALL_REPLACE_EXISTING
                //int returnCode = PluginManager.getInstance().installPackage(filepath, PackageManagerCompat.INSTALL_REPLACE_EXISTING);
                int returnCode = PluginManager.getInstance().installPackage(apkPathAndName, 0);
                if (returnCode == PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION) {
                    //安装失败,文件请求的权限太多
                    //installTips(context,"安装失败,文件请求的权限太多");
                } else {
                    //安装完成
                    //installTips(context,"安装完成");
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

}


五、具体场景使用:

剩下的就是实战了。在没有安装目标app的前提下,如果你想在你的app中,点击按钮跳转到目标app的某页面。


1、click事件跳转时,你的判断插件有没有安装。当然了,某次插件安装成功后,你得做标记了。判断是也就是拿这个标记判断了。工具类中提供检查插件包是否安装的方法。

2、如果没有安装,则启动子线程安装了。肯定,你的下载第三方包到手机存储的某个位置,不然怎么办。

boolean isInstall = PluginUtils.installApk(weakReference.get(), basePath + "/a.apk", packagename);
可能是耗时操作,需要异步。需要知道第三方app下载到的位置,需要知道第三方app的包名。需要私下协商告知的,不然两个公司怎么合作?

3、跳转进入第三方app:

@Override
    public void onClick(View v) {
        if (v.getId() == R.id.jumpBtn) {
            PluginUtils.startActivity(PluginTestActivity.this, packagename);
        }
    }

/**
     * 启动插件
     *
     * @param activity
     * @param packageName
     */
    public static void startActivity(Activity activity, String packageName) {
        PackageManager pm = activity.getPackageManager();
        Intent intent = pm.getLaunchIntentForPackage(packageName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        activity.startActivity(intent);
    }

必须知道第三方app的包名。还有,如果不是launch模式的intent的话,或者说启动的不是第三方app的启动页时,怎么办?

一样的,跟app内部跳转一样,或者给一个特定的action,或者知道第三方app某Activity的具体路径,即可跳转了。


最后,还有一个混淆配置,坑爹的货:

##----360插件----------------------------------------
-keep class com.morgoo.droidplugin.**{*;}



没有上传真个项目,单独把配置好的插件部分上传了

下载地址:http://download.csdn.net/download/fesdgasdgasdg/9873964






你可能感兴趣的:(android,Android)