├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── gradle.xml
├── markdown-navigator.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── EasyDemo.iml
├── README.md
├── app
├── .gitignore
├── app.iml
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── net
│ │ └── goeasyway
│ │ └── easydemo
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── bundles
│ │ ├── AmapDemo.apk
│ │ └── bundle1-debug.apk
│ ├── java
│ └── net
│ │ └── goeasyway
│ │ └── easydemo
│ │ ├── App.java
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── bundle1
├── .gitignore
├── build.gradle
├── bundle1.iml
├── libs
│ └── classes.jar
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── net
│ │ └── goeasyway
│ │ └── bundle1
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── net
│ │ │ └── goeasyway
│ │ │ └── bundle1
│ │ │ └── MainActivity.java
│ └── res
│ │ ├── layout
│ │ └── activity_main.xml
│ │ ├── mipmap-hdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ └── ic_launcher.png
│ │ ├── values-w820dp
│ │ └── dimens.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── net
│ └── goeasyway
│ └── bundle1
│ └── ExampleUnitTest.java
├── easyand
├── .gitignore
├── build.gradle
├── easyand.iml
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── net
│ │ └── goeasyway
│ │ └── easyand
│ │ ├── bundle
│ │ ├── Bundle.java
│ │ ├── BundleClassLoader.java
│ │ ├── BundleContextThemeWrapper.java
│ │ ├── BundleContextWrapper.java
│ │ ├── BundleException.java
│ │ ├── BundleFrameworkApp.java
│ │ ├── BundleInfo.java
│ │ ├── BundleModule.java
│ │ ├── BundlePackageInfo.java
│ │ ├── container
│ │ │ ├── ActivityStub.java
│ │ │ └── BundleServiceContainer.java
│ │ └── hook
│ │ │ ├── ActivityThreadHandlerCallback.java
│ │ │ ├── FrameworkHookHelper.java
│ │ │ ├── IActivityManagerHandler.java
│ │ │ └── IPackageManagerHandler.java
│ │ ├── bundlemananger
│ │ ├── BundleManager.java
│ │ └── BundleUtils.java
│ │ └── utils
│ │ ├── LogUtils.java
│ │ ├── ParcelableUtils.java
│ │ └── ReflectUtils.java
│ └── res
│ └── values
│ └── strings.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── projectFilesBackup
└── .idea
│ └── workspace.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/workspace.xml
4 | /.idea/libraries
5 | .DS_Store
6 | /build
7 | /captures
8 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | EasyDemo
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
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 | Android API 23 Platform
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/EasyDemo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EasyPlug--最简单的Android开源插件框架
2 |
3 | 2015年Android的插件开发热情空前高涨,开源的插件框架也如雨后春笋,初步了解对比了一下各个开源框架,感觉各有千秋。因为面对不同的业务需求,所以在针对性和实现方式上都略有不同。
4 |
5 | 在没有开源框架的时候,世界很安静,大家只能低头研究android机制和framework的代码自己闭门造车,一直怨恨无人同行且不能踩在前辈的肩膀向上。而今面对扑面而来的开源插件时,大家又陷入选择的泥潭而不能自拔,这真是幸福的烦恼。每个插件框架都各有各的优缺点,当你花时间去熟悉对比完各个插件框架后,你也不一定能百分百确定某某框架一定适合你用。你终于在绕了一圈后又回到起点,是自己写一个框架呢还是用开源框架呢?因为你始终不能确认开源的框架是否能经得起实际项目的检测,害怕还有N多不知道的坑在等着你。
6 |
7 | 这就是我推出EasyPlug的目的,无他就是要easy!大道至简,对系统HOOK太多也就会依赖太多,GOOGLE或手机产商任何一点小改动都可能会让你的HOOK失效。太多的功能就会面对太多的不稳定性,太多的类和设计模式会加剧开发者学习曲线,所以EasyPlug要在实现既定目标的前提下,用尽可能少的代码来实现一个基本的插件框架,并用实际中的经验帮大家把兼容性的坑填平。
8 |
9 | ### 需求
10 | 首先明确一下EasyPlug要解决的问题:
11 |
12 | 模块化开发:模块可以独立开发,甚至在Android设备上安装运行和调式;各个模块应相互解耦,也可以有效的解决DEX方法数65535上限的问题。
13 | 不改变原生开发规范:插件的开发仍然按原生开发APK应用的规范,不需要额外学习成本;插件不需要添加需置文件。
14 | 动态部署:可以动态安装、卸载和更新插件,安装和更新完成后插件可以直接运行,不需要宿主进程重启。
15 | 支持插件调用宿主的类:这样很多基础功能(OKHttp, Retrofit, RxJava等)可以编译打包在宿主的APK中,插件无需将这个包打入自己的APK,在运行时用到会从宿主的类加载器中加载。可以极大的减小插件包的大小,并可以对基础库进行统一处理。
16 | 支持常用功能:Activity, Service,自定义View, so库等;
17 |
18 | ### Demo
19 | 如何确认EasyPlug是否合适你的项目使用,请直接看如下的例子,直接不做修改将百度和高德地图的DEMO做为插件。
20 |
21 | 我们写了三个应用:
22 |
23 | >1. 宿主APK:运行EasyPlug框架,负责安装卸载插件,插件的跳转入口。
24 | 2. 两个个插件:高德地图Demo,一个简单的APK应用(调用地图DEMO中的自定义MapView)。
25 |
26 | 当你看完上面的应用,发现适合你的需求,那么最重要的来了:兼容性问题。所以第二步,你需要做的是把上面的DEMO运行在你需要支持的各个手机平台,看是否可个插件都能正常运行。如果有兼容性问题,可以参看源码进行修改,或者发送相关的问题报告给我,我很乐意为大家解决(在下班空闲时间)。
27 |
28 | ### 总结
29 | 1. EasyPlug很多地方参考了其他的开源插件框架,有些代码直接从360的DroidPlugin项目复制过来。在此鸣谢。
30 | 2. EasyPlug有别于其他的开源框架,大而全不是我们追求目标,我们的目的是用尽可能少的代码实现基础的框架能力,去除一些对插件核心无关的代码,不使用复杂的设计模块,降低刚接触插件开发的朋友入门的难度。当你掌握这些核心的知识并能灵活运用时,在框架中添加对其他的组件支持并不是难事。
31 | 3. 不要迷信插件框架,插件框架虽然解决了很多现实的业务问题,可以实现模块化开发,极大的提高了开发协同效率。但所有的Android插件框架都或多或少的使用反射(或者HOOK),如果过于依赖它,把所有Android功能都希望在插件中实现,那么势必要承担更大的风险,Android SDK的每次更新都可能威胁到你的框架。这个度的取舍问题需要各自去把握了。
32 | 4. 如果大家在使用中遇到问题,可以在Github上发起讨论。
33 |
34 |
35 | >对Android开发和面试感兴趣的话可以关注我的微信公众号:Android面试启示录
36 |
37 | 
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "net.goeasyway.easydemo"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:cardview-v7:23.1.1'
25 | compile 'com.android.support:recyclerview-v7:23.1.1'
26 | compile 'com.android.support:design:23.1.1'
27 |
28 | compile project(':easyand')
29 | }
30 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\work\sdk_20150801\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/goeasyway/easydemo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easydemo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
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 |
51 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/assets/bundles/AmapDemo.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/assets/bundles/AmapDemo.apk
--------------------------------------------------------------------------------
/app/src/main/assets/bundles/bundle1-debug.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/assets/bundles/bundle1-debug.apk
--------------------------------------------------------------------------------
/app/src/main/java/net/goeasyway/easydemo/App.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easydemo;
2 |
3 | import net.goeasyway.easyand.bundle.BundleFrameworkApp;
4 | import net.goeasyway.easyand.bundlemananger.BundleManager;
5 |
6 | /**
7 | * Created by goeasyway.net on 2016/2/5.
8 | */
9 | public class App extends BundleFrameworkApp {
10 |
11 | @Override
12 | public void onCreate() {
13 | super.onCreate();
14 | BundleManager.getInstance().installOrUpgradeAssetsBundles();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/net/goeasyway/easydemo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easydemo;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.Button;
9 |
10 | import net.goeasyway.easyand.bundle.hook.FrameworkHookHelper;
11 |
12 | public class MainActivity extends AppCompatActivity {
13 |
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 | Button button = (Button) findViewById(R.id.button);
20 | button.setOnClickListener(new View.OnClickListener() {
21 | @Override
22 | public void onClick(View v) {
23 | Intent intent = new Intent();
24 | intent.setClassName("com.amap.map3d.demo", "com.amap.map3d.demo.MainActivity");
25 | startActivity(intent);
26 | }
27 | });
28 |
29 | Button button2 = (Button) findViewById(R.id.button2);
30 | button2.setOnClickListener(new View.OnClickListener() {
31 | @Override
32 | public void onClick(View v) {
33 | Intent intent = new Intent();
34 | intent.setClassName("net.goeasyway.bundle1", "net.goeasyway.bundle1.MainActivity");
35 | startActivity(intent);
36 | }
37 | });
38 | }
39 |
40 | @Override
41 | protected void attachBaseContext(Context newBase) {
42 | super.attachBaseContext(newBase);
43 | try {
44 | FrameworkHookHelper.hookActivityManagerNative();
45 | FrameworkHookHelper.hookActivityThreadHandler();
46 | FrameworkHookHelper.hookPackageManager();
47 | } catch (Exception e) {
48 | e.printStackTrace();
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
27 |
28 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | EasyDemo
3 |
4 | 这个是宿主应用
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | classpath "io.realm:realm-gradle-plugin:2.2.1"
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/bundle1/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/bundle1/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.2"
6 |
7 | defaultConfig {
8 | applicationId "net.goeasyway.bundle1"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | provided fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 | testCompile 'junit:junit:4.12'
26 | compile 'com.android.support:appcompat-v7:23.1.1'
27 | }
28 |
--------------------------------------------------------------------------------
/bundle1/bundle1.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
--------------------------------------------------------------------------------
/bundle1/libs/classes.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/libs/classes.jar
--------------------------------------------------------------------------------
/bundle1/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\work\sdk_20150801\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/bundle1/src/androidTest/java/net/goeasyway/bundle1/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.bundle1;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/bundle1/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/bundle1/src/main/java/net/goeasyway/bundle1/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.bundle1;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.LinearLayout;
7 |
8 | import net.goeasyway.easyand.bundlemananger.BundleManager;
9 |
10 | public class MainActivity extends Activity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_main);
16 |
17 | String mapPackageName = "com.amap.map3d.demo";
18 | String viewClassName = "com.amap.api.maps.MapView";
19 |
20 | LinearLayout layout = (LinearLayout)findViewById(R.id.view);
21 | View mapView = BundleManager.getInstance().getBundleView(this, mapPackageName, viewClassName);
22 | if (mapView != null) {
23 | layout.addView(mapView);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bundle1/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bundle1/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bundle1/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bundle1/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/bundle1/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/bundle1/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Bundle TestAPK
3 |
4 |
--------------------------------------------------------------------------------
/bundle1/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/bundle1/src/test/java/net/goeasyway/bundle1/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.bundle1;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/easyand/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/easyand/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'realm-android'
3 |
4 |
5 | android {
6 | compileSdkVersion 23
7 | buildToolsVersion "23.0.2"
8 |
9 | defaultConfig {
10 | minSdkVersion 15
11 | targetSdkVersion 23
12 | versionCode 1
13 | versionName "1.0"
14 | }
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | }
20 | }
21 | }
22 |
23 | dependencies {
24 | compile fileTree(dir: 'libs', include: ['*.jar'])
25 | compile 'com.android.support:appcompat-v7:23.1.1'
26 | compile 'com.android.support:support-v13:23.1.1'
27 |
28 | //compile 'io.realm:realm-android:0.87.4'
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/easyand/easyand.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/easyand/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\work\sdk_20150801\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/easyand/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
11 |
12 |
17 |
22 |
27 |
32 |
37 |
38 |
41 |
44 |
47 |
50 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/Bundle.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.content.Context;
4 | import android.content.pm.PackageInfo;
5 | import android.os.Parcel;
6 |
7 | import net.goeasyway.easyand.bundlemananger.BundleManager;
8 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
9 | import net.goeasyway.easyand.utils.ParcelableUtils;
10 |
11 | import java.io.File;
12 |
13 | import io.realm.Realm;
14 | import io.realm.RealmQuery;
15 |
16 | /**
17 | * Copyright (C) 2015-present, goeasyway.net
18 | * Project: EasyPlug an open source Android Plugin Framework
19 | * Author: goeasyway@163.com
20 | * Site: www.goeasyway.net
21 | * Class Description:
22 | * Create Date: 2016/5/12
23 | */
24 | public class Bundle {
25 |
26 | private BundleModule bundleModule;
27 | private BundleInfo bundleInfo;
28 | private BundleClassLoader bundleClassLoader;
29 |
30 | public Bundle(BundleInfo info) {
31 | bundleInfo = info;
32 | }
33 |
34 | public BundleInfo getBundleInfo() {
35 | return bundleInfo;
36 | }
37 |
38 | public BundleModule getBundleModule() {
39 | if (bundleModule == null) {
40 | String apkPath = bundleInfo.getApkPath();
41 | String libraryPath = bundleInfo.getBundlePath() + "/version" + bundleInfo.getVersion() + "/lib";
42 | String optimized = bundleInfo.getBundlePath() + "/version" + bundleInfo.getVersion();
43 | File bundleDataFile = new File(bundleInfo.getBundlePath() + "/data");
44 | ClassLoader parent = BundleManager.getInstance().getParentClassLoader();
45 | if ( bundleClassLoader == null) {
46 | bundleClassLoader = new BundleClassLoader(apkPath, optimized, libraryPath, parent);
47 | }
48 | PackageInfo packageInfo = bundleInfo.getPackageInfo();
49 | Context hostContext = BundleManager.getInstance().getHostContext();
50 | if (packageInfo == null) {
51 | packageInfo = getPackageInfo(bundleInfo);
52 | bundleInfo.setPackageInfo(packageInfo);
53 | }
54 | bundleModule = new BundleModule(hostContext, apkPath, bundleDataFile, bundleClassLoader, packageInfo);
55 | }
56 | return bundleModule;
57 | }
58 |
59 | public BundleClassLoader getBundleClassLoader() {
60 | if (bundleClassLoader == null) {
61 | String apkPath = bundleInfo.getApkPath();
62 | String libraryPath = bundleInfo.getBundlePath() + "/version" + bundleInfo.getVersion() + "/lib";
63 | String optimized = bundleInfo.getBundlePath() + "/version" + bundleInfo.getVersion();
64 | ClassLoader parent = BundleManager.getInstance().getParentClassLoader();
65 | bundleClassLoader = new BundleClassLoader(apkPath, optimized, libraryPath, parent);
66 | }
67 | return bundleClassLoader;
68 | }
69 |
70 | public void releasePluginBundle() {
71 | if (bundleModule != null) {
72 | bundleModule = null;
73 | }
74 | }
75 |
76 | public String getPackageName() {
77 | return bundleInfo.getPackageName();
78 | }
79 |
80 | public String getType() {
81 | return bundleInfo.getType();
82 | }
83 |
84 | /**
85 | * 获取PackageInfo信息
86 | * 如果在数据库中未保存有PackageInfo (Parcelable对像),则从APK文件解析,并转换为byte[]保存入数据库。
87 | */
88 | private PackageInfo getPackageInfo(BundleInfo bundleInfo) {
89 | PackageInfo packageInfo = null;
90 | Context hostContext = BundleManager.getInstance().getHostContext();
91 | Realm realm = Realm.getDefaultInstance();//Realm.getInstance(hostContext);
92 | RealmQuery query = realm.where(BundlePackageInfo.class);
93 | BundlePackageInfo info = query.equalTo("packageName", bundleInfo.getPackageName()).findFirst();
94 | //从数据库获取的数据转换成PackageInfo
95 | if (info != null) {
96 | Parcel parcel = ParcelableUtils.unmarshall(info.getInfoData());
97 | try {
98 | packageInfo = PackageInfo.CREATOR.createFromParcel(parcel);
99 | } catch (Exception e) {
100 | e.printStackTrace();
101 | }
102 | }
103 | // 从APK文件解析出PackageInfo,并存入数据库中
104 | if (packageInfo == null) {
105 | packageInfo = BundleUtils.parseApk(hostContext, bundleInfo.getApkPath());
106 |
107 | savePackageInfo(bundleInfo, packageInfo, realm);
108 | }
109 | realm.close();
110 | return packageInfo;
111 | }
112 |
113 | public static void savePackageInfo(BundleInfo bundleInfo, PackageInfo packageInfo, Realm realm) {
114 | BundlePackageInfo info = new BundlePackageInfo();
115 | info.setPackageName(bundleInfo.getPackageName());
116 | info.setInfoData(ParcelableUtils.marshall(packageInfo));
117 |
118 | realm.beginTransaction();
119 | realm.copyToRealmOrUpdate(info);
120 | realm.commitTransaction();
121 | }
122 |
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import net.goeasyway.easyand.utils.LogUtils;
4 |
5 | import dalvik.system.DexClassLoader;
6 |
7 | /**
8 | * Copyright (C) 2015-present, goeasyway.net
9 | * Project: EasyPlug an open source Android Plugin Framework
10 | * Author: goeasyway@163.com
11 | * Site: www.goeasyway.net
12 | * Class Description:
13 | * Create Date: 2016/5/12
14 | */
15 | public class BundleClassLoader extends DexClassLoader {
16 |
17 | private ClassLoader hostClassLoader; // Host APK的类加载器, 由Android系统为其生成的PathClassLoader
18 |
19 | public BundleClassLoader(String dexPath, String optimizedDirectory,
20 | String libraryPath, ClassLoader parent) {
21 | super(dexPath, optimizedDirectory, libraryPath, parent);
22 | hostClassLoader = parent;
23 | }
24 |
25 | @Override
26 | protected Class> loadClass(String className, boolean resolve)
27 | throws ClassNotFoundException {
28 | Class> clazz = findLoadedClass(className);
29 | ClassLoader systemClassLoader = hostClassLoader.getParent();
30 | if (clazz == null) {
31 | // 借鉴Felix OSGI的思路:java开头或android系统的类 从parent的类加载器查找 (
32 | if (className.startsWith("java.") || className.startsWith("javax.")
33 | || className.startsWith("android.")) {
34 | try {
35 | clazz = systemClassLoader.loadClass(className);
36 | } catch (ClassNotFoundException e) {
37 | }
38 | }
39 | // 从自身的DEX查找
40 | if (clazz == null) {
41 | try {
42 | clazz = findClass(className);
43 | } catch (Exception e) {
44 | }
45 | }
46 | if (clazz == null) {
47 | try {
48 | clazz = hostClassLoader.loadClass(className);
49 | } catch (ClassNotFoundException e) {
50 | clazz = systemClassLoader.loadClass(className);
51 | }
52 | }
53 | }
54 | if (clazz == null) {
55 | LogUtils.w("BundleClassLoader", "Can't find class: " + className);
56 | }
57 | return clazz;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleContextThemeWrapper.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.content.res.Resources.Theme;
6 | import android.view.ContextThemeWrapper;
7 |
8 | import net.goeasyway.easyand.bundlemananger.BundleManager;
9 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
10 |
11 | import java.io.File;
12 |
13 | public class BundleContextThemeWrapper extends ContextThemeWrapper {
14 |
15 | private Context hostContext;
16 | private BundleModule module;
17 |
18 | public BundleContextThemeWrapper(Context base, int themeResId) {
19 | super(base, themeResId);
20 | this.hostContext = BundleManager.getInstance().getHostContext();
21 | }
22 |
23 | public void setBundleModule(BundleModule module) {
24 | this.module = module;
25 | }
26 |
27 | @Override
28 | public Theme getTheme() {
29 | if (module != null) {
30 | return module.getTheme();
31 | }
32 | return super.getTheme();
33 | }
34 |
35 | @Override
36 | public ContentResolver getContentResolver() {
37 | return hostContext.getContentResolver();
38 | }
39 |
40 | @Override
41 | public Object getSystemService(String name) {
42 | if (BundleUtils.useHostSystemService(name)) {
43 | return hostContext.getSystemService(name);
44 | }
45 | return super.getSystemService(name);
46 | }
47 |
48 | @Override
49 | public File getExternalFilesDir(String type) {
50 | return hostContext.getExternalFilesDir(type);
51 | }
52 |
53 | @Override
54 | public File[] getExternalFilesDirs(String type) {
55 | return hostContext.getExternalFilesDirs(type);
56 | }
57 |
58 | @Override
59 | public File getExternalCacheDir() {
60 | return hostContext.getExternalCacheDir();
61 | }
62 |
63 | @Override
64 | public File[] getExternalCacheDirs() {
65 | return hostContext.getExternalCacheDirs();
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleContextWrapper.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.content.ContextWrapper;
6 |
7 | import net.goeasyway.easyand.bundlemananger.BundleManager;
8 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
9 |
10 | import java.io.File;
11 |
12 | /**
13 | * Copyright (C) 2015-present, goeasyway.net
14 | * Project: EasyPlug an open source Android Plugin Framework
15 | * Author: goeasyway@163.com
16 | * Site: www.goeasyway.net
17 | * Class Description:
18 | * Create Date: 2016/5/12
19 | */
20 | public class BundleContextWrapper extends ContextWrapper {
21 | Context hostContext;
22 |
23 | public BundleContextWrapper(Context base) {
24 | super(base);
25 | this.hostContext = BundleManager.getInstance().getHostContext();
26 | }
27 |
28 | @Override
29 | public ContentResolver getContentResolver() {
30 | return hostContext.getContentResolver();
31 | }
32 |
33 | @Override
34 | public Object getSystemService(String name) {
35 | if (BundleUtils.useHostSystemService(name)) {
36 | return hostContext.getSystemService(name);
37 | }
38 | return super.getSystemService(name);
39 | }
40 |
41 | @Override
42 | public File getExternalFilesDir(String type) {
43 | return hostContext.getExternalFilesDir(type);
44 | }
45 |
46 | @Override
47 | public File[] getExternalFilesDirs(String type) {
48 | return hostContext.getExternalFilesDirs(type);
49 | }
50 |
51 | @Override
52 | public File getExternalCacheDir() {
53 | return hostContext.getExternalCacheDir();
54 | }
55 |
56 | @Override
57 | public File[] getExternalCacheDirs() {
58 | return hostContext.getExternalCacheDirs();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleException.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 |
4 | /**
5 | * Copyright (C) 2015-present, goeasyway.net
6 | * Project: EasyPlug an open source Android Plugin Framework
7 | * Author: goeasyway@163.com
8 | * Site: www.goeasyway.net
9 | * Class Description:
10 | * Create Date: 2016/5/12
11 | */
12 | public class BundleException extends Exception {
13 |
14 | public final static int ERROR_CODE_NONE = 0; // 成功
15 |
16 | public final static int ERROR_CODE_COPY_FILE_APK = 40;
17 | public final static int ERROR_CODE_COPY_FILE_SO = 41;
18 |
19 | int errorCode;
20 |
21 | public BundleException(int errorCode) {
22 | this.errorCode = errorCode;
23 | }
24 |
25 | public BundleException(int errorCode, String detailMessage) {
26 | super(detailMessage);
27 | this.errorCode = errorCode;
28 | }
29 |
30 | public int getErrorCode() {
31 | return errorCode;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleFrameworkApp.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.res.AssetManager;
6 |
7 | import net.goeasyway.easyand.bundlemananger.BundleManager;
8 |
9 | /**
10 | * Copyright (C) 2015-present, goeasyway.net
11 | * Project: EasyPlug an open source Android Plugin Framework
12 | * Author: goeasyway@163.com
13 | * Site: www.goeasyway.net
14 | * Class Description:
15 | * Create Date: 2016/5/12
16 | */
17 | public abstract class BundleFrameworkApp extends Application {
18 |
19 | private BundleManager bundleManager;
20 |
21 | @Override
22 | public void onCreate() {
23 | super.onCreate();
24 | }
25 |
26 | @Override
27 | protected void attachBaseContext(Context base) {
28 | super.attachBaseContext(base);
29 | bundleManager = BundleManager.getInstance();
30 | bundleManager.init(this);
31 | }
32 |
33 | // @Override
34 | // public AssetManager getAssets() {
35 | // if (bundleManager != null) {
36 | // /**
37 | // * 主要针对SDK4.4以上WebView更换了实现方式(Chromium),
38 | // * 需要对返回的AssetManager进行判断是否要用Bundle中的;
39 | // */
40 | // AssetManager webViewAssetManager = bundleManager.getWebViewAssetManager();
41 | // if (webViewAssetManager != null) {
42 | // return webViewAssetManager;
43 | // }
44 | // }
45 | // return super.getAssets();
46 | // }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleInfo.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.content.pm.PackageInfo;
4 |
5 | import io.realm.RealmObject;
6 | import io.realm.annotations.Ignore;
7 | import io.realm.annotations.PrimaryKey;
8 |
9 | /**
10 | * Copyright (C) 2015-present, goeasyway.net
11 | * Project: EasyPlug an open source Android Plugin Framework
12 | * Author: goeasyway@163.com
13 | * Site: www.goeasyway.net
14 | * Class Description:
15 | * Create Date: 2016/5/12
16 | */
17 | public class BundleInfo extends RealmObject {
18 |
19 | @PrimaryKey
20 | private long id;
21 | private String packageName;
22 | private String apkPath;
23 | private int version;
24 | private String bundlePath;
25 | private String type; // TODO 添加Lib库作为Bundle,目前只支持APK作为Bundle
26 | @Ignore
27 | private PackageInfo packageInfo;
28 |
29 | public long getId() {
30 | return id;
31 | }
32 |
33 | public void setId(long id) {
34 | this.id = id;
35 | }
36 |
37 | public String getPackageName() {
38 | return packageName;
39 | }
40 |
41 | public void setPackageName(String packageName) {
42 | this.packageName = packageName;
43 | }
44 |
45 | public String getApkPath() {
46 | return apkPath;
47 | }
48 |
49 | public void setApkPath(String apkPath) {
50 | this.apkPath = apkPath;
51 | }
52 |
53 | public int getVersion() {
54 | return version;
55 | }
56 |
57 | public void setVersion(int version) {
58 | this.version = version;
59 | }
60 |
61 | public String getBundlePath() {
62 | return bundlePath;
63 | }
64 |
65 | public void setBundlePath(String bundlePath) {
66 | this.bundlePath = bundlePath;
67 | }
68 |
69 | public String getType() {
70 | return type;
71 | }
72 |
73 | public void setType(String type) {
74 | this.type = type;
75 | }
76 |
77 | public PackageInfo getPackageInfo() {
78 | return packageInfo;
79 | }
80 |
81 | public void setPackageInfo(PackageInfo packageInfo) {
82 | this.packageInfo = packageInfo;
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundleModule.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import android.app.Application;
4 | import android.app.Instrumentation;
5 | import android.content.Context;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.ApplicationInfo;
8 | import android.content.pm.PackageInfo;
9 | import android.content.res.AssetManager;
10 | import android.content.res.Resources;
11 | import android.os.Build;
12 | import android.os.IBinder;
13 |
14 | import net.goeasyway.easyand.bundlemananger.BundleManager;
15 | import net.goeasyway.easyand.utils.LogUtils;
16 | import net.goeasyway.easyand.utils.ReflectUtils;
17 |
18 | import java.io.File;
19 | import java.lang.reflect.Method;
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * Copyright (C) 2015-present, goeasyway.net
25 | * Project: EasyPlug an open source Android Plugin Framework
26 | * Author: goeasyway@163.com
27 | * Site: www.goeasyway.net
28 | * Class Description:
29 | * Create Date: 2016/5/12
30 | */
31 | public class BundleModule {
32 | private static final String TAG = "BundleModule";
33 |
34 | private Context hostContext;
35 | private Application application;
36 | private ClassLoader classLoader;
37 | private Resources resources;
38 | private AssetManager assetManager;
39 | private String packageName;
40 | private PackageInfo packageInfo;
41 | private ApplicationInfo appInfo;
42 | private Object activityThread;
43 | private Object loadedApk;
44 | private String apkPath;
45 | private File dataDir;
46 | private Map activityInfoMap = new HashMap();
47 | private int themeResId;
48 | private Resources.Theme theme;
49 |
50 | public BundleModule(Context context, String apkPath,
51 | File bundleDataFile, ClassLoader bundleClassLoader, PackageInfo packageInfo) {
52 | this.hostContext = context;
53 | this.apkPath = apkPath;
54 | this.classLoader = bundleClassLoader;
55 | this.dataDir = bundleDataFile;
56 | this.packageInfo = packageInfo;
57 | this.activityThread = BundleManager.getInstance().getActivityThread();
58 | createResources(apkPath);
59 | buildModule();
60 | }
61 |
62 | private void buildModule() {
63 | try {
64 | packageName = packageInfo.packageName;
65 | appInfo = (ApplicationInfo) parseApplicationInfo(hostContext, packageInfo, apkPath, dataDir.getAbsolutePath());
66 | themeResId = appInfo.theme;
67 | //LoadedApk
68 | Object compatibilityInfo = ReflectUtils.invoke(resources.getClass(), resources,"getCompatibilityInfo");
69 | loadedApk = ReflectUtils.invoke(activityThread.getClass(), activityThread, "getPackageInfoNoCheck",
70 | new Class[]{ApplicationInfo.class, Class.forName("android.content.res.CompatibilityInfo")},
71 | new Object[]{appInfo, compatibilityInfo});
72 | ReflectUtils.writeField(loadedApk, "mClassLoader", classLoader);
73 |
74 | // 创建Bundle的Application实例,同时会调用它的onCreate方法
75 | long time = System.currentTimeMillis();
76 | makeApplication();
77 | LogUtils.i(TAG, "[makeApplication] " + packageName + " used Time: " + (System.currentTimeMillis() - time));
78 | if (packageInfo.activities != null) {
79 | int length = packageInfo.activities.length;
80 | for (int i = 0; i < length; i++) {
81 | ActivityInfo info = packageInfo.activities[i];
82 | activityInfoMap.put(info.name, info);
83 | }
84 | }
85 | } catch (Exception e) {
86 | e.printStackTrace();
87 | }
88 | }
89 |
90 | /**
91 | * 创建Bundle应用的Application实例,并调用它的onCreate方法
92 | */
93 | private void makeApplication() {
94 | //创建Application实例
95 | Instrumentation instrumentation = ReflectUtils.invoke(activityThread.getClass(), activityThread, "getInstrumentation");
96 |
97 | String appClassName = appInfo.className;
98 | if (appClassName == null) {
99 | appClassName = "android.app.Application";
100 | }
101 | try {
102 | Class> cls = Class.forName("android.app.ContextImpl");
103 | Context base = null;
104 | if (Build.VERSION.SDK_INT >= 21) {
105 | //用于5.0及以后的版本
106 | base = ReflectUtils.invoke(cls, null, "createAppContext", new Class[]{activityThread.getClass(), loadedApk.getClass()},
107 | new Object[]{activityThread, loadedApk});
108 | } else {
109 | base = ReflectUtils.newInstance(cls, null);
110 | if (base == null) {
111 | //SDK4.4.3开始的版本
112 | base = ReflectUtils.invoke(cls, null, "createAppContext", new Class[]{activityThread.getClass(), loadedApk.getClass()},
113 | new Object[]{activityThread, loadedApk});
114 | } else {
115 | ReflectUtils.invoke(cls, base, "init", new Class[]{loadedApk.getClass(), IBinder.class, activityThread.getClass()},
116 | new Object[]{loadedApk, null, activityThread});
117 | }
118 | }
119 | application = instrumentation.newApplication(classLoader, appClassName, base);
120 | ReflectUtils.writeField(loadedApk, "mApplication", application);
121 | if (base != null) {
122 | ReflectUtils.invoke(cls, base, "setOuterContext", new Class[]{Context.class},
123 | new Object[]{application});
124 | BundleContextWrapper newBase = new BundleContextWrapper(base);
125 | ReflectUtils.writeField(application, "mBase", newBase);
126 | }
127 | } catch (Exception e) {
128 | LogUtils.e("PluginModule", "[" + packageName + "] makeApplication error: " + e.getMessage());
129 | e.printStackTrace();
130 | }
131 |
132 | //执行onCreate
133 | if (application != null) {
134 | application.onCreate();
135 | }
136 | }
137 |
138 | public ClassLoader getClassLoader() {
139 | return classLoader;
140 | }
141 |
142 | private void createResources(String path) {
143 | try {
144 | AssetManager asset = AssetManager.class.newInstance();
145 | Method method = asset.getClass().getDeclaredMethod("addAssetPath",
146 | String.class);
147 | method.setAccessible(true);
148 | method.invoke(asset, path);
149 | assetManager = asset;
150 | } catch (Exception e) {
151 | e.printStackTrace();
152 | }
153 | Resources res = hostContext.getResources();
154 | resources = new Resources(assetManager, res.getDisplayMetrics(),
155 | res.getConfiguration());
156 | }
157 |
158 | /**
159 | * 解析和设置ApplicationInfo
160 | */
161 | private ApplicationInfo parseApplicationInfo(Context hostContext,
162 | PackageInfo packageInfo, String apkPath,
163 | String dataDirPath) {
164 | ApplicationInfo applicationInfo = packageInfo.applicationInfo;
165 | if (applicationInfo != null) {
166 | applicationInfo.uid = hostContext.getApplicationInfo().uid;
167 | applicationInfo.sourceDir = apkPath;
168 | applicationInfo.dataDir = dataDirPath;
169 | applicationInfo.nativeLibraryDir = dataDir.getAbsolutePath();
170 | }
171 | return applicationInfo;
172 | }
173 |
174 | public AssetManager getAssetManager() {
175 | return assetManager;
176 | }
177 |
178 | public Resources getResources() {
179 | return resources;
180 | }
181 |
182 | public Application getPulginApplication() {
183 | return application;
184 | }
185 |
186 | public int getAppThemeResId() {
187 | return themeResId;
188 | }
189 |
190 | /**
191 | * 获取Activity的theme id,如无设置,则返回Application的theme id
192 | * @return
193 | */
194 | public int getThemeResId(String activityClassName) {
195 | ActivityInfo info = getActivityInfo(activityClassName);
196 | if (info != null) {
197 | return info.getThemeResource();
198 | }
199 | return themeResId;
200 | }
201 |
202 | /**
203 | * 获取Activity的加载模式
204 | */
205 | public int getActivityLaunchMode(String activityClassName) {
206 | ActivityInfo info = getActivityInfo(activityClassName);
207 | if (info != null) {
208 | return info.launchMode;
209 | }
210 | return ActivityInfo.LAUNCH_MULTIPLE;
211 | }
212 |
213 | public Resources.Theme getTheme() {
214 | if (theme == null) {
215 | theme = resources.newTheme();
216 | }
217 | return theme;
218 | }
219 |
220 | public String getPackageName() {
221 | return packageName;
222 | }
223 |
224 | public PackageInfo getPackageInfo() {
225 | return packageInfo;
226 | }
227 |
228 | public ApplicationInfo getApplicationInfo() {
229 | return appInfo;
230 | }
231 |
232 | public ActivityInfo getActivityInfo(String className) {
233 | return activityInfoMap.get(className);
234 | }
235 |
236 | }
237 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/BundlePackageInfo.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle;
2 |
3 | import io.realm.RealmObject;
4 | import io.realm.annotations.PrimaryKey;
5 |
6 | /**
7 | * Copyright (C) 2015-present, goeasyway.net
8 | * Project: EasyPlug an open source Android Plugin Framework
9 | * Author: goeasyway@163.com
10 | * Site: www.goeasyway.net
11 | * Class Description:
12 | * Create Date: 2016/5/12
13 | */
14 | public class BundlePackageInfo extends RealmObject {
15 | @PrimaryKey
16 | private String packageName;
17 | private byte[] infoData;
18 |
19 | public String getPackageName() {
20 | return packageName;
21 | }
22 |
23 | public void setPackageName(String packageName) {
24 | this.packageName = packageName;
25 | }
26 |
27 | public byte[] getInfoData() {
28 | return infoData;
29 | }
30 |
31 | public void setInfoData(byte[] infoData) {
32 | this.infoData = infoData;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/container/ActivityStub.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.container;
2 |
3 | import android.app.Activity;
4 |
5 | /**
6 | * Copyright (C) 2015-present, goeasyway.net
7 | * Project: EasyPlug an open source Android Plugin Framework
8 | * Author: goeasyway@163.com
9 | * Site: www.goeasyway.net
10 | * Class Description:
11 | * Create Date: 2016/5/12
12 | */
13 | public class ActivityStub extends Activity {
14 |
15 | private static class SingleTaskStub1 extends ActivityStub {
16 | }
17 | private static class SingleTaskStub2 extends ActivityStub {
18 | }
19 | private static class SingleTaskStub3 extends ActivityStub {
20 | }
21 | private static class SingleTaskStub4 extends ActivityStub {
22 | }
23 | private static class SingleTaskStub5 extends ActivityStub {
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/container/BundleServiceContainer.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.container;
2 |
3 | import android.app.Application;
4 | import android.app.Service;
5 | import android.content.ComponentName;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.ServiceConnection;
9 | import android.content.res.Configuration;
10 | import android.os.IBinder;
11 |
12 | import net.goeasyway.easyand.bundle.Bundle;
13 | import net.goeasyway.easyand.bundle.BundleContextWrapper;
14 | import net.goeasyway.easyand.bundle.BundleModule;
15 | import net.goeasyway.easyand.bundlemananger.BundleManager;
16 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
17 | import net.goeasyway.easyand.utils.LogUtils;
18 | import net.goeasyway.easyand.utils.ReflectUtils;
19 |
20 | /**
21 | * Copyright (C) 2015-present, goeasyway.net
22 | * Project: EasyPlug an open source Android Plugin Framework
23 | * Author: goeasyway@163.com
24 | * Site: www.goeasyway.net
25 | * Class Description: 插件Service的代理容器,用来接管插件Service的生命周期。
26 | * Create Date: 2016/5/12
27 | */
28 | public class BundleServiceContainer extends Service {
29 |
30 | /**
31 | * 注册5个代理容器给Bundle Service使用
32 | * 如果你的项目需要更多的service,可以自己添加,并修改BundleManager中的设置的最大Service数量的值
33 | */
34 | public static class Proxy1 extends BundleServiceContainer {
35 | }
36 | public static class Proxy2 extends BundleServiceContainer {
37 | }
38 | public static class Proxy3 extends BundleServiceContainer {
39 | }
40 | public static class Proxy4 extends BundleServiceContainer {
41 | }
42 | public static class Proxy5 extends BundleServiceContainer {
43 | }
44 |
45 | private Service service;
46 | private String className; //真正要运行的Bundle的service
47 | private BundleManager bundleManager;
48 |
49 | @Override
50 | public IBinder onBind(Intent intent) {
51 | if (service == null) {
52 | loadBundleService(intent);
53 | }
54 | if (service != null) {
55 | return service.onBind(getBundleIntent(intent));
56 | }
57 | return null;
58 | }
59 |
60 | @Override
61 | public void onRebind(Intent intent) {
62 | if (service != null) {
63 | service.onRebind(getBundleIntent(intent));
64 | }
65 | super.onRebind(intent);
66 | }
67 |
68 | @Override
69 | public void unbindService(ServiceConnection conn) {
70 | if (service != null) {
71 | service.unbindService(conn);
72 | return;
73 | }
74 | super.unbindService(conn);
75 | }
76 |
77 | @Override
78 | public void onCreate() {
79 | super.onCreate();
80 | bundleManager = BundleManager.getInstance();
81 | }
82 |
83 | @Override
84 | public int onStartCommand(Intent intent, int flags, int startId) {
85 | if (service == null) {
86 | loadBundleService(intent);
87 | }
88 | if (service != null) {
89 | return service.onStartCommand(getBundleIntent(intent), flags, startId);
90 | }
91 | return super.onStartCommand(intent, flags, startId);
92 | }
93 |
94 | @Override
95 | public void onStart(Intent intent, int startId) {
96 | if (service != null) {
97 | LogUtils.i("BundleServiceContainer", "onStart [" + className + "]");
98 | service.onStart(getBundleIntent(intent), startId);
99 | return;
100 | }
101 | super.onStart(intent, startId);
102 | }
103 |
104 | @Override
105 | public void onLowMemory() {
106 | if (service != null) {
107 | service.onLowMemory();
108 | }
109 | super.onLowMemory();
110 | }
111 |
112 | @Override
113 | public void onTrimMemory(int level) {
114 | if (service != null) {
115 | service.onTrimMemory(level);
116 | }
117 | super.onTrimMemory(level);
118 | }
119 |
120 | @Override
121 | public void onDestroy() {
122 | if (service != null) {
123 | LogUtils.i("BundleServiceContainer", "onDestroy [" + className + "]");
124 | bundleManager.removeProxyService(className);
125 | service.onDestroy();
126 | }
127 | super.onDestroy();
128 | }
129 |
130 | @Override
131 | public void onConfigurationChanged(Configuration newConfig) {
132 | if (service != null) {
133 | service.onConfigurationChanged(newConfig);
134 | }
135 | super.onConfigurationChanged(newConfig);
136 | }
137 |
138 | /**
139 | * 从代理封装的Intent获取启动Service的源Intent
140 | */
141 | private Intent getBundleIntent(Intent intent) {
142 | if (intent == null) {
143 | return null;
144 | }
145 | Intent bundleIntent = intent.getParcelableExtra(BundleUtils.EXTRA_BUNDLE_INTENT);
146 | return bundleIntent;
147 | }
148 |
149 | private void loadBundleService(Intent intent) {
150 | if (intent == null) {
151 | return;
152 | }
153 | try {
154 | ComponentName componentName = intent.getParcelableExtra("componentName");
155 | className = componentName.getClassName();
156 | String packageName = componentName.getPackageName();
157 | Bundle bundle = bundleManager.getBundleByPackageName(packageName);
158 | if (bundle == null || bundle.getBundleModule() == null) {
159 | LogUtils.e("BundleServiceContainer", "[loadBundleService] bundle =" + bundle +
160 | " packageName[" + packageName + "] className[" + className + "]");
161 | return;
162 | }
163 | BundleModule bundleModule = bundle.getBundleModule();
164 | if (bundleModule == null) {
165 | LogUtils.e("BundleServiceContainer", "[loadBundleService] bundleModule =" + bundleModule);
166 | return;
167 | }
168 |
169 | Context newBase = new BundleContextWrapper(bundleModule.getPulginApplication());
170 | ClassLoader classLoader = bundle.getBundleClassLoader();
171 | try {
172 | Class> c = classLoader.loadClass(className);
173 | service = (Service) c.newInstance();
174 | // 获取attach方法需要的参数
175 | Object activityThread = ReflectUtils.readField(this, "mThread");
176 | IBinder token = ReflectUtils.readField(this, "mToken");
177 | Object activityManager = ReflectUtils.readField(this, "mActivityManager");
178 | // 调用attach方法
179 | ReflectUtils.invoke(Service.class, service, "attach",
180 | new Class[]{Context.class, activityThread.getClass(), String.class, IBinder.class, Application.class, Object.class},
181 | new Object[]{newBase, activityThread, BundleServiceContainer.class.getName(), token, bundleModule.getPulginApplication(), activityManager});
182 | // 调用onCreate
183 | service.onCreate();
184 | } catch (Exception e) {
185 | e.printStackTrace();
186 | }
187 | } catch (Exception e) {
188 | e.printStackTrace();
189 | }
190 | }
191 |
192 | }
193 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/hook/ActivityThreadHandlerCallback.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.hook;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.pm.ActivityInfo;
8 | import android.os.Handler;
9 | import android.os.Message;
10 |
11 | import net.goeasyway.easyand.bundle.Bundle;
12 | import net.goeasyway.easyand.bundle.BundleContextThemeWrapper;
13 | import net.goeasyway.easyand.bundle.BundleModule;
14 | import net.goeasyway.easyand.bundlemananger.BundleManager;
15 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
16 | import net.goeasyway.easyand.utils.ReflectUtils;
17 |
18 | import java.lang.reflect.Field;
19 |
20 | /**
21 | * Copyright (C) 2015-present, goeasyway.net
22 | * Project: EasyPlug an open source Android Plugin Framework
23 | * Author: goeasyway@163.com
24 | * Site: www.goeasyway.net
25 | * Class Description:
26 | * Create Date: 2016/5/12
27 | */
28 | public class ActivityThreadHandlerCallback implements Handler.Callback {
29 |
30 | public static final int LAUNCH_ACTIVITY = 100;
31 |
32 | Handler mBase;
33 |
34 | public ActivityThreadHandlerCallback(Handler base) {
35 | mBase = base;
36 | }
37 |
38 | @Override
39 | public boolean handleMessage(Message msg) {
40 | switch (msg.what) {
41 | case LAUNCH_ACTIVITY:
42 | handleLaunchActivity(msg);
43 | return true; //直接退出,handleLaunchActivity负责处理
44 | }
45 | mBase.handleMessage(msg);
46 | return true;
47 | }
48 |
49 | private void handleLaunchActivity(Message msg) {
50 | try {
51 | Object obj = msg.obj; //ActivityClientRecord
52 | Intent stubIntent = ReflectUtils.readField(obj, "intent");
53 | BundleModule module = null;
54 | Intent bundleIntent = stubIntent.getParcelableExtra(BundleUtils.EXTRA_BUNDLE_INTENT);
55 | if (bundleIntent != null) {
56 | ComponentName componentName = bundleIntent.getComponent();
57 | String packageName = componentName.getPackageName();
58 | Bundle bundle = BundleManager.getInstance().getBundleByPackageName(packageName);
59 | if (bundle != null) {
60 | //stubIntent.setComponent(componentName);
61 | module = bundle.getBundleModule();
62 |
63 | ClassLoader bundleClassLoader = bundle.getBundleClassLoader();
64 | setIntentClassLoader(bundleIntent, bundleClassLoader);
65 |
66 | ReflectUtils.writeField(obj, "intent", bundleIntent);
67 |
68 | Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");
69 | activityInfoField.setAccessible(true);
70 |
71 | // 根据 getPackageInfo 根据这个 包名获取 LoadedApk的信息; 因此这里我们需要手动填上, 从而能够命中缓存
72 | ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);
73 |
74 | activityInfo.applicationInfo = module.getApplicationInfo();
75 | }
76 | }
77 | // 更换完args后转到原来的处理流程
78 | mBase.handleMessage(msg);
79 | //处理完成后,对此Activity的上下文进行
80 | Activity activity = ReflectUtils.readField(obj, "activity");
81 | Context base = ReflectUtils.readField(activity, "mBase");
82 | BundleContextThemeWrapper newBase = new BundleContextThemeWrapper(base, 0);
83 | if (module != null) {
84 | newBase.setBundleModule(module);
85 | }
86 | ReflectUtils.writeField(activity, "mBase", newBase);
87 | } catch (Exception e) {
88 | e.printStackTrace();
89 | }
90 | }
91 |
92 | private void setIntentClassLoader(Intent intent, ClassLoader classLoader) {
93 | try {
94 | android.os.Bundle mExtras = ReflectUtils.readField(intent, "mExtras");
95 | if (mExtras != null) {
96 | mExtras.setClassLoader(classLoader);
97 | } else {
98 | android.os.Bundle value = new android.os.Bundle();
99 | value.setClassLoader(classLoader);
100 | ReflectUtils.writeField(intent, "mExtras", value);
101 | }
102 | } catch (Exception e) {
103 | } finally {
104 | intent.setExtrasClassLoader(classLoader);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/hook/FrameworkHookHelper.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.hook;
2 |
3 | import android.os.Handler;
4 |
5 | import net.goeasyway.easyand.bundlemananger.BundleManager;
6 | import net.goeasyway.easyand.utils.ReflectUtils;
7 |
8 | import java.lang.reflect.Field;
9 | import java.lang.reflect.Method;
10 | import java.lang.reflect.Proxy;
11 |
12 | /**
13 | * Copyright (C) 2015-present, goeasyway.net
14 | * Project: EasyPlug an open source Android Plugin Framework
15 | * Author: goeasyway@163.com
16 | * Site: www.goeasyway.net
17 | * Class Description:
18 | * Create Date: 2016/5/12
19 | */
20 | public class FrameworkHookHelper {
21 |
22 | public static void hookActivityManagerNative() throws ClassNotFoundException,
23 | NoSuchFieldException, IllegalAccessException {
24 | Class> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
25 |
26 | Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
27 | gDefaultField.setAccessible(true);
28 |
29 | Object gDefault = gDefaultField.get(null);
30 |
31 | // gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
32 | Class> singleton = Class.forName("android.util.Singleton");
33 | Field mInstanceField = singleton.getDeclaredField("mInstance");
34 | mInstanceField.setAccessible(true);
35 |
36 | // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象
37 | Object rawIActivityManager = mInstanceField.get(gDefault);
38 |
39 | // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活
40 | Class> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
41 | Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
42 | new Class>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
43 | mInstanceField.set(gDefault, proxy);
44 | }
45 |
46 | public static void hookActivityThreadHandler() throws Exception {
47 | // 先获取到当前的ActivityThread对象
48 | Object currentActivityThread = BundleManager.getInstance().getActivityThread();
49 | // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
50 | Handler mH = ReflectUtils.readField(currentActivityThread,"mH");
51 | ReflectUtils.writeField(mH, "mCallback", new ActivityThreadHandlerCallback(mH));
52 | }
53 |
54 | public static void hookPackageManager() throws Exception {
55 |
56 | // 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装
57 | // 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.
58 |
59 | Class> activityThreadClass = Class.forName("android.app.ActivityThread");
60 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
61 | currentActivityThreadMethod.setAccessible(true);
62 | Object currentActivityThread = currentActivityThreadMethod.invoke(null);
63 |
64 | // 获取ActivityThread里面原始的 sPackageManager
65 | Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
66 | sPackageManagerField.setAccessible(true);
67 | Object sPackageManager = sPackageManagerField.get(currentActivityThread);
68 |
69 | // 准备好代理对象, 用来替换原始的对象
70 | Class> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
71 | Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
72 | new Class>[] { iPackageManagerInterface },
73 | new IPackageManagerHandler(sPackageManager, null));
74 |
75 | // 1. 替换掉ActivityThread里面的 sPackageManager 字段
76 | sPackageManagerField.set(currentActivityThread, proxy);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/hook/IActivityManagerHandler.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.hook;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Build;
6 |
7 | import net.goeasyway.easyand.bundlemananger.BundleManager;
8 | import net.goeasyway.easyand.bundlemananger.BundleUtils;
9 |
10 | import java.lang.reflect.InvocationHandler;
11 | import java.lang.reflect.Method;
12 |
13 | /**
14 | * Copyright (C) 2015-present, goeasyway.net
15 | * Project: EasyPlug an open source Android Plugin Framework
16 | * Author: goeasyway@163.com
17 | * Site: www.goeasyway.net
18 | * Class Description:
19 | * Create Date: 2016/5/12
20 | */
21 | public class IActivityManagerHandler implements InvocationHandler {
22 | private static final String TAG = "IActivityManagerHandler";
23 |
24 | Object mBase;
25 | Context hostContext;
26 |
27 | public IActivityManagerHandler(Object base) {
28 | mBase = base;
29 | hostContext = BundleManager.getInstance().getHostContext();
30 | }
31 |
32 | @Override
33 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
34 | if ("startActivity".equals(method.getName())) {
35 | Intent raw;
36 | int index = 0;
37 | for (int i = 0; i < args.length; i++) {
38 | if (args[i] instanceof Intent) {
39 | index = i;
40 | break;
41 | }
42 | }
43 | raw = (Intent) args[index];
44 | Intent newIntent = BundleUtils.getActivityStubIntent(raw);
45 | args[index] = newIntent;
46 | } else if ("registerReceiver".equals(method.getName())) {
47 | if (args != null && args.length > 0) {
48 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
49 | for (int index = 0; index < args.length; index++) {
50 | if (args[index] instanceof String) {
51 | String callerPackage = (String) args[index];
52 | if (BundleManager.getInstance().getBundleByPackageName(callerPackage) != null) {
53 | args[index] = hostContext.getPackageName();
54 | break;
55 | }
56 | }
57 | }
58 | }
59 | }
60 | } else if ("startService".equals(method.getName())
61 | || "stopService".equals(method.getName())
62 | || "bindService".equals(method.getName())) {
63 | if (args != null && args.length > 0) {
64 | Intent raw;
65 | int index = 0;
66 | for (int i = 0; i < args.length; i++) {
67 | if (args[i] instanceof Intent) {
68 | index = i;
69 | break;
70 | }
71 | }
72 | raw = (Intent) args[index];
73 | Intent pluginService = BundleUtils.changeToPluginServiceIntent(raw);
74 | args[index] = pluginService;
75 | }
76 | }
77 | return method.invoke(mBase, args);
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundle/hook/IPackageManagerHandler.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundle.hook;
2 |
3 | import android.content.ComponentName;
4 | import android.content.pm.ActivityInfo;
5 | import android.content.pm.ApplicationInfo;
6 | import android.content.pm.PackageInfo;
7 | import android.content.pm.PackageManager;
8 |
9 | import net.goeasyway.easyand.bundle.Bundle;
10 | import net.goeasyway.easyand.bundle.BundleModule;
11 | import net.goeasyway.easyand.bundlemananger.BundleManager;
12 |
13 | import java.lang.reflect.InvocationHandler;
14 | import java.lang.reflect.Method;
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | /**
19 | * Copyright (C) 2015-present, goeasyway.net
20 | * Project: EasyPlug an open source Android Plugin Framework
21 | * Author: goeasyway@163.com
22 | * Site: www.goeasyway.net
23 | * Class Description:
24 | * Create Date: 2016/5/12
25 | */
26 | public class IPackageManagerHandler implements InvocationHandler {
27 |
28 | Object mBase;
29 | BundleModule module;
30 | //系统已安装的应用包名
31 | private List packageNames;
32 |
33 | public IPackageManagerHandler(Object pm, BundleModule module) {
34 | this.mBase = pm;
35 | this.module = module;
36 | }
37 |
38 | @Override
39 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
40 | if ("getPackageInfo".equals(method.getName())) {
41 | if (args != null) {
42 | final int index = 0;
43 | String packageName = null;
44 | if (args.length > index) {
45 | if (args[index] != null && args[index] instanceof String) {
46 | packageName = (String) args[index];
47 | }
48 | }
49 | if (!isInstalled(packageName)) {
50 | Bundle bundle = BundleManager.getInstance().getBundleByPackageName(packageName);
51 | if (bundle != null) {
52 | PackageInfo packageInfo = bundle.getBundleModule().getPackageInfo();
53 | if (packageInfo != null) {
54 | return packageInfo;
55 | }
56 | }
57 | }
58 | }
59 | } else if ("getApplicationInfo".equals(method.getName())) {
60 | if (args != null) {
61 | final int index = 0;
62 | String packageName = null;
63 | if (args.length > index) {
64 | if (args[index] != null && args[index] instanceof String) {
65 | packageName = (String) args[index];
66 | }
67 | }
68 | if (!isInstalled(packageName)) {
69 | Bundle bundle = BundleManager.getInstance().getBundleByPackageName(packageName);
70 | if (bundle != null) {
71 | ApplicationInfo appInfo = bundle.getBundleModule().getApplicationInfo();
72 | if (appInfo != null) {
73 | return appInfo;
74 | }
75 | }
76 | }
77 | }
78 | }else if ("getActivityInfo".equals(method.getName())) {
79 | if (args != null) {
80 | final int index0 = 0;
81 | if (args.length >= 2 && args[index0] instanceof ComponentName) {
82 | ComponentName componentName = (ComponentName) args[index0];
83 | String packageName = componentName.getPackageName();
84 | if (!isInstalled(packageName)) {
85 | Bundle bundle = BundleManager.getInstance().getBundleByPackageName(packageName);
86 | if (bundle != null) {
87 | ActivityInfo info = bundle.getBundleModule().getActivityInfo(componentName.getClassName());
88 | if (info != null) {
89 | return info;
90 | }
91 | }
92 | }
93 | }
94 | }
95 | }
96 | return method.invoke(mBase, args);
97 | }
98 |
99 | /**
100 | * 是否已在系统安装
101 | * @param packageName
102 | * @return
103 | */
104 | private boolean isInstalled(String packageName) {
105 | if (packageNames == null) {
106 | List packageInfos = BundleManager.getInstance().getHostContext().getPackageManager().getInstalledPackages(0);
107 | packageNames = new ArrayList();
108 | if (packageInfos != null) {
109 | for (int i = 0; i < packageInfos.size(); i++) {
110 | String packName = packageInfos.get(i).packageName;
111 | packageNames.add(packName);
112 | }
113 | }
114 | }
115 | return packageNames.contains(packageName);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundlemananger/BundleManager.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundlemananger;
2 |
3 | import android.app.Activity;
4 | import android.app.ActivityManager;
5 | import android.app.Application;
6 | import android.content.ComponentName;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.pm.PackageInfo;
10 | import android.content.res.AssetManager;
11 | import android.os.Build;
12 | import android.text.TextUtils;
13 | import android.util.Log;
14 | import android.util.SparseArray;
15 | import android.view.ContextThemeWrapper;
16 | import android.view.View;
17 |
18 | import net.goeasyway.easyand.bundle.Bundle;
19 | import net.goeasyway.easyand.bundle.BundleContextThemeWrapper;
20 | import net.goeasyway.easyand.bundle.BundleException;
21 | import net.goeasyway.easyand.bundle.BundleInfo;
22 | import net.goeasyway.easyand.bundle.BundleModule;
23 | import net.goeasyway.easyand.bundle.BundlePackageInfo;
24 | import net.goeasyway.easyand.utils.LogUtils;
25 | import net.goeasyway.easyand.utils.ReflectUtils;
26 |
27 | import java.io.File;
28 | import java.lang.reflect.Constructor;
29 | import java.sql.Ref;
30 | import java.util.ArrayList;
31 | import java.util.HashMap;
32 | import java.util.Map;
33 |
34 | import io.realm.Realm;
35 | import io.realm.RealmResults;
36 |
37 | /**
38 | * Copyright (C) 2015-present, goeasyway.net
39 | * Project: EasyPlug an open source Android Plugin Framework
40 | * Author: goeasyway@163.com
41 | * Site: www.goeasyway.net
42 | * Class Description:
43 | * Create Date: 2016/5/12
44 | */
45 | public class BundleManager {
46 | private static final String TAG = "BundleManager";
47 | private static BundleManager instance;
48 | private Context hostContext;
49 | private Object activityThread;
50 |
51 | /**
52 | * 正在运行的plugin service
53 | */
54 | private SparseArray runningPluginServices = new SparseArray();
55 | private final int MAX_SERVICE_NUM = 5;
56 | private final String PLUGIN_SERVICE_CLASSNAME = "net.goeasyway.easyand.bundle.container.BundleServiceContainer$Proxy";
57 |
58 |
59 | /**
60 | * 正在运行的SingleTask模式的activity
61 | */
62 | private SparseArray runningSingleTaskActivities = new SparseArray();
63 | private final int MAX_ACTIVITY_NUM = 5;
64 | private final String PLUGIN_SINGLETASK_ACTIVITY_CLASSNAME = "net.goeasyway.easyand.bundle.container.ActivityStub$SingleTaskStub";
65 |
66 | private ActivityManager am;
67 |
68 | public static String webViewContextPackageName;
69 |
70 | private volatile Map installedBundles = new HashMap();
71 |
72 | private String cachePath;
73 |
74 | private BundleManager() {
75 |
76 | }
77 |
78 | public static BundleManager getInstance() {
79 | if (instance == null) {
80 | instance = new BundleManager();
81 | }
82 | return instance;
83 | }
84 |
85 | public void init(Context hostContext) {
86 |
87 | String processName = getCurProcessName(hostContext);
88 | if (processName != null && processName.contains(":")) {
89 | Log.i("BundleManager", "Bundle Framework will not start in other process: " + processName);
90 | return ;
91 | }
92 | if (!(hostContext instanceof Application)) {
93 |
94 | }
95 | this.hostContext = hostContext;
96 |
97 | String rootPath = hostContext.getFilesDir().getAbsolutePath();
98 | cachePath = rootPath + "/bundles";
99 | File cacheDir = new File(cachePath);
100 | if (!cacheDir.exists()) {
101 | if (!cacheDir.mkdirs()) {
102 | throw new IllegalStateException("Unable to create bundles dir");
103 | }
104 | }
105 |
106 | Realm.init(hostContext);
107 |
108 | loadBundles();
109 | }
110 |
111 | private static String getCurProcessName(Context context) {
112 | int pid = android.os.Process.myPid();
113 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
114 | for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) {
115 | if (appProcess.pid == pid) {
116 | return appProcess.processName;
117 | }
118 | }
119 | return null;
120 | }
121 |
122 | private void loadBundles() {
123 | ArrayList exceptionBundles = new ArrayList(2);
124 |
125 | Realm realm = Realm.getDefaultInstance();//Realm.getInstance(hostContext);
126 | RealmResults infos = realm.where(BundleInfo.class).findAll();
127 |
128 | if (infos != null && infos.size() > 0) {
129 | for (BundleInfo info : infos) {
130 | File bundlePathFile = new File(info.getBundlePath() + "/version" + info.getVersion());
131 | if (!bundlePathFile.exists()) {
132 | LogUtils.e(TAG, "[loadBundles] exception bundle:" + info.getPackageName());
133 | exceptionBundles.add(info);
134 | continue;
135 | }
136 |
137 | Bundle bundle = new Bundle(BundleUtils.copyNewBundleInfo(info));
138 | installedBundles.put(info.getPackageName(), bundle);
139 | }
140 | }
141 |
142 | // 清除掉数据库的残留信息
143 | for (BundleInfo info : exceptionBundles) {
144 | removeBundleInfo(info);
145 | }
146 | realm.close();
147 | }
148 |
149 | public Context getHostContext() {
150 | return hostContext;
151 | }
152 |
153 | public Object getActivityThread() {
154 | if (activityThread == null) {
155 | try {
156 | activityThread = ReflectUtils.invoke(Class.forName("android.app.ActivityThread"),
157 | null, "currentActivityThread");
158 | } catch (ClassNotFoundException e) {
159 | e.printStackTrace();
160 | }
161 | }
162 | return activityThread;
163 | }
164 |
165 | public Bundle getBundleByPackageName(String packageName) {
166 | return installedBundles.get(packageName);
167 | }
168 |
169 | /**
170 | * 获取SparseArray中和className相同值的Index
171 | * @param array
172 | * @param className
173 | * @return
174 | */
175 | private int getSparseArrayIndexOfValue(SparseArray array, String className) {
176 | if (TextUtils.isEmpty(className)) {
177 | return -1;
178 | }
179 | if (array != null && array.size() > 0) {
180 | for (int i = 0; i < array.size(); i++) {
181 | Integer key = array.keyAt(i);
182 | String value = array.get(key);
183 | if (className.equals(value)) {
184 | return i;
185 | }
186 | }
187 | }
188 | return -1;
189 | }
190 |
191 | public String chooseProxySingleTaskActivity(ComponentName componentName) {
192 | String result = "";
193 | if (componentName == null) {
194 | return result;
195 | }
196 | String className = componentName.getClassName();
197 | if (TextUtils.isEmpty(className)) {
198 | return result;
199 | }
200 | int index = getSparseArrayIndexOfValue(runningSingleTaskActivities, className);
201 | if (index >= 0 && index < MAX_ACTIVITY_NUM) { //如果此className已运行
202 | return PLUGIN_SINGLETASK_ACTIVITY_CLASSNAME + String.valueOf(index + 1);
203 | }
204 | // 从1~MAX_SINGLETASK中找出一个不在runningPluginServices的keys中的序号
205 | for (int i = 0; i < MAX_ACTIVITY_NUM; i++) {
206 | if (runningSingleTaskActivities.valueAt(i) == null ||
207 | !(runningSingleTaskActivities.valueAt(i) instanceof String)) {
208 | runningSingleTaskActivities.put(i, className);
209 | return PLUGIN_SINGLETASK_ACTIVITY_CLASSNAME + String.valueOf(i + 1);
210 | }
211 | }
212 | LogUtils.i(TAG, "[chooseProxySingleTaskActivity] Don't find can use Proxy Activity.");
213 | return result;
214 | }
215 |
216 | public void removeProxySingleTaskActivity(String className) {
217 | int index = getSparseArrayIndexOfValue(runningSingleTaskActivities, className);
218 | if (index >= 0) {
219 | runningSingleTaskActivities.remove(index);
220 | }
221 | }
222 |
223 | /**
224 | * 根据要启动的Service类名获取一个未被使用的Service容器(真正注册的)
225 | * 目前默认会声名5个PluginProxyService在AndroidManifest.xml
226 | * PluginProxyService1~PluginProxyService10
227 | */
228 | public String chooseProxyService(ComponentName componentName) {
229 | String result = "";
230 | if (componentName == null) {
231 | return result;
232 | }
233 | String className = componentName.getClassName();
234 | if (TextUtils.isEmpty(className)) {
235 | return result;
236 | }
237 | int index = getSparseArrayIndexOfValue(runningPluginServices, className);
238 | if (index >= 0 && index < MAX_SERVICE_NUM) { //如果此className已运行
239 | return PLUGIN_SERVICE_CLASSNAME + String.valueOf(index + 1);
240 | }
241 | // 从1~10中找出一个不在runningPluginServices的keys中的序号
242 | for (int i = 0; i < MAX_SERVICE_NUM; i++) {
243 | if (runningPluginServices.valueAt(i) == null ||
244 | !(runningPluginServices.valueAt(i) instanceof String)) {
245 | runningPluginServices.put(i, className);
246 | return PLUGIN_SERVICE_CLASSNAME + String.valueOf(i + 1);
247 | }
248 | }
249 | LogUtils.i(TAG, "[chooseProxyService] Can't find free Proxy service.");
250 | return result;
251 | }
252 |
253 | /**
254 | * Bundle Service destroy时要从runningPluginServices中remove
255 | * @param className
256 | */
257 | public void removeProxyService(String className) {
258 | int index = getSparseArrayIndexOfValue(runningPluginServices, className);
259 | if (index >= 0) {
260 | runningPluginServices.remove(index);
261 | }
262 | }
263 |
264 | /**
265 | * 获取Host平台的类加载器;
266 | * 自定义的Bundle类加载器需要使用它加载一些系统和Host平台上的类
267 | * @return
268 | */
269 | public ClassLoader getParentClassLoader() {
270 | return getClass().getClassLoader();
271 | }
272 |
273 | /**
274 | * 获取Bundle中的View实例
275 | */
276 | public View getBundleView(Context context, String packageName, String viewClassName) {
277 | Bundle bundle = getBundleByPackageName(packageName);
278 | if (bundle != null) {
279 | BundleModule module = bundle.getBundleModule();
280 | ClassLoader classLoader = module.getClassLoader();
281 | try {
282 | Class> aClass = classLoader.loadClass("android.app.Activity");
283 | Activity activity = (Activity) aClass.newInstance();
284 | BundleContextThemeWrapper newBase = new BundleContextThemeWrapper(module.getPulginApplication(), 0);
285 | newBase.setBundleModule(module);
286 | //ReflectUtils.invoke(c, activity, "attachBaseContext", new Class[]{Context.class}, new Object[]{newBase});
287 | ReflectUtils.invoke(ContextThemeWrapper.class, activity, "attachBaseContext",
288 | new Class[]{Context.class}, new Object[]{newBase});
289 | // ReflectUtils.writeField(activity, "mBase", newBase);
290 | Class> cls = classLoader.loadClass(viewClassName);
291 | Constructor> constructor = cls.getDeclaredConstructor(Context.class);
292 | constructor.setAccessible(true);
293 | View view = (View) constructor.newInstance(activity);
294 | return view;
295 | } catch (Exception e) {
296 | LogUtils.e("BundleManager", "[getBundleView] error: " + e.toString());
297 | e.printStackTrace();
298 | }
299 | }
300 | return null;
301 | }
302 |
303 |
304 | /**
305 | * 安装、更新和删除Bundle功能 =========================================
306 | */
307 |
308 | /**
309 | * 同步方法:生成一个新的Bundle Id
310 | */
311 | private synchronized long generateBundleId() {
312 | long id = -1;
313 | Realm realm = Realm.getDefaultInstance();//Realm.getInstance(hostContext);
314 | long count = realm.where(BundleInfo.class).count();
315 | if (count > 0) {
316 | id = realm.where(BundleInfo.class).max("id").intValue() + 1;
317 | } else {
318 | id = 0;
319 | }
320 | if (id >= 0) {
321 | BundleInfo info = new BundleInfo();
322 | info.setId(id);
323 | realm.beginTransaction();
324 | realm.copyToRealm(info);
325 | realm.commitTransaction();
326 | }
327 | realm.close();
328 | return id;
329 | }
330 |
331 | private BundleInfo getBundleInfoFromApk(String apkPath) {
332 | BundleInfo info = null;
333 | PackageInfo packageInfo = BundleUtils.parseApk(hostContext, apkPath);
334 | if (packageInfo != null) {
335 | info = new BundleInfo();
336 | info.setPackageName(packageInfo.packageName);
337 | info.setVersion(packageInfo.versionCode);
338 | android.os.Bundle metaData = packageInfo.applicationInfo.metaData;
339 | if (metaData != null) {
340 | // 解析出Bundle的配置信息
341 | info.setType((String) metaData.get("net.goeasyway.bundle_type"));
342 | }
343 | info.setPackageInfo(packageInfo);
344 | }
345 | return info;
346 | }
347 |
348 | /**
349 | * 安装Bundle
350 | */
351 | private void installBundle(final String apkFilePath) {
352 | BundleInfo info = getBundleInfoFromApk(apkFilePath);
353 | if (info == null) {
354 | return;
355 | }
356 | String packageName = info.getPackageName();
357 | Bundle bundle = installedBundles.get(packageName);
358 | if (bundle != null) {
359 | // TODO 已安装过的Bundle如何提示HOST端
360 | return;
361 | }
362 |
363 | long bundleId = generateBundleId();
364 | if (bundleId == -1) {
365 | return;
366 | }
367 |
368 | try {
369 | info.setId(bundleId);
370 | info.setBundlePath(cachePath + "/bundle" + bundleId);
371 | info.setApkPath(info.getBundlePath() + "/version" + info.getVersion() + "/bundle.apk");
372 | extractApkFileToCache(info, apkFilePath);
373 | } catch (Exception e) {
374 | e.printStackTrace();
375 | return;
376 | }
377 | bundle = new Bundle(info);
378 | installedBundles.put(info.getPackageName(), bundle);
379 | //保存安装的BundleInfo到数据库
380 | saveBundleInfo(info);
381 | /**
382 | * 第一次安装Bundle,创建一个DexClassLoader实例,会去做一些优化DEX文件的工作,
383 | * 在安装时就创建ClassLoader,这样可以达到优化下次使用Bundle时创建ClassLoader的耗时
384 | */
385 | bundle.getBundleClassLoader();
386 | }
387 |
388 | private synchronized void saveBundleInfo(BundleInfo info) {
389 | if (info == null || info.getId() < 0) {
390 | return;
391 | }
392 | Realm realm = Realm.getDefaultInstance();//Realm.getInstance(hostContext);
393 | realm.beginTransaction();
394 | realm.copyToRealmOrUpdate(info);
395 | realm.commitTransaction();
396 | Bundle.savePackageInfo(info, info.getPackageInfo(), realm);
397 | realm.close();
398 | }
399 |
400 |
401 | public void asyncInstallBundle(final String apkPath) {
402 | new Thread(new Runnable() {
403 | @Override
404 | public void run() {
405 | installBundle(apkPath);
406 | }
407 | }).start();
408 | }
409 |
410 |
411 | /**
412 | * 将Bundle APK文件释放到相应的Bundle Cache目录中
413 | */
414 | private void extractApkFileToCache(BundleInfo info, String apkFilePath) throws Exception {
415 | File bundlePath = new File(info.getBundlePath() + "/version" + info.getVersion());
416 | // Bundle目录是否存在
417 | if (!bundlePath.exists()) {
418 | bundlePath.mkdirs();
419 | }
420 | // 复制APK到指定Bundle目录中
421 | try {
422 | BundleUtils.copyApk(apkFilePath, info.getApkPath());
423 | } catch (Exception e) {
424 | throw new BundleException(BundleException.ERROR_CODE_COPY_FILE_APK, e.getMessage());
425 | }
426 | // 复制APK中的so库文件到Bundle指定的LIB目录
427 | File libPathFile = new File(info.getBundlePath() + "/version" + info.getVersion() + "/lib");
428 | if (!libPathFile.exists()) {
429 | libPathFile.mkdirs();
430 | }
431 | try {
432 | BundleUtils.copyLibs(apkFilePath, libPathFile.getAbsolutePath());
433 | } catch (Exception e) {
434 | throw new BundleException(BundleException.ERROR_CODE_COPY_FILE_SO, e.getMessage());
435 | }
436 | // 创建应用的data目录
437 | File dataPathFile = new File(info.getBundlePath() + "/data");
438 | if (!dataPathFile.exists()) {
439 | dataPathFile.mkdirs();
440 | }
441 | }
442 |
443 | private void updateBundle(Bundle bundle, String newApkPath) {
444 | BundleInfo info = getBundleInfoFromApk(newApkPath);
445 | if (info == null) {
446 | return;
447 | }
448 |
449 | String packageName = info.getPackageName();
450 | int version = info.getVersion();
451 | int oldVersion = bundle.getBundleInfo().getVersion();
452 | if (version == oldVersion) {
453 | LogUtils.e("updateBundle", "installed [" + packageName + "] version is same with update version " + version);
454 | return;
455 | }
456 |
457 | bundle.releasePluginBundle(); //释放PluginModule
458 |
459 | // 删除旧版本
460 | String path = cachePath + "/bundle" + bundle.getBundleInfo().getId() + "/version" + oldVersion;
461 | File bundleFile = new File(path);
462 | BundleUtils.deleteDirectoryTree(bundleFile);
463 | installedBundles.remove(packageName);
464 |
465 | long bundleId = bundle.getBundleInfo().getId();
466 | try {
467 | info.setId(bundleId);
468 | info.setBundlePath(cachePath + "/bundle" + bundleId);
469 | info.setApkPath(info.getBundlePath() + "/version" + info.getVersion() + "/bundle.apk");
470 | extractApkFileToCache(info, newApkPath);
471 | } catch (Exception e) {
472 | LogUtils.e("updateBundle", "[" + packageName + "] extractApkFileToCache error:" + e.getMessage());
473 | return;
474 | }
475 | Bundle newBundle = new Bundle(info);
476 | installedBundles.put(info.getPackageName(), newBundle);
477 | //保存安装的BundleInfo到数据库
478 | saveBundleInfo(info);
479 | }
480 |
481 | /**
482 | * 更新Bundle
483 | * @param bundle
484 | * @param newApkPath Bundle apk路径
485 | */
486 | public void asyncUpdateBundle(final Bundle bundle, final String newApkPath) {
487 | new Thread(new Runnable() {
488 | @Override
489 | public void run() {
490 | updateBundle(bundle, newApkPath);
491 | }
492 | }).start();
493 | }
494 |
495 | /**
496 | * 卸载Bundle
497 | * @param bundle
498 | */
499 | public void asyncUnInstallBundle(final Bundle bundle) {
500 | new Thread(new Runnable() {
501 | @Override
502 | public void run() {
503 | String packageName = bundle.getPackageName();
504 | if (TextUtils.isEmpty(packageName)) {
505 | return;
506 | }
507 |
508 | bundle.releasePluginBundle(); //释放PluginModule
509 |
510 | String path = cachePath + "/bundle" + bundle.getBundleInfo().getId();
511 | File bundleFile = new File(path);
512 | BundleUtils.deleteDirectoryTree(bundleFile);
513 | removeBundleInfo(bundle.getBundleInfo());
514 | installedBundles.remove(packageName);
515 | }
516 | }).start();
517 | }
518 |
519 | private synchronized void removeBundleInfo(BundleInfo info) {
520 | if (info == null) {
521 | return;
522 | }
523 | Realm realm = Realm.getDefaultInstance();//Realm.getInstance(hostContext);
524 | RealmResults results = realm.where(BundleInfo.class).equalTo("packageName", info.getPackageName()).findAll();
525 | RealmResults packageInfos = realm.where(BundlePackageInfo.class).equalTo("packageName", info.getPackageName()).findAll();
526 | realm.beginTransaction();
527 | if (results != null && results.size() > 0) {
528 | results.clear();
529 | }
530 | if (packageInfos != null && packageInfos.size() > 0) {
531 | packageInfos.clear();
532 | }
533 | realm.commitTransaction();
534 | realm.close();
535 | }
536 |
537 | public void installOrUpgradeAssetsBundles() {
538 | try {
539 | final String[] bundleNames = hostContext.getAssets().list("bundles");
540 | if (bundleNames == null) {
541 | return;
542 | }
543 | new Thread(new Runnable() {
544 | @Override
545 | public void run() {
546 | BundleUtils.copyAssetsBundlesToPhone(hostContext);
547 | String sdPath = BundleUtils.getDefaultBundleFilePath();
548 | File files = new File(sdPath);
549 | File[] fileList = files.listFiles();
550 | if (fileList == null) {
551 | return;
552 | }
553 | for (File file : fileList) {
554 | String apkPath = file.getAbsolutePath();
555 | BundleInfo info = getBundleInfoFromApk(apkPath);
556 | Bundle bundle = installedBundles.get(info.getPackageName());
557 | if (bundle != null) {
558 | updateBundle(bundle, apkPath);
559 | } else {
560 | installBundle(apkPath);
561 | }
562 | }
563 | }
564 | }).start();
565 |
566 | } catch (Exception e) {
567 | e.printStackTrace();
568 | }
569 | }
570 |
571 | }
572 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/bundlemananger/BundleUtils.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.bundlemananger;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.PackageInfo;
8 | import android.content.pm.PackageManager;
9 | import android.text.TextUtils;
10 | import android.view.LayoutInflater;
11 |
12 | import net.goeasyway.easyand.bundle.Bundle;
13 | import net.goeasyway.easyand.bundle.BundleInfo;
14 | import net.goeasyway.easyand.bundle.container.ActivityStub;
15 | import net.goeasyway.easyand.utils.ReflectUtils;
16 |
17 | import java.io.File;
18 | import java.io.FileInputStream;
19 | import java.io.FileOutputStream;
20 | import java.io.InputStream;
21 | import java.util.Enumeration;
22 | import java.util.HashMap;
23 | import java.util.HashSet;
24 | import java.util.Set;
25 | import java.util.zip.ZipEntry;
26 | import java.util.zip.ZipFile;
27 |
28 | /**
29 | * Copyright (C) 2015-present, goeasyway.net
30 | * Project: EasyPlug an open source Android Plugin Framework
31 | * Author: goeasyway@163.com
32 | * Site: www.goeasyway.net
33 | * Class Description:
34 | * Create Date: 2016/5/12
35 | */
36 | public class BundleUtils {
37 | //Extra
38 | public static final String EXTRA_BUNDLE_INTENT = "extra_bundle_intent";
39 | public static final String CPU_ABI = android.os.Build.CPU_ABI;
40 |
41 | /**
42 | * 从APK文件解析PackageInfo信息
43 | */
44 | public static PackageInfo parseApk(Context hostContext, String apkPath) {
45 | PackageManager packageManager = hostContext.getPackageManager();
46 | int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA
47 | | PackageManager.GET_SERVICES | PackageManager.GET_SIGNATURES;;
48 | PackageInfo info = packageManager.getPackageArchiveInfo(apkPath, flags);
49 | if (info == null) {
50 | /**
51 | * 去掉获取签名信息到PackageInfo
52 | * 在SDK5.0后的版本,如果不签名的APK getPackageArchiveInfo会返回Null
53 | */
54 | info = packageManager.getPackageArchiveInfo(apkPath, flags ^ PackageManager.GET_SIGNATURES);
55 | }
56 | return info;
57 | }
58 |
59 | public static boolean useHostSystemService(String serviceName) {
60 | if (Context.WIFI_SERVICE.equals(serviceName) || Context.LOCATION_SERVICE.equals(serviceName)
61 | || Context.TELEPHONY_SERVICE.equals(serviceName)
62 | || Context.CLIPBOARD_SERVICE.equals(serviceName)
63 | || Context.INPUT_METHOD_SERVICE.equals(serviceName)) {
64 | return true;
65 | }
66 | return false;
67 | }
68 |
69 | /**
70 | * 获取封装后的指向ProxyActivity容器的Intent
71 | */
72 | public static Intent getActivityStubIntent(Intent intent) {
73 | Intent stubIntent;
74 | Bundle bundle = null;
75 | ComponentName componentName = intent.getComponent();
76 | if (componentName != null) {
77 | String packageName = componentName.getPackageName();
78 | bundle = BundleManager.getInstance().getBundleByPackageName(packageName);
79 | }
80 | if (bundle != null) {
81 | int launchMode = bundle.getBundleModule().getActivityLaunchMode(componentName.getClassName());
82 | if (launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { // singleTask模式的Activity
83 | stubIntent = changeToSingleTaskPluginIntent(intent);
84 | } else {
85 | // 启动Bundle的Activity
86 | stubIntent = changeToPluginIntent(intent, ActivityStub.class.getName());
87 | }
88 | } else {
89 | // 调用平台外部的Activity
90 | stubIntent = intent;
91 | }
92 | return stubIntent;
93 | }
94 |
95 | public static Intent changeToPluginIntent(Intent intent, String className) {
96 | Intent stubIntent = new Intent();
97 | ComponentName componentName = intent.getComponent();
98 | if (componentName != null) {
99 | stubIntent.setClassName(BundleManager.getInstance().getHostContext(), className);
100 | stubIntent.putExtra("componentName", componentName);
101 | stubIntent.putExtra(EXTRA_BUNDLE_INTENT, intent);
102 | /**
103 | * 在此添加上FLAG_ACTIVITY_NEW_TASK会影响到Activity的onActivityResult,
104 | * 使得这个方法在startActivityForResult后立即就被调用
105 | */
106 | // stubIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
107 | stubIntent.setFlags(intent.getFlags());
108 | return stubIntent;
109 | }
110 | return intent;
111 | }
112 |
113 | public static Intent changeToSingleTaskPluginIntent(Intent intent) {
114 | Intent stubIntent = new Intent();
115 | ComponentName componentName = intent.getComponent();
116 | if (componentName != null) {
117 | BundleManager bundleManager = BundleManager.getInstance();
118 | //选择一个可用的SingleTask的Activity类名
119 | String className = bundleManager.chooseProxySingleTaskActivity(componentName);
120 | if (TextUtils.isEmpty(className)) {
121 | // 如果无法获取空闲的SingleTask的Activity类名,那么就使用默认模式的Activity类名
122 | className = ActivityStub.class.getName();
123 | }
124 | stubIntent.setClassName(BundleManager.getInstance().getHostContext(), className);
125 | stubIntent.putExtra("componentName", componentName);
126 | stubIntent.putExtra(EXTRA_BUNDLE_INTENT, intent);
127 | stubIntent.setFlags(intent.getFlags());
128 | return stubIntent;
129 | }
130 | return intent;
131 | }
132 |
133 | public static Intent changeToPluginServiceIntent(Intent intent) {
134 | Intent proxyIntent = new Intent();
135 | ComponentName componentName = intent.getComponent();
136 | if (componentName != null) {
137 | BundleManager bundleManager = BundleManager.getInstance();
138 | //选择一个代理service
139 | String className = bundleManager.chooseProxyService(componentName);
140 | if (TextUtils.isEmpty(className)) {
141 | return intent;
142 | }
143 | proxyIntent.setClassName(BundleManager.getInstance().getHostContext(), className);
144 | proxyIntent.putExtra("componentName", componentName);
145 | proxyIntent.putExtra(EXTRA_BUNDLE_INTENT, intent);
146 | proxyIntent.setFlags(intent.getFlags());
147 | return proxyIntent;
148 | }
149 | return intent;
150 | }
151 |
152 | /**
153 | * 获取默认安装的bundle的文件路径
154 | */
155 | public static String getDefaultBundleFilePath() {
156 | Context context = BundleManager.getInstance().getHostContext();
157 | return context.getFilesDir().toString() + File.separator
158 | + "defaultBundles" + File.separator;
159 | }
160 |
161 | /**
162 | * 把assets下的bundle文件拷贝到data目录
163 | */
164 | public static boolean copyAssetsBundlesToPhone(Context context) {
165 | try {
166 | String defaultBundleFilePath = getDefaultBundleFilePath();
167 | File file = new File(defaultBundleFilePath);
168 | // 先清空目的路径
169 | deleteDirectoryTree(file);
170 | if (!file.exists()) {
171 | file.mkdirs();
172 | }
173 |
174 | String[] bundleNames = context.getAssets().list("bundles");
175 | for (String name : bundleNames) {
176 | if (name == null || !name.contains(".apk")) {
177 | continue;
178 | }
179 | copyApk(context.getAssets().open("bundles/" + name), defaultBundleFilePath + name);
180 | }
181 | return true;
182 | } catch (Exception e) {
183 | e.printStackTrace();
184 | }
185 | return false;
186 | }
187 |
188 | public static BundleInfo copyNewBundleInfo(BundleInfo info) {
189 | BundleInfo result = new BundleInfo();
190 | result.setId(info.getId());
191 | result.setVersion(info.getVersion());
192 | result.setPackageName(info.getPackageName());
193 | result.setApkPath(info.getApkPath());
194 | result.setBundlePath(info.getBundlePath());
195 | result.setType(info.getType());
196 | return result;
197 | }
198 |
199 | /**
200 | * 从文件流复制APK到指定目录
201 | * @param inputStream
202 | * @param desApkPath
203 | * @throws Exception
204 | */
205 | public static void copyApk(InputStream inputStream, String desApkPath) throws Exception {
206 | File outFile = new File(desApkPath);
207 | if (outFile.exists()) {
208 | outFile.delete();
209 | }
210 | FileOutputStream out = new FileOutputStream(outFile);
211 | byte[] buffer = new byte[4096];
212 | int length = 0;
213 | while ((length = inputStream.read(buffer)) != -1) {
214 | out.write(buffer, 0, length);
215 | }
216 | out.close();
217 | inputStream.close();
218 | }
219 |
220 | /**
221 | * 复到APK到指定目录
222 | * @param apkPath
223 | * @param desApkPath
224 | * @throws Exception
225 | */
226 | public static void copyApk(String apkPath, String desApkPath) throws Exception {
227 | File apkFile = new File(apkPath);
228 | if (!apkFile.exists()) {
229 | return;
230 | }
231 | File outFile = new File(desApkPath);
232 | if (outFile.exists()) {
233 | outFile.delete();
234 | }
235 | InputStream in = new FileInputStream(apkFile);
236 | FileOutputStream out = new FileOutputStream(outFile);
237 | byte[] buffer = new byte[4096];
238 | int length = 0;
239 | while ((length = in.read(buffer)) != -1) {
240 | out.write(buffer, 0, length);
241 | }
242 | out.close();
243 | in.close();
244 | }
245 |
246 | /**
247 | * 复到SO到指定目录
248 | * @param apkPath
249 | * @param libPath
250 | * @throws Exception
251 | */
252 | public static void copyLibs(String apkPath, String libPath) throws Exception {
253 | ZipFile zipFile = new ZipFile(apkPath);
254 | Enumeration> e = zipFile.entries();
255 | Set exactLibNames = new HashSet();
256 | while (e.hasMoreElements()) {
257 | ZipEntry entry = (ZipEntry) e.nextElement();
258 | String entryName = entry.getName();
259 | if (entryName.endsWith(".so") &&
260 | (entryName.contains("armeabi") || entryName.contains(CPU_ABI))) {
261 | String libName = entryName.substring(entryName.lastIndexOf("/"));
262 | if (entryName.contains(CPU_ABI)) {
263 | //和本机CPU_ABI一样
264 | exactLibNames.add(libName);
265 | } else if (exactLibNames.contains(libName)) {
266 | // 非精确的so,可以是armeabi或者armeabi-v7a, 且前面已经加载过精确的so了
267 | continue;
268 | }
269 | File outFile = new File(libPath + File.separator + libName);
270 | if (outFile.exists()) {
271 | outFile.delete();
272 | }
273 | InputStream in = zipFile.getInputStream(entry);
274 | FileOutputStream out = new FileOutputStream(outFile);
275 | byte[] buffer = new byte[1024];
276 | int length = 0;
277 | while ((length = in.read(buffer)) != -1) {
278 | out.write(buffer, 0, length);
279 | }
280 | out.close();
281 | in.close();
282 | }
283 | }
284 | zipFile.close();
285 | }
286 |
287 | public static boolean deleteDirectoryTree(File target) {
288 | if (!deleteDirectoryTreeRecursive(target))
289 | {
290 | System.gc();
291 | System.gc();
292 | return deleteDirectoryTreeRecursive(target);
293 | }
294 | return true;
295 | }
296 |
297 | private static boolean deleteDirectoryTreeRecursive(File target)
298 | {
299 | if (!target.exists())
300 | {
301 | return true;
302 | }
303 | if (target.isDirectory())
304 | {
305 | File[] files = target.listFiles();
306 | if (files != null)
307 | {
308 | for (int i = 0; i < files.length; i++)
309 | {
310 | deleteDirectoryTreeRecursive(files[i]);
311 | }
312 | }
313 | }
314 | return target.delete();
315 | }
316 |
317 | }
318 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/utils/LogUtils.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.utils;
2 |
3 | import android.util.Log;
4 |
5 | public class LogUtils {
6 | private static final String TAG = "EasyAnd_Framework";
7 |
8 | public static void i(String tag, String msg) {
9 | Log.i(TAG, tag + " " + msg);
10 | }
11 |
12 | public static void w(String tag, String msg) {
13 | Log.w(TAG, tag + " " + msg);
14 | }
15 |
16 | public static void e(String tag, String msg) {
17 | Log.e(TAG, tag + " " + msg);
18 | // TODO 记录到文件
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/utils/ParcelableUtils.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.utils;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public class ParcelableUtils {
7 |
8 | public static byte[] marshall(Parcelable parceable) {
9 | Parcel parcel = Parcel.obtain();
10 | parceable.writeToParcel(parcel, 0);
11 | byte[] bytes = parcel.marshall();
12 | parcel.recycle(); // not sure if needed or a good idea
13 | return bytes;
14 | }
15 |
16 | public static T unmarshall(byte[] bytes, Parcelable.Creator creator) {
17 | Parcel parcel = unmarshall(bytes);
18 | return creator.createFromParcel(parcel);
19 | }
20 |
21 | public static Parcel unmarshall(byte[] bytes) {
22 | Parcel parcel = Parcel.obtain();
23 | parcel.unmarshall(bytes, 0, bytes.length);
24 | parcel.setDataPosition(0); // this is extremely important!
25 | return parcel;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/easyand/src/main/java/net/goeasyway/easyand/utils/ReflectUtils.java:
--------------------------------------------------------------------------------
1 | package net.goeasyway.easyand.utils;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.Field;
5 | import java.lang.reflect.Method;
6 |
7 | /**
8 | * Copyright (C) 2015-present, goeasyway.net
9 | * Project: EasyPlug an open source Android Plugin Framework
10 | * Author: goeasyway@163.com
11 | * Site: www.goeasyway.net
12 | * Class Description:
13 | * Create Date: 2016/5/12
14 | */
15 | public class ReflectUtils {
16 |
17 | public static T newInstance(String className, Object... args) {
18 | try {
19 | Class> cls = Class.forName(className);
20 | Constructor> constructor = null;
21 | if (args == null) {
22 | constructor = cls.getDeclaredConstructor();
23 | } else {
24 | constructor = cls.getDeclaredConstructor(getArgClasses(args));
25 | }
26 | constructor.setAccessible(true);
27 | return (T) constructor.newInstance(args);
28 | } catch (Exception e) {
29 | LogUtils.e("ReflectUtils", "[newInstance] className=" + className
30 | + " error:" + e.getMessage());
31 | }
32 | return null;
33 | }
34 |
35 | public static T newInstance(Class> cls, Class>[] argClasses, Object... args) {
36 | try {
37 | Constructor> constructor = null;
38 | if (args == null) {
39 | constructor = cls.getDeclaredConstructor();
40 | } else {
41 | constructor = cls.getDeclaredConstructor(argClasses);
42 | }
43 | constructor.setAccessible(true);
44 | return (T) constructor.newInstance(args);
45 | } catch (Exception e) {
46 | LogUtils.e("ReflectUtils", "[newInstance] cls=" + cls
47 | + " error:" + e.getMessage());
48 | }
49 | return null;
50 | }
51 |
52 | public static boolean writeField(Object obj, String filedName, Object value) {
53 | try {
54 | Field filed = null;
55 | Class> cls = obj.getClass();
56 | while(cls != null && cls != Object.class) {
57 | try {
58 | filed = cls.getDeclaredField(filedName);
59 | } catch (Exception e) {
60 | }
61 | cls = cls.getSuperclass();
62 | }
63 | if (filed == null) {
64 | return false;
65 | }
66 | filed.setAccessible(true);
67 | filed.set(obj, value);
68 | return true;
69 | } catch (Exception e) {
70 | e.printStackTrace();
71 | }
72 | return false;
73 | }
74 |
75 | public static T readField(Object obj, String filedName) {
76 | try {
77 | Class> cls = obj.getClass();
78 | Field filed = null;
79 | while(cls != null && cls != Object.class) {
80 | try {
81 | filed = cls.getDeclaredField(filedName);
82 | } catch (Exception e) {
83 | }
84 | cls = cls.getSuperclass();
85 | }
86 | filed.setAccessible(true);
87 | return (T)filed.get(obj);
88 | } catch (Exception e) {
89 | e.printStackTrace();
90 | }
91 | return null;
92 | }
93 |
94 | public static T readStaticField(Class> cls, String filedName) {
95 | try {
96 | Field filed = null;
97 | while(cls != null && cls != Object.class) {
98 | try {
99 | filed = cls.getDeclaredField(filedName);
100 | } catch (Exception e) {
101 | }
102 | cls = cls.getSuperclass();
103 | }
104 | filed.setAccessible(true);
105 | return (T)filed.get(null);
106 | } catch (Exception e) {
107 | e.printStackTrace();
108 | }
109 | return null;
110 |
111 | }
112 |
113 | private static Class>[] getArgClasses(Object... args) {
114 | int count = args.length;
115 | Class>[] argClasses = new Class>[count];
116 | for (int i = 0; i < count; i++) {
117 | argClasses[i] = args[i].getClass();
118 | }
119 | return argClasses;
120 | }
121 |
122 | public static T invoke(Class> cls, Object obj, String methodName) {
123 | Method method = null;
124 | try {
125 | method = cls.getDeclaredMethod(methodName);
126 | } catch (Exception e) {
127 | }
128 | if (method == null) {
129 | Class> superCls = cls.getSuperclass();
130 | while(superCls != null && superCls != Object.class) {
131 | try {
132 | method = superCls.getMethod(methodName);
133 | } catch (Exception e) {
134 | }
135 | superCls = superCls.getSuperclass();
136 | }
137 | }
138 | if (method == null) {
139 | return null;
140 | }
141 | try {
142 | method.setAccessible(true);
143 | return (T)method.invoke(obj);
144 | } catch (Exception e) {
145 | LogUtils.e("ReflectUtils", "[invoke] methodName=" + methodName
146 | + " error:" + e.getMessage());
147 | }
148 | return null;
149 | }
150 |
151 | public static T invoke(Class> cls, Object obj, String methodName, Class>[] argClasses, Object[] args) {
152 | Method method = null;
153 | try {
154 | method = cls.getDeclaredMethod(methodName, argClasses);
155 | } catch (Exception e) {
156 | e.printStackTrace();
157 | }
158 | if (method == null) {
159 | Class> superCls = cls.getSuperclass();
160 | while(superCls != null && superCls != Object.class) {
161 | try {
162 | method = superCls.getMethod(methodName, argClasses);
163 | } catch (Exception e) {
164 | }
165 | superCls = superCls.getSuperclass();
166 | }
167 | }
168 | if (method == null) {
169 | return null;
170 | }
171 | try {
172 | method.setAccessible(true);
173 | return (T)method.invoke(obj, args);
174 | } catch (Exception e) {
175 | LogUtils.e("ReflectUtils", "[invoke] methodName=" + methodName
176 | + " error:" + e.getMessage());
177 | }
178 | return null;
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/easyand/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | easyand
3 |
4 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/goeasyway/EasyPlug/de76a47116cd7525e825ee3368c92c676ad18471/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 13 09:48:49 HKT 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':easyand', ':bundle1'
2 |
--------------------------------------------------------------------------------