Android插件实例——360 DroidPlugin详解
2017-06-07 15:56
417 查看
版权声明:转载请标注:http://blog.csdn.net/yzzst 。 本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
在中国找到钱不难,但你的一个点子不意味着是一个创业。你谈一个再好的想法,比如我今天谈一个创意说,新浪为什么不收购GOOGLE呢?这个创意很好。新浪一收购GOOGLE,是不是新浪就变成老大了?你从哪儿弄来钱?怎么去整合GOOGLE呢;
之前写过有关于Android 插件方向的文章,解析了一下Android的插件原理与运行方式。很多小伙伴都问我,为什么不把我制作的插件放到Github上,让大家共享一下。
我只能说,大哥啊,这个插件是我在公司研发的时候制作的,商业机密,不能开源啊。
刚好,最近逛github的时候,看到了奇虎360手机助手团队的一个Android插件开源项目。今天,我们就具体的分析一下它的原理与实现逻辑。让大家更清楚的了解,一个Android插件的构造。
这个框架是奇虎360手机助手团队,最近在github上开源出来的Android插件框架。这种精神是很值得鼓励的。
github地址为: https://github.com/Qihoo360/DroidPlugin
好,一下纯属引入了官方的说明:
说明
DroidPlugin是360手机助手在Android系统上实现了一种新的插件机制:
它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。
特点
支持Androd 2.3以上系统
插件APK完全不需做任何修改,可以独立安装运行、也可以做插件运行。要以插件模式运行某个APK,你无需重新编译、无需知道其源码。
插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件
插件之间、Host程序与插件之间会互相认为对方已经”安装”在系统上了。
API低侵入性:极少的API。HOST程序只是需要一行代码即可集成Droid Plugin
超强隔离:插件之间、插件与Host之间完全的代码级别的隔离:不能互相调用对方的代码。通讯只能使用Android系统级别的通讯方法。
支持所有系统API 资源完全隔离:插件之间、与Host之间实现了资源完全隔离,不会出现资源窜用的情况。
实现了进程管理,插件的空进程会被及时回收,占用内存低。
插件的静态广播会被当作动态处理,如果插件没有运行(即没有插件进程运行),其静态广播也永远不回被触发。
那么这个插件框架,和我们之前所说的插件框架,有什么特点?它的实现方式是什么样的?这里我们从源码来分析一下。
之前,我们也讨论过,一个Android的apk插件是个什么形式。主要是三点,我总结如下:
- DexLoader动态的加载dex文件进入vm
- AndroidManifest.xml中预注册一些将要使用的四大组件(方法很low,其实有更好的)
- 针对四大组件,分别进行抽象代理,proxy
- Apk安装还需要单独处理native的,so文件。
- 宿主需要处理好Resource,保证R文件的正确使用。
360这个Android插件系统是否和我们之前讨论的类似呢?这里我们从IPluginManagerImpl.Java文件中,我们能够看到其真正的安装apk插件的方法——installPackage。具体方法详解如下所示:
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
98
99
100
101
102
103
104
13f58
105
106
107
108
109
110
111
112
113
114
115
116
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
以下是dexOpt方法的具体内容,简单的贴一下,不做说明了。
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
360 Android插件DroidPlugin中,主要的四大组件代理方式还是占坑。这里所说的占坑就是指,在AndroidManifest.xml中预注册以下可能会用到的信息。
ps.这个方式不是特别好。如果大家对为什么要预注册这个问题不是很了解,或者不了解为什么不好。可以看一下我的博客,我会持续更新。http://blog.csdn.net/yzzst/article/details/45582315
OK,下面是我们从DroidPlugin的Test项目中,截取出来的AndroidManifest.xml代码,我们看看什么叫占坑,怎么占坑。代码如下所示:
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
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
大家都看的出来,虽然简单的插件已经完全够用了。但是这种方式确实不是很好。
DroidPlugin中最重要的就是,为了让插件中的方法能够正确的调用。DroidPlugin把所有的,方法都在内部Hook并替换掉了。
等等,为什么不能够正确的调用?
因为,这个是一个插件系统,Activity是虚拟Proxy出来的。四大组件,并不是真正的,即Context拿到不一定是真的。系统也不会有此类的回调。所以,很多单例都需要Context才能使用。
比如:InputMethodManager.getInstance(Context context);
这时,插件中的代码将不能够直接运行。当然,还有其他原因。
具体的替换和操作方法,我们能够在 HookFactory.java文件中installHook方法中,看到整个DroidPlugin的具体操作如下所示:
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
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
但是,对于DroidPlugin来说,目前还存在一下几种不足和缺点:
无法在插件中发送具有自定义资源的Notification,例如: a. 带自定义RemoteLayout的Notification b.
图标通过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap)
无法在插件中注册一些具有特殊Intent
Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。
对Activity的LaunchMode支持不够好,Activity
Stack管理存在一定缺陷。Activity的onNewIntent函数可能不会被触发。 (此为BUG,未来会修复)
缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏(cocos2d、Unity3D开发的游戏估计都用不了)无法当作插件运行。
当然,看我说的再多,也只是我的个人理解,有兴趣的同学,直接去github clone一份代码阅读阅读吧。
github地址为: https://github.com/Qihoo360/DroidPlugin
/*
* @author zhoushengtao(周圣韬)
* @since 2015年8月31日 凌晨 00:46:42
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
* @交流学习QQ群:341989536
* @私人QQ:445914891
/
目录(?)[+]
在中国找到钱不难,但你的一个点子不意味着是一个创业。你谈一个再好的想法,比如我今天谈一个创意说,新浪为什么不收购GOOGLE呢?这个创意很好。新浪一收购GOOGLE,是不是新浪就变成老大了?你从哪儿弄来钱?怎么去整合GOOGLE呢;
之前写过有关于Android 插件方向的文章,解析了一下Android的插件原理与运行方式。很多小伙伴都问我,为什么不把我制作的插件放到Github上,让大家共享一下。
我只能说,大哥啊,这个插件是我在公司研发的时候制作的,商业机密,不能开源啊。
刚好,最近逛github的时候,看到了奇虎360手机助手团队的一个Android插件开源项目。今天,我们就具体的分析一下它的原理与实现逻辑。让大家更清楚的了解,一个Android插件的构造。
360 Android 插件项目 DroidPlugin
这个框架是奇虎360手机助手团队,最近在github上开源出来的Android插件框架。这种精神是很值得鼓励的。github地址为: https://github.com/Qihoo360/DroidPlugin
好,一下纯属引入了官方的说明:
说明
DroidPlugin是360手机助手在Android系统上实现了一种新的插件机制:
它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。
特点
支持Androd 2.3以上系统
插件APK完全不需做任何修改,可以独立安装运行、也可以做插件运行。要以插件模式运行某个APK,你无需重新编译、无需知道其源码。
插件的四大组件完全不需要在Host程序中注册,支持Service、Activity、BroadcastReceiver、ContentProvider四大组件
插件之间、Host程序与插件之间会互相认为对方已经”安装”在系统上了。
API低侵入性:极少的API。HOST程序只是需要一行代码即可集成Droid Plugin
超强隔离:插件之间、插件与Host之间完全的代码级别的隔离:不能互相调用对方的代码。通讯只能使用Android系统级别的通讯方法。
支持所有系统API 资源完全隔离:插件之间、与Host之间实现了资源完全隔离,不会出现资源窜用的情况。
实现了进程管理,插件的空进程会被及时回收,占用内存低。
插件的静态广播会被当作动态处理,如果插件没有运行(即没有插件进程运行),其静态广播也永远不回被触发。
那么这个插件框架,和我们之前所说的插件框架,有什么特点?它的实现方式是什么样的?这里我们从源码来分析一下。
插件原理——DexClassLoader
之前,我们也讨论过,一个Android的apk插件是个什么形式。主要是三点,我总结如下: - DexLoader动态的加载dex文件进入vm
- AndroidManifest.xml中预注册一些将要使用的四大组件(方法很low,其实有更好的)
- 针对四大组件,分别进行抽象代理,proxy
- Apk安装还需要单独处理native的,so文件。
- 宿主需要处理好Resource,保证R文件的正确使用。
360这个Android插件系统是否和我们之前讨论的类似呢?这里我们从IPluginManagerImpl.Java文件中,我们能够看到其真正的安装apk插件的方法——installPackage。具体方法详解如下所示:
/** * @param filepath 插件Apk的路径 * @param flags Flag * */ @Override public int installPackage(String filepath, int flags) throws RemoteException { //install plugin String apkfile = null; try { // 验证合法性 PackageManager pm = mContext.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(filepath, 0); if (info == null) { return PackageManagerCompat.INSTALL_FAILED_INVALID_APK; } apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName); if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) { // 新安装的插件 // 停止,删除插件的缓存 forceStopPackage(info.packageName); if (mPluginCache.containsKey(info.packageName)) { deleteApplicationCacheFiles(info.packageName, null); } new File(apkfile).delete(); Utils.copyFile(filepath, apkfile); PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); parser.collectCertificates(0); PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); // 验证申请的permission,宿主中是否存在,不存在不让安装。 if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) { for (String requestedPermission : pkgInfo.requestedPermissions) { boolean b = false; try { b = pm.getPermissionInfo(requestedPermission, 0) != null; } catch (NameNotFoundException e) { } if (!mHostRequestedPermission.contains(requestedPermission) && b) { Log.e(TAG, "No Permission %s", requestedPermission); new File(apkfile).delete(); return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION; } } } // 保存签名 saveSignatures(pkgInfo); // if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) { // for (FeatureInfo reqFeature : pkgInfo.reqFeatures) { // Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion()); // } // } // 拷贝插件中的native库文件。 copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0)); // opt Dex文件,并使用DexClassLoader动态的载入类代码。 dexOpt(mContext, apkfile, parser); mPluginCache.put(parser.getPackageName(), parser); // 回调函数,通知插件安装 mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName()); sendInstalledBroadcast(info.packageName); return PackageManagerCompat.INSTALL_SUCCEEDED; } else { // 以下是插件,覆盖安装的过程。即更新的过程。与新安装类似。不做解释了。 if (mPluginCache.containsKey(info.packageName)) { return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS; } else { forceStopPackage(info.packageName); new File(apkfile).delete(); Utils.copyFile(filepath, apkfile); PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); parser.collectCertificates(0); PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES); if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) { for (String requestedPermission : pkgInfo.requestedPermissions) { boolean b = false; try { b = pm.getPermissionInfo(requestedPermission, 0) != null; } catch (NameNotFoundException e) { } if (!mHostRequestedPermission.contains(requestedPermission) && b) { Log.e(TAG, "No Permission %s", requestedPermission); new File(apkfile).delete(); return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION; } } } saveSignatures(pkgInfo); // if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) { // for (FeatureInfo reqFeature : pkgInfo.reqFeatures) { // Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion()); // } // } copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0)); dexOpt(mContext, apkfile, parser); mPluginCache.put(parser.getPackageName(), parser); mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName()); sendInstalledBroadcast(info.packageName); return PackageManagerCompat.INSTALL_SUCCEEDED; } } } catch (Exception e) { if (apkfile != null) { new File(apkfile).delete(); } handleException(e); return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR; } }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
98
99
100
101
102
103
104
13f58
105
106
107
108
109
110
111
112
113
114
115
116
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
以下是dexOpt方法的具体内容,简单的贴一下,不做说明了。
private void dexOpt(Context hostContext, String apkfile, PluginPackageParser parser) throws Exception { String packageName = parser.getPackageName(); String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, packageName); String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, packageName); ClassLoader classloader = new PluginClassLoader(apkfile, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader()); // DexFile dexFile = DexFile.loadDex(apkfile, PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()), 0); // Log.e(TAG, "dexFile=%s,1=%s,2=%s", dexFile, DexFile.isDexOptNeeded(apkfile), DexFile.isDexOptNeeded(PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()))); }1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
占坑——预注册四大组件
360 Android插件DroidPlugin中,主要的四大组件代理方式还是占坑。这里所说的占坑就是指,在AndroidManifest.xml中预注册以下可能会用到的信息。
ps.这个方式不是特别好。如果大家对为什么要预注册这个问题不是很了解,或者不了解为什么不好。可以看一下我的博客,我会持续更新。http://blog.csdn.net/yzzst/article/details/45582315
OK,下面是我们从DroidPlugin的Test项目中,截取出来的AndroidManifest.xml代码,我们看看什么叫占坑,怎么占坑。代码如下所示:
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MyActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".ServiceTest1" android:label="服务测试" /> <activity android:name=".ContentProviderTest" android:label="ContentProvider测试" /> <activity android:name=".BroadcastReceiverTest" android:label="广播测试" /> <activity android:name=".NotificationTest" android:label="通知测试" /> <activity android:name=".ServiceTest2" android:label="多进程服务测试" /> <activity android:name=".ContentProviderTest2" android:label="多进程ContentProvider测试" /> <!-- 预声明不同process的Service --> <service android:name=".Service1" /> <service android:name=".Service2" /> <service android:name=".Service3" android:process=":Process2" /> <service android:name=".Service4" android:process=":Process2" /> <!-- 预声明两种process的Provider --> <provider android:name=".MyContentProvider1" android:authorities="com.example.ApiTest.MyContentProvider1" /> <provider android:name=".MyContentProvider2" android:authorities="com.example.ApiTest.MyContentProvider2" android:process=":Process2" /> <receiver android:name=".StaticBroadcastReceiver" > <intent-filter> <action android:name="com.example.ApiTest.StaticBroadcastReceiver" /> </intent-filter> </receiver> <!-- 预声明各种launchMode的Activity --> <activity android:name=".StandardActivity" android:label="@string/title_activity_standard" /> <activity android:name=".SingleTopActivity" android:label="@string/title_activity_single_top" android:launchMode="singleTop" /> <activity android:name=".SingleTaskActivity" android:label="@string/title_activity_single_task" android:launchMode="singleTask" /> <activity android:name=".SingleInstanceActivity" android:label="@string/title_activity_single_instance" android:launchMode="singleInstance" /> <activity android:name=".ActivityTestActivity" android:label="@string/title_activity_activity_test" > </activity> </application>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
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
大家都看的出来,虽然简单的插件已经完全够用了。但是这种方式确实不是很好。
Hook 系统API方法
DroidPlugin中最重要的就是,为了让插件中的方法能够正确的调用。DroidPlugin把所有的,方法都在内部Hook并替换掉了。
等等,为什么不能够正确的调用?
因为,这个是一个插件系统,Activity是虚拟Proxy出来的。四大组件,并不是真正的,即Context拿到不一定是真的。系统也不会有此类的回调。所以,很多单例都需要Context才能使用。
比如:InputMethodManager.getInstance(Context context);
这时,插件中的代码将不能够直接运行。当然,还有其他原因。
具体的替换和操作方法,我们能够在 HookFactory.java文件中installHook方法中,看到整个DroidPlugin的具体操作如下所示:
public final void installHook(Context context, ClassLoader classLoader) throws Throwable { installHook(new IClipboardBinderHook(context), classLoader); //for INotificationManager installHook(new INotificationManagerBinderHook(context), classLoader); installHook(new IMountServiceBinder(context), classLoader); installHook(new IAudioServiceBinderHook(context), classLoader); installHook(new IContentServiceBinderHook(context), classLoader); installHook(new IWindowManagerBinderHook(context), classLoader); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP_MR1) { installHook(new IGraphicsStatsBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { installHook(new IMediaRouterServiceBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { installHook(new ISessionManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { installHook(new IWifiManagerBinderHook(context), classLoader); } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) { installHook(new IInputMethodManagerBinderHook(context), classLoader); } installHook(new IPackageManagerHook(context), classLoader); installHook(new IActivityManagerHook(context), classLoader); installHook(new PluginCallbackHook(context), classLoader); installHook(new InstrumentationHook(context), classLoader); installHook(new LibCoreHook(context), classLoader); installHook(new SQLiteDatabaseHook(context), classLoader); }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
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
限制和缺陷
Ok,说到最后,还是得称赞一下360。国内大公司开源的使用框架原来越少了。他们的这一举动确实是造福了目前的Android开发者。但是,对于DroidPlugin来说,目前还存在一下几种不足和缺点:
无法在插件中发送具有自定义资源的Notification,例如: a. 带自定义RemoteLayout的Notification b.
图标通过R.drawable.XXX指定的通知(插件系统会自动将其转化为Bitmap)
无法在插件中注册一些具有特殊Intent
Filter的Service、Activity、BroadcastReceiver、ContentProvider等组件以供Android系统、已经安装的其他APP调用。
对Activity的LaunchMode支持不够好,Activity
Stack管理存在一定缺陷。Activity的onNewIntent函数可能不会被触发。 (此为BUG,未来会修复)
缺乏对Native层的Hook,对某些带native代码的apk支持不好,可能无法运行。比如一部分游戏(cocos2d、Unity3D开发的游戏估计都用不了)无法当作插件运行。
当然,看我说的再多,也只是我的个人理解,有兴趣的同学,直接去github clone一份代码阅读阅读吧。
github地址为: https://github.com/Qihoo360/DroidPlugin
/*
* @author zhoushengtao(周圣韬)
* @since 2015年8月31日 凌晨 00:46:42
* @weixin stchou_zst
* @blog http://blog.csdn.net/yzzst
* @交流学习QQ群:341989536
* @私人QQ:445914891
/
相关文章推荐
- Android插件实例——360 DroidPlugin详解
- Android插件实例——360 DroidPlugin详解
- Android插件实例——360 DroidPlugin详解
- Android插件实例——360 DroidPlugin具体解释
- android 使用360插件化DroidPlugin碰到的坑(持续更新)
- 360 Android 插件开发 DroidPlugin 代码分析 -随笔
- 360 Android 插件项目 DroidPlugin
- 插件开发之360 DroidPlugin源码分析(三)Binder代理
- 插件开发之360 DroidPlugin源码分析(一)初识
- Android移动APP开发笔记——Cordova(PhoneGap)通过CordovaPlugin插件调用 Activity 实例
- 在android studio 中使用360插件框架Droid Plugin的问题
- Android插件化开发 第五篇 [360 Droid Plugin]
- 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
- Android插件化开发 第五篇 [360 Droid Plugin]
- 插件开发之360 DroidPlugin源码分析(三)Binder代理
- Android系统实现DroidPlugin插件机制
- 插件开发之360 DroidPlugin源码分析(五)Service预注册占坑
- 插件开发之360 DroidPlugin源码分析(一)初识
- Android插件实例——360 DroidPlugin详解
- Android插件实例——360 DroidPlugin详解