上一次项目迭代中,接触到了插件化框架。
使用场景:我们的app需要集成某一直播app。即在不安装第三方直播app到手机的情况下,点击我们app内部的某一连接能跳转到直播app中,运行里面原有的所有功能。
原理:也就是通过插件化框架,把第三方直播app的apk文件包下载到手机本地,然后在我们的app中,使用插件化框架解压第三方app的代码到我们app的安装目录下,使用classloader加载字节码到另一个进程中,与我们的app进程进行通讯。或许不太对,是个大概的思路。总之,好处就是,不用强制安装第三方app到手机桌面上,可使用第三方app的大部分功能,下载时会一起卸载第三方app的所有文件。
360插件化框架DroidPlugin地址
下面开始集成步骤:
一、下载插件代码,把library以module的形式加入到项目中:
解压下载 的源码,复制..\DroidPlugin-master\project\Libraries\DroidPlugin目录到项目的根目录,如图:
在主工程的settings.gradle文件中添加DroidPlugin库,如图:
编译程序。
二、修改插件内部配置:
如果上一步编译不通过,很正常,可能有些地方需要修改。先说插件内部:
把图中这些lib统一一下名称,都改成libs吧。
还是刚才那个文件,改成你的包名。现在应该可以编译通过了。
将插件中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内部跳转一样,或者给一个特定的action,或者知道第三方app某Activity的具体路径,即可跳转了。
最后,还有一个混淆配置,坑爹的货:
##----360插件----------------------------------------
-keep class com.morgoo.droidplugin.**{*;}
没有上传真个项目,单独把配置好的插件部分上传了
下载地址:http://download.csdn.net/download/fesdgasdgasdg/9873964