简介
DroidPlugin 是360手机助手在Android系统上实现的一种新的插件机制:它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处
详情请查看DroidPlugin的github地址
背景
- 将项目中某个相对独立的功能模块分解出来
- 例如:语音搜索功能模块独立出来,这样减少了项目中依赖包的数量,减少了项目中某些包的层层依赖关系,将语音搜索模块独立成一个单独的apk
- 可以独立开发语音搜索模块
- 假设主项目为Host,依赖library为DroidPlugin,独立出来的语音模块生成为voice.apk
应用
这里将独立出来的语音搜索模块打包成单独的apk,放在项目的assets目录下
项目中的设置步骤:
- 导入DroidPlugin相关的library
- 在自己的Application当中添加如下代码
@Override
public void onCreate() {
super.onCreate();
PluginHelper.getInstance().applicationOnCreate(getBaseContext());
}
@Override
protected void attachBaseContext(Context base) {
PluginHelper.getInstance().applicationAttachBaseContext(base);
super.attachBaseContext(base);
}
- 配置主应用AndroidManifest.xml中相关的组件和权限
这里需要注意DroidPlugin library中需要的权限以及组件要在主项目Host中声明
还有就是Host中的权限只能比voice的多,不能少,否则会有问题 - 读取assets里面的voice.apk并复制到sd上或者其他的可读取存储位置,然后再使用DroidPlugin进行安装,这里将需要用到的一些方法封装在PluginUtils工具类中。
- 当voice.apk通过插件式的形式安装完成以后,我们就可以通过如下方式启动voice.apk
PluginUtils.startActivity(SearchResultActivity.this,"com.storm.smart.voice");
- 在Application或者MainActivty中加载voice.apk
/**
* 通过DroidPlugin插件安装voice.apk
* 从sd卡根目录读取
*/
private void droidPluginInstall() {
BfExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
if (PluginUtils.copyApkFromAssets(getApplicationContext(), "voice.apk",
Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice.apk")) {
PluginUtils.installApk(getApplicationContext(),
Environment.getExternalStorageDirectory().getAbsolutePath() + "/voice.apk",
"com.storm.smart.voice");
}
}
});
}
/**
* 通过DroidPlugin插件安装voice.apk
* 从data下读取,需要权限(有可能手机没有sd卡)
*/
private void droidPluginInstall() {
BfExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
String path = "/data/data/" + getApplicationContext().getPackageName() + "/files" + "/voice.apk";
try {
String command = "chmod " + "666" + " " + path;
Runtime runtime = Runtime.getRuntime();
runtime.exec(command);
} catch (IOException e) {
e.printStackTrace();
}
if (PluginUtils.copyApkFromAssets(getApplicationContext(), "voice.apk", path)) {
PluginUtils.installApk(getApplicationContext(), path, "com.storm.smart.voice");
}
}
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
public class PluginUtils {
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 doUninstall( 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 activity
* @param apkPath
* @param packageName
*/
public static boolean installApk(Context context, String apkPath, String packageName) {
if (!PluginManager.getInstance().isConnected()) {
return false;
}
try {
if (PluginManager.getInstance().getPackageInfo(packageName, 0) != null) {
} else {
int re = PluginManager.getInstance().installPackage(apkPath, 0);
if(re == PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION){
}else{
return true;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**安装提示
* @param context
* @param tips
*/
public static void installTips(Context context,String tips){
Looper.prepare();
Toast.makeText(context, tips, Toast.LENGTH_SHORT).show();
Looper.loop();
}
/** 将assets文件下的apk读取到读取到指定路径
* @param context
* @param fileName
* @param path
* @return
*/
public static boolean copyApkFromAssets(Context context, String fileName, String path) {
boolean copyIsFinish = false;
try {
InputStream is = context.getAssets().open(fileName);
File file = new File(path);
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
byte[] temp = new byte[1024];
int i = 0;
while ((i = is.read(temp)) > 0) {
fos.write(temp, 0, i);
}
fos.close();
is.close();
copyIsFinish = true;
} catch (IOException e) {
e.printStackTrace();
}
return copyIsFinish;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
总结
最终我们会发现在/data/data/【host packageName】下有一个Plugin文件夹,文件夹中有个【voice packageName】文件夹,里面有voice的相关数据
这样voice.apk就以插件的方式嵌入到我们的主应用中了。
原文地址: http://blog.csdn.net/s003603u/article/details/50750335