├── .gitignore
├── DynamicAPK_Project_Analysis
├── bundle_path_load.png
├── multidex.png
├── readme.md
├── resource_load_replace.PNG
└── src_dir.png
├── LICENSE.md
├── README.md
├── 第一课-改进的MultiDex动态加载普通apk
├── BundleApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── normal
│ │ └── apk
│ │ ├── FileUtils.java
│ │ └── Utils.java
├── HostApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── hostapk
│ │ ├── AssetsManager.java
│ │ ├── AssetsMultiDexLoader.java
│ │ └── MainActivity.java
├── README.md
└── multidex.png
├── 第三课-Resource资源文件的加载
├── BundleApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── bundle_img.png
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── normal
│ │ └── apk
│ │ ├── FileUtils.java
│ │ └── Utils.java
├── HostApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── hostapk
│ │ ├── AssetsManager.java
│ │ ├── AssetsMultiDexLoader.java
│ │ ├── BundleClassLoaderManager.java
│ │ ├── BundleDexClassLoader.java
│ │ ├── BundlerResourceLoader.java
│ │ └── MainActivity.java
└── README.md
├── 第二课-使用DexClassLoader加载普通apk
├── BundleApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── normal
│ │ └── apk
│ │ ├── FileUtils.java
│ │ └── Utils.java
├── HostApk
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── hostapk
│ │ ├── AssetsManager.java
│ │ ├── AssetsMultiDexLoader.java
│ │ ├── BundleClassLoaderManager.java
│ │ ├── BundleDexClassLoader.java
│ │ └── MainActivity.java
└── README.md
├── 第五课-动态启动插件中的Activity
├── BundleApk5
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── bundle_img.png
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ └── bundle_layout.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── normal
│ │ └── apk
│ │ ├── BaseActivity.java
│ │ ├── BundleActivity.java
│ │ ├── FileUtils.java
│ │ └── Utils.java
├── BundleBuilder
│ ├── .classpath
│ ├── .project
│ ├── .settings
│ │ └── org.eclipse.jdt.core.prefs
│ ├── batch
│ │ ├── 1.bat
│ │ ├── 2.bat
│ │ ├── 3.bat
│ │ ├── 4.bat
│ │ ├── 5.bat
│ │ └── build.bat
│ └── src
│ │ └── com
│ │ └── taobao
│ │ └── trip
│ │ ├── BuildApkUtils.java
│ │ └── BuildMain.java
├── HostApk5
│ ├── .classpath
│ ├── .project
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── libs
│ │ └── android-support-v4.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── layout
│ │ │ └── activity_main.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ └── values
│ │ │ ├── dimens.xml
│ │ │ └── strings.xml
│ └── src
│ │ └── net
│ │ └── mobctrl
│ │ └── hostapk
│ │ ├── MainActivity.java
│ │ ├── application
│ │ └── HostApplication.java
│ │ ├── bundle
│ │ └── BundleInfo.java
│ │ ├── classloader
│ │ ├── AssetsMultiDexLoader.java
│ │ ├── BundleClassLoaderManager.java
│ │ └── BundleDexClassLoader.java
│ │ ├── resource
│ │ ├── AppResource.java
│ │ └── BundlerResourceLoader.java
│ │ └── utils
│ │ └── AssetsManager.java
├── decode.jpg
└── readme.md
└── 第四课-使用LayoutInflate加载离线的布局及资源
├── BundleApk
├── .classpath
├── .project
├── AndroidManifest.xml
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ ├── bundle_img.png
│ │ └── ic_launcher.png
│ ├── layout
│ │ ├── activity_main.xml
│ │ └── bundle_layout.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
└── src
│ └── net
│ └── mobctrl
│ └── normal
│ └── apk
│ ├── FileUtils.java
│ └── Utils.java
├── HostApk
├── .classpath
├── .project
├── AndroidManifest.xml
├── ic_launcher-web.png
├── libs
│ └── android-support-v4.jar
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── activity_main.xml
│ ├── values-w820dp
│ │ └── dimens.xml
│ └── values
│ │ ├── dimens.xml
│ │ └── strings.xml
└── src
│ └── net
│ └── mobctrl
│ └── hostapk
│ ├── AssetsManager.java
│ ├── AssetsMultiDexLoader.java
│ ├── BundleActivity.java
│ ├── BundleClassLoaderManager.java
│ ├── BundleDexClassLoader.java
│ ├── BundlerResourceLoader.java
│ └── MainActivity.java
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 | /*/build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Proguard folder generated by Eclipse
24 | proguard/
25 |
26 | # Log Files
27 | *.log
28 |
--------------------------------------------------------------------------------
/DynamicAPK_Project_Analysis/bundle_path_load.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/DynamicAPK_Project_Analysis/bundle_path_load.png
--------------------------------------------------------------------------------
/DynamicAPK_Project_Analysis/multidex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/DynamicAPK_Project_Analysis/multidex.png
--------------------------------------------------------------------------------
/DynamicAPK_Project_Analysis/readme.md:
--------------------------------------------------------------------------------
1 | #携程DynamicAPK插件化框架源码分析
2 |
3 | ##Author:莫川
4 |
5 | ##插件核心思想
6 |
7 | ###1.aapt的改造
8 | 分别对不同的插件项目分配不同的packageId,然后对各个插件的资源进行编译,生成R文件,然后与宿主项目的R文件进行id的合并。
9 | 要求:由于最终会将所有的资源文件id进行合并,因此,所有的资源名称均不能相同。
10 |
11 | ###2.运行ClassLoader加载各Bundle
12 | 和MultiDex的思路是一样的,所有的插件都被加载到同一个ClassLoader当中,因此,不同插件中的Class必须保持包名和类名的唯一。否则,加载过的类不会再次被加载。
13 | 优缺点:各个Bundle之间完全可以相互调用,但是这也造成了各个Bundle之间ClassLoader的非隔离性。并且随着数组的加长,每次findClass的时间会变长,对性能照成一定长度的影响。
14 | 让我们在熟悉一下这张图:
15 | 
16 |
17 | 在DynamicAPK框架中,每个Bundle被加载到ClassLoader的调用栈如下:
18 | Bundle的Application:BundleBaseApplication
19 | ->BundleBaseApplication(onCreate)
20 | ->BundleCore(run)
21 | ->BundleImpl(optDexFile)
22 | ->BundleArchiveRevision(optDexFile)
23 | ->BundlePathLoader(installBundleDexs)
24 | ->...
25 |
26 | 如下图所示:
27 | 
28 |
29 | ###3.热修复
30 | 由于所有的插件都被加载到同一个ClassLoader当中,因为,热修复的方案都是从dexElements数组的顺序入手,修改expandFieldArray方法的实现,将修复的类放到dexElements的前方。核心代码如下(详见BundlePathLoader):
31 | ```java
32 |
33 | private static void expandFieldArray(Object instance, String fieldName,
34 | Object[] extraElements,boolean isHotFix) throws NoSuchFieldException, IllegalArgumentException,
35 | IllegalAccessException {
36 | synchronized (BundlePathLoader.class) {
37 | Field jlrField = findField(instance, fieldName);
38 | Object[] original = (Object[]) jlrField.get(instance);
39 | Object[] combined = (Object[]) Array.newInstance(
40 | original.getClass().getComponentType(), original.length + extraElements.length);
41 | if(isHotFix) {
42 | System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
43 | System.arraycopy(original, 0, combined, extraElements.length, original.length);
44 | }else {
45 | System.arraycopy(original, 0, combined, 0, original.length);
46 | System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
47 | }
48 | jlrField.set(instance, combined);
49 | }
50 | }
51 |
52 | ```
53 |
54 | 调用的关键代码如下(HotPatchItem.class):
55 |
56 | ```java
57 | public void optDexFile() throws Exception{
58 | List files = new ArrayList();
59 | files.add(this.hotFixFile);
60 | BundlePathLoader.installBundleDexs(RuntimeArgs.androidApplication.getClassLoader(), storageDir, files, false);
61 | }
62 |
63 | public void optHotFixDexFile() throws Exception{
64 | List files = new ArrayList();
65 | files.add(this.hotFixFile);
66 | BundlePathLoader.installBundleDexs(RuntimeArgs.androidApplication.getClassLoader(), storageDir, files, true);
67 | }
68 | ```
69 |
70 | ###4.运行时资源的加载
71 | 所有插件的资源都加载到DelegateResources中,关键代码如下:
72 | DelegateResources.class
73 |
74 | ```java
75 | ...
76 | public static void newDelegateResources(Application application, Resources resources) throws Exception {
77 | List bundles = Framework.getBundles();
78 | if (bundles != null && !bundles.isEmpty()) {
79 | Resources delegateResources;
80 | List arrayList = new ArrayList();
81 | arrayList.add(application.getApplicationInfo().sourceDir);
82 | for (Bundle bundle : bundles) {
83 | arrayList.add(((BundleImpl) bundle).getArchive().getArchiveFile().getAbsolutePath());
84 | }
85 | AssetManager assetManager = AssetManager.class.newInstance();
86 | for (String str : arrayList) {
87 | SysHacks.AssetManager_addAssetPath.invoke(assetManager, str);
88 | }
89 | //处理小米UI资源
90 | if (resources == null || !resources.getClass().getName().equals("android.content.res.MiuiResources")) {
91 | delegateResources = new DelegateResources(assetManager, resources);
92 | } else {
93 | Constructor declaredConstructor = Class.forName("android.content.res.MiuiResources").getDeclaredConstructor(new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class});
94 | declaredConstructor.setAccessible(true);
95 | delegateResources = (Resources) declaredConstructor.newInstance(new Object[]{assetManager, resources.getDisplayMetrics(), resources.getConfiguration()});
96 | }
97 | RuntimeArgs.delegateResources = delegateResources;
98 | AndroidHack.injectResources(application, delegateResources);
99 | StringBuffer stringBuffer = new StringBuffer();
100 | stringBuffer.append("newDelegateResources [");
101 | for (int i = 0; i < arrayList.size(); i++) {
102 | if (i > 0) {
103 | stringBuffer.append(",");
104 | }
105 | stringBuffer.append(arrayList.get(i));
106 | }
107 | stringBuffer.append("]");
108 | log.log(stringBuffer.toString(), Logger.LogLevel.DBUG);
109 | }
110 | }
111 |
112 | ...
113 |
114 | ```
115 |
116 | 上述代码就是将所有Bundle中的资源,通过调用AssetManager的addAssetPath方法,加载到assetManager对象中,然后再用assetManager对象,创建delegateResources对象,并保存在RuntimeArgs.delegateResources当中,然后调用AndroidHack.injectResources方法,对Application和LoadedApk中的mResources成员变量进行注入,代码如下:
117 | ```java
118 | public static void injectResources(Application application, Resources resources) throws Exception {
119 | Object activityThread = getActivityThread();
120 | if (activityThread == null) {
121 | throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
122 | }
123 | Object loadedApk = getLoadedApk(activityThread, application.getPackageName());
124 | if (loadedApk == null) {
125 | throw new Exception("Failed to get ActivityThread.mLoadedApk");
126 | }
127 | SysHacks.LoadedApk_mResources.set(loadedApk, resources);
128 | SysHacks.ContextImpl_mResources.set(application.getBaseContext(), resources);
129 | SysHacks.ContextImpl_mTheme.set(application.getBaseContext(), null);
130 | }
131 | ```
132 | 其中,上述获取LoadedApk的代码,也是通过反射,获取运行时ActivityThread类的LoadedApk对象.
133 |
134 | ###5.运行时动态替换Resource对象
135 |
136 | ####ContextImplHook,动态替换getResources
137 | 为了控制startActivity的时候,能够及时替换Activity的Resource和AssetsManager对象,使用ContextImplHook类对Comtext进行替换,然后动态的返回上一步加载的RuntimeArgs.delegateResources委托资源对象。ContextImplHook的核心代码如下:
138 | ```java
139 |
140 | @Override
141 | public Resources getResources() {
142 | log.log("getResources is invoke", Logger.LogLevel.INFO);
143 | return RuntimeArgs.delegateResources;
144 | }
145 |
146 | @Override
147 | public AssetManager getAssets() {
148 | log.log("getAssets is invoke", Logger.LogLevel.INFO);
149 | return RuntimeArgs.delegateResources.getAssets();
150 | }
151 |
152 | ```
153 | ####如何在Activity跳转过程中,动态的替换呢
154 | 通过反射替换ActivityThread的mInstrumentation对象,替换成InstrumentationHook.class,然后就可以在执行startActivity时,拦截其newActivity和callActivityOnCreate方法,在newActivity方法中,动态的替换newActivity的mResources对象。在callActivityOnCreate方法中将ContextImplHook注入到新创建的Activity中。核心代码如下:
155 | ```java
156 |
157 | @Override
158 | public Activity newActivity(Class> cls, Context context, IBinder iBinder, Application application, Intent intent, ActivityInfo activityInfo, CharSequence charSequence, Activity activity, String str, Object obj) throws InstantiationException, IllegalAccessException {
159 | Activity newActivity = this.mBase.newActivity(cls, context, iBinder, application, intent, activityInfo, charSequence, activity, str, obj);
160 | if (RuntimeArgs.androidApplication.getPackageName().equals(activityInfo.packageName) && SysHacks.ContextThemeWrapper_mResources != null) {
161 | SysHacks.ContextThemeWrapper_mResources.set(newActivity, RuntimeArgs.delegateResources);
162 | }
163 | return newActivity;
164 | }
165 |
166 | @Override
167 | public Activity newActivity(ClassLoader classLoader, String str, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
168 | Activity newActivity;
169 | try {
170 | newActivity = this.mBase.newActivity(classLoader, str, intent);
171 | if (SysHacks.ContextThemeWrapper_mResources != null) {
172 | SysHacks.ContextThemeWrapper_mResources.set(newActivity, RuntimeArgs.delegateResources);
173 | }
174 | } catch (ClassNotFoundException e) {
175 | String property = Framework.getProperty("ctrip.android.bundle.welcome", "ctrip.android.view.home.CtripSplashActivity");
176 | if (StringUtil.isEmpty(property)) {
177 | throw e;
178 | } else {
179 | List runningTasks = ((ActivityManager) this.context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningTasks(1);
180 | if (runningTasks != null && runningTasks.size() > 0 && ((ActivityManager.RunningTaskInfo) runningTasks.get(0)).numActivities > 1) {
181 | if (intent.getComponent() == null) {
182 | intent.setClassName(this.context, str);
183 | }
184 | }
185 | log.log("Could not find activity class: " + str, Logger.LogLevel.WARN);
186 | log.log("Redirect to welcome activity: " + property, Logger.LogLevel.WARN);
187 | newActivity = this.mBase.newActivity(classLoader, property, intent);
188 | }
189 | }
190 | return newActivity;
191 | }
192 |
193 | @Override
194 | public void callActivityOnCreate(Activity activity, Bundle bundle) {
195 | if (RuntimeArgs.androidApplication.getPackageName().equals(activity.getPackageName())) {
196 | ContextImplHook contextImplHook = new ContextImplHook(activity.getBaseContext());
197 | if (!(SysHacks.ContextThemeWrapper_mBase == null || SysHacks.ContextThemeWrapper_mBase.getField() == null)) {
198 | SysHacks.ContextThemeWrapper_mBase.set(activity, contextImplHook);
199 | }
200 | SysHacks.ContextWrapper_mBase.set(activity, contextImplHook);
201 | }
202 | this.mBase.callActivityOnCreate(activity, bundle);
203 | }
204 |
205 | ```
206 |
207 | 总结如下图,Resource的加载和动态替换:
208 | 
209 |
210 | ###6.插件Activity在宿主AndroidManifest中的预注册
211 | 每个插件的Activity,必须在宿主的AndroidManifest.xml中进行注册。
212 |
213 | ##DynamicAPK源码导读:
214 | 源代码的目录结构图
215 | 
216 |
217 |
218 | - framework
219 | 管理各个Bundle以及各个Bundle的封装、版本控制等。
220 | - hack
221 | 通过反射的形式,hack类,方法,成员变量等
222 | - hotpatch
223 | 热修复相关的封装
224 | - loader
225 | 对MultiDex的修改,各Bundle加载到ClassLoader,热修复。
226 | - log
227 | 日志管理
228 | - runtime
229 | 运行时,对Resources进行动态替换
230 | - util
231 | 工具类
232 |
233 |
234 |
235 | ##参考
236 | [1.AssetManager源码]()
237 | [2.LoadedApk源码](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/LoadedApk.java)
238 | [3.ActivityThread源码](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/app/ActivityThread.java)
239 | [4.DynamicAPK源码](https://github.com/CtripMobile/DynamicAPK)
240 |
241 |
--------------------------------------------------------------------------------
/DynamicAPK_Project_Analysis/resource_load_replace.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/DynamicAPK_Project_Analysis/resource_load_replace.PNG
--------------------------------------------------------------------------------
/DynamicAPK_Project_Analysis/src_dir.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/DynamicAPK_Project_Analysis/src_dir.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AndroidPluginFramework
2 | ##Android插件化框架系列文章以及DEMO
3 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HelloWorld
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/src/net/mobctrl/normal/apk/FileUtils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: FileUtils.java, v 0.1 2015年12月10日 下午2:30:40 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class FileUtils {
14 |
15 | public void write(){
16 |
17 | }
18 |
19 | public void print(Context context, String name) {
20 | Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/BundleApk/src/net/mobctrl/normal/apk/Utils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: Utils.java, v 0.1 2015年12月10日 下午2:25:39 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class Utils {
14 |
15 | /**
16 | * 计算 a+b
17 | *
18 | * @param context
19 | * @param a
20 | * @param b
21 | * @param name
22 | */
23 | public int printSum(Context context,int a,int b,String name){
24 | int sum = a + b;
25 | Toast.makeText(context, name+":"+sum, Toast.LENGTH_SHORT).show();
26 | return sum;
27 | }
28 |
29 | public void printFileName(Context context,String name){
30 | new FileUtils().print(context,name);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HostApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
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 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/HostApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HostApk
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/src/net/mobctrl/hostapk/AssetsManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 |
8 | import android.content.Context;
9 | import android.content.res.AssetManager;
10 | import android.util.Log;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Mail mochuan.zhb@alibaba-inc.com
15 | * @Company Alibaba Group
16 | * @PersonalWebsite http://www.mobctrl.net
17 | * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
18 | * @Description
19 | */
20 | public class AssetsManager {
21 |
22 | public static final String TAG = "AssetsApkLoader";
23 |
24 | //从assets复制出去的apk的目标目录
25 | public static final String APK_DIR = "third_apk";
26 |
27 | //文件结尾过滤
28 | public static final String FILE_FILTER = ".apk";
29 |
30 |
31 | /**
32 | * 将资源文件中的apk文件拷贝到私有目录中
33 | *
34 | * @param context
35 | */
36 | public static void copyAllAssetsApk(Context context) {
37 |
38 | AssetManager assetManager = context.getAssets();
39 | long startTime = System.currentTimeMillis();
40 | try {
41 | File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
42 | dex.mkdir();
43 | String []fileNames = assetManager.list("");
44 | for(String fileName:fileNames){
45 | if(!fileName.endsWith(FILE_FILTER)){
46 | return;
47 | }
48 | InputStream in = null;
49 | OutputStream out = null;
50 | in = assetManager.open(fileName);
51 | File f = new File(dex, fileName);
52 | if (f.exists() && f.length() == in.available()) {
53 | Log.i(TAG, fileName+"no change");
54 | return;
55 | }
56 | Log.i(TAG, fileName+" chaneged");
57 | out = new FileOutputStream(f);
58 | byte[] buffer = new byte[2048];
59 | int read;
60 | while ((read = in.read(buffer)) != -1) {
61 | out.write(buffer, 0, read);
62 | }
63 | in.close();
64 | in = null;
65 | out.flush();
66 | out.close();
67 | out = null;
68 | Log.i(TAG, fileName+" copy over");
69 | }
70 | Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
71 | } catch (Exception e) {
72 | e.printStackTrace();
73 | }
74 |
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/HostApk/src/net/mobctrl/hostapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | import android.annotation.TargetApi;
8 | import android.app.Activity;
9 | import android.content.Context;
10 | import android.os.Build;
11 | import android.os.Bundle;
12 | import android.view.View;
13 | import android.widget.TextView;
14 |
15 | public class MainActivity extends Activity {
16 |
17 | public static final String TAG = "MainActivity";
18 |
19 | private TextView invokeTv;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | AssetsMultiDexLoader.install(getApplicationContext());
26 | invokeTv = (TextView) findViewById(R.id.invoke_tv);
27 | invokeTv.setOnClickListener(new View.OnClickListener() {
28 |
29 | @Override
30 | public void onClick(View view) {
31 | loadApk();
32 | }
33 | });
34 | loadClass();
35 |
36 | }
37 |
38 | private void loadClass(){
39 | try{
40 | Class> clazz = Class.forName("net.mobctrl.normal.apk.FileUtils");
41 |
42 | Constructor> constructor = clazz.getConstructor();
43 | Object bundleUtils = constructor.newInstance();
44 |
45 | Method printSumMethod = clazz.getMethod("print", Context.class,String.class);
46 | printSumMethod.setAccessible(true);
47 | printSumMethod.invoke(bundleUtils,
48 | getApplicationContext(), "Hello");
49 | }catch(Exception e){
50 | e.printStackTrace();
51 | }
52 |
53 | }
54 |
55 | @Override
56 | protected void attachBaseContext(Context newBase) {
57 |
58 | super.attachBaseContext(newBase);
59 | }
60 |
61 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
62 | private void loadApk() {
63 | try {
64 | Class> clazz = getClassLoader()
65 | .loadClass("net.mobctrl.normal.apk.Utils");
66 | Constructor> constructor = clazz.getConstructor();
67 | Object bundleUtils = constructor.newInstance();
68 |
69 | Method printSumMethod = clazz.getMethod("printSum", Context.class,
70 | int.class, int.class, String.class);
71 | printSumMethod.setAccessible(true);
72 | Integer sum = (Integer)printSumMethod.invoke(bundleUtils,
73 | getApplicationContext(), 10, 20, "计算结果");
74 | System.out.println("debug:sum = " + sum);
75 | } catch (ClassNotFoundException e) {
76 | e.printStackTrace();
77 | } catch (SecurityException e) {
78 | e.printStackTrace();
79 | } catch (NoSuchMethodException e) {
80 | e.printStackTrace();
81 | } catch (IllegalArgumentException e) {
82 | e.printStackTrace();
83 | } catch (InstantiationException e) {
84 | e.printStackTrace();
85 | } catch (IllegalAccessException e) {
86 | e.printStackTrace();
87 | } catch (InvocationTargetException e) {
88 | e.printStackTrace();
89 | }
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/第一课-改进的MultiDex动态加载普通apk/multidex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第一课-改进的MultiDex动态加载普通apk/multidex.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/drawable-xxhdpi/bundle_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/res/drawable-xxhdpi/bundle_img.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/BundleApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bundle
5 | I am in bundle str
6 | Settings
7 |
8 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/src/net/mobctrl/normal/apk/FileUtils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: FileUtils.java, v 0.1 2015年12月10日 下午2:30:40 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class FileUtils {
14 |
15 | public void write(){
16 |
17 | }
18 |
19 | public void print(Context context, String name) {
20 | Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/BundleApk/src/net/mobctrl/normal/apk/Utils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: Utils.java, v 0.1 2015年12月10日 下午2:25:39 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class Utils {
14 |
15 | /**
16 | * 计算 a+b
17 | *
18 | * @param context
19 | * @param a
20 | * @param b
21 | * @param name
22 | */
23 | public int printSum(Context context,int a,int b,String name){
24 | int sum = a + b;
25 | Toast.makeText(context, name+":"+sum, Toast.LENGTH_SHORT).show();
26 | return sum;
27 | }
28 |
29 | public void printFileName(Context context,String name){
30 | new FileUtils().print(context,name);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HostApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
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 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第三课-Resource资源文件的加载/HostApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HostApk
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/src/net/mobctrl/hostapk/AssetsManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 |
8 | import android.content.Context;
9 | import android.content.res.AssetManager;
10 | import android.util.Log;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
17 | * @Description
18 | */
19 | public class AssetsManager {
20 |
21 | public static final String TAG = "AssetsApkLoader";
22 |
23 | //从assets复制出去的apk的目标目录
24 | public static final String APK_DIR = "third_apk";
25 |
26 | //文件结尾过滤
27 | public static final String FILE_FILTER = ".apk";
28 |
29 |
30 | /**
31 | * 将资源文件中的apk文件拷贝到私有目录中
32 | *
33 | * @param context
34 | */
35 | public static void copyAllAssetsApk(Context context) {
36 |
37 | AssetManager assetManager = context.getAssets();
38 | long startTime = System.currentTimeMillis();
39 | try {
40 | File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
41 | dex.mkdir();
42 | String []fileNames = assetManager.list("");
43 | for(String fileName:fileNames){
44 | if(!fileName.endsWith(FILE_FILTER)){
45 | return;
46 | }
47 | InputStream in = null;
48 | OutputStream out = null;
49 | in = assetManager.open(fileName);
50 | File f = new File(dex, fileName);
51 | if (f.exists() && f.length() == in.available()) {
52 | Log.i(TAG, fileName+"no change");
53 | return;
54 | }
55 | Log.i(TAG, fileName+" chaneged");
56 | out = new FileOutputStream(f);
57 | byte[] buffer = new byte[2048];
58 | int read;
59 | while ((read = in.read(buffer)) != -1) {
60 | out.write(buffer, 0, read);
61 | }
62 | in.close();
63 | in = null;
64 | out.flush();
65 | out.close();
66 | out = null;
67 | Log.i(TAG, fileName+" copy over");
68 | }
69 | Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
70 | } catch (Exception e) {
71 | e.printStackTrace();
72 | }
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/src/net/mobctrl/hostapk/BundleClassLoaderManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import android.annotation.TargetApi;
9 | import android.content.Context;
10 | import android.os.Build;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
17 | * mochuan.zhb Exp $
18 | * @Description
19 | */
20 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
21 | public class BundleClassLoaderManager {
22 |
23 | public static List bundleDexClassLoaderList = new ArrayList();
24 |
25 | /**
26 | * 加载Assets里的apk文件
27 | * @param context
28 | */
29 | public static void install(Context context) {
30 | AssetsManager.copyAllAssetsApk(context);
31 | // 获取dex文件列表
32 | File dexDir = context.getDir(AssetsManager.APK_DIR,
33 | Context.MODE_PRIVATE);
34 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
35 |
36 | @Override
37 | public boolean accept(File dir, String filename) {
38 | return filename.endsWith(AssetsManager.FILE_FILTER);
39 | }
40 | });
41 | for (File f : szFiles) {
42 | System.out.println("debug:load file:" + f.getName());
43 | //加载apk,生成对应的ClassLoader
44 | BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
45 | f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
46 | context.getClassLoader());
47 | bundleDexClassLoaderList.add(bundleDexClassLoader);
48 | }
49 | }
50 |
51 | /**
52 | * 查找类
53 | *
54 | * @param className
55 | * @return
56 | * @throws ClassNotFoundException
57 | */
58 | public static Class> loadClass(Context context,String className) throws ClassNotFoundException {
59 | try {
60 | Class> clazz = context.getClassLoader().loadClass(className);
61 | if (clazz != null) {
62 | System.out.println("debug: class find in main classLoader");
63 | return clazz;
64 | }
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
69 | try {
70 | Class> clazz = bundleDexClassLoader.loadClass(className);
71 | if (clazz != null) {
72 | System.out.println("debug: class find in bundle classLoader");
73 | return clazz;
74 | }
75 | } catch (Exception e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | throw new ClassCastException(className + " not found exception");
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/src/net/mobctrl/hostapk/BundleDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import dalvik.system.DexClassLoader;
4 |
5 | /**
6 | * @Author Zheng Haibo
7 | * @Company Alibaba Group
8 | * @PersonalWebsite http://www.mobctrl.net
9 | * @version $Id: BundleDexClassLoader.java, v 0.1 2015年12月11日 下午7:12:49 mochuan.zhb Exp $
10 | * @Description bundle的类加载器
11 | */
12 | public class BundleDexClassLoader extends DexClassLoader {
13 |
14 | public BundleDexClassLoader(String dexPath, String optimizedDirectory,
15 | String libraryPath, ClassLoader parent) {
16 | super(dexPath, optimizedDirectory, libraryPath, parent);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/src/net/mobctrl/hostapk/BundlerResourceLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 |
5 | import android.content.Context;
6 | import android.content.res.AssetManager;
7 | import android.content.res.Resources;
8 |
9 | /**
10 | * @Author Zheng Haibo
11 | * @PersonalWebsite http://www.mobctrl.net
12 | * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
13 | * Exp $
14 | * @Description 动态加载资源的管理器
15 | */
16 | public class BundlerResourceLoader {
17 |
18 | private static AssetManager createAssetManager(String apkPath) {
19 | try {
20 | AssetManager assetManager = AssetManager.class.newInstance();
21 | try {
22 | AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
23 | assetManager, apkPath);
24 | } catch (Throwable th) {
25 | System.out.println("debug:createAssetManager :"+th.getMessage());
26 | th.printStackTrace();
27 | }
28 | return assetManager;
29 | } catch (Throwable th) {
30 | System.out.println("debug:createAssetManager :"+th.getMessage());
31 | th.printStackTrace();
32 | }
33 | return null;
34 | }
35 |
36 | /**
37 | * 获取Bundle中的资源
38 | * @param context
39 | * @param apkPath
40 | * @return
41 | */
42 | public static Resources getBundleResource(Context context){
43 | AssetsManager.copyAllAssetsApk(context);
44 | File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
45 | String apkPath = dir.getAbsolutePath()+"/BundleApk.apk";
46 | System.out.println("debug:apkPath = "+apkPath+",exists="+(new File(apkPath).exists()));
47 | AssetManager assetManager = createAssetManager(apkPath);
48 | return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/HostApk/src/net/mobctrl/hostapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.content.res.Resources;
6 | import android.graphics.drawable.Drawable;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 | import android.widget.Toast;
13 |
14 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
15 | public class MainActivity extends Activity {
16 |
17 | public static final String TAG = "MainActivity";
18 |
19 | private TextView invokeTv;
20 | private ImageView imageView;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_main);
26 | BundleClassLoaderManager.install(getApplicationContext());
27 | invokeTv = (TextView) findViewById(R.id.invoke_tv);
28 | invokeTv.setOnClickListener(new View.OnClickListener() {
29 |
30 | @Override
31 | public void onClick(View view) {
32 |
33 | }
34 | });
35 | Resources resources = BundlerResourceLoader.getBundleResource(getApplicationContext());
36 | imageView = (ImageView)findViewById(R.id.image_view_iv);
37 | if(resources != null){
38 | String str = resources.getString(resources.getIdentifier("test_str", "string", "net.mobctrl.normal.apk"));
39 | String strById = resources.getString(0x7f050001);//注意,id参照Bundle apk中的R文件
40 | System.out.println("debug:"+str);
41 | Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show();
42 |
43 | Drawable drawable = resources.getDrawable(0x7f020000);//注意,id参照Bundle apk中的R文件
44 | imageView.setImageDrawable(drawable);
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/第三课-Resource资源文件的加载/README.md:
--------------------------------------------------------------------------------
1 | #Android加载插件apk中的Resource资源
2 |
3 |
4 | ##Author:莫川
5 |
6 | ##简介
7 | 如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了。总结如下:
8 |
9 | - 1.新建一个AssetManager对象
10 | - 2.通过反射调用addAssetPath方法
11 | - 3.以AssetsManager对象为参数,创建Resources对象即可。
12 |
13 | 代码如下:
14 | ```java
15 | package net.mobctrl.hostapk;
16 |
17 | import java.io.File;
18 |
19 | import android.content.Context;
20 | import android.content.res.AssetManager;
21 | import android.content.res.Resources;
22 |
23 | /**
24 | * @Author Zheng Haibo
25 | * @PersonalWebsite http://www.mobctrl.net
26 | * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
27 | * Exp $
28 | * @Description 动态加载资源的管理器
29 | */
30 | public class BundlerResourceLoader {
31 |
32 | private static AssetManager createAssetManager(String apkPath) {
33 | try {
34 | AssetManager assetManager = AssetManager.class.newInstance();
35 | try {
36 | AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
37 | assetManager, apkPath);
38 | } catch (Throwable th) {
39 | System.out.println("debug:createAssetManager :"+th.getMessage());
40 | th.printStackTrace();
41 | }
42 | return assetManager;
43 | } catch (Throwable th) {
44 | System.out.println("debug:createAssetManager :"+th.getMessage());
45 | th.printStackTrace();
46 | }
47 | return null;
48 | }
49 |
50 | /**
51 | * 获取Bundle中的资源
52 | * @param context
53 | * @param apkPath
54 | * @return
55 | */
56 | public static Resources getBundleResource(Context context){
57 | AssetsManager.copyAllAssetsApk(context);
58 | File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
59 | String apkPath = dir.getAbsolutePath()+"/BundleApk.apk";
60 | System.out.println("debug:apkPath = "+apkPath+",exists="+(new File(apkPath).exists()));
61 | AssetManager assetManager = createAssetManager(apkPath);
62 | return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
63 | }
64 |
65 | }
66 |
67 | ```
68 |
69 | ##DEMO
70 | 注意:我们使用Resources对象,获取资源时,传递的ID必须是离线apk中R文件对应的资源的ID。如果使用getIdentifier方法,第一个参数是资源名称,第二个参数是资源类型,第三个参数是离线apk的包名,切记第三个参数。
71 |
72 | ```java
73 | Resources resources = BundlerResourceLoader.getBundleResource(getApplicationContext());
74 | imageView = (ImageView)findViewById(R.id.image_view_iv);
75 | if(resources != null){
76 | String str = resources.getString(resources.getIdentifier("test_str", "string", "net.mobctrl.normal.apk"));
77 | String strById = resources.getString(0x7f050001);//注意,id参照Bundle apk中的R文件
78 | System.out.println("debug:"+str);
79 | Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show();
80 |
81 | Drawable drawable = resources.getDrawable(0x7f020000);//注意,id参照Bundle apk中的R文件
82 | imageView.setImageDrawable(drawable);
83 | }
84 | ```
85 |
86 | 上述代码是加载离线apk中的字符串和Drawable资源,那么layout资源呢?
87 |
88 | ##问题引入
89 | 我们使用LayoutInflate对象,一般使用方法如下:
90 | ```java
91 | View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);
92 | ```
93 | 其中,R.layout.main_fragment我们可以通过上述方法获取其ID,那么关键的一步就是如何生成一个context?直接传入当前的context是不行的。
94 | 解决方案有2个:
95 | - 1.创建一个自己的ContextImpl,Override其方法。
96 | - 2.通过反射,直接替换当前context的mResources私有成员变量。
97 | 当然,我们是使用第二种方案:
98 |
99 | ```java
100 | @Override
101 | protected void attachBaseContext(Context context) {
102 | replaceContextResources(context);
103 | super.attachBaseContext(context);
104 | }
105 |
106 | /**
107 | * 使用反射的方式,使用Bundle的Resource对象,替换Context的mResources对象
108 | * @param context
109 | */
110 | public void replaceContextResources(Context context){
111 | try {
112 | Field field = context.getClass().getDeclaredField("mResources");
113 | field.setAccessible(true);
114 | field.set(context, mBundleResources);
115 | System.out.println("debug:repalceResources succ");
116 | } catch (Exception e) {
117 | System.out.println("debug:repalceResources error");
118 | e.printStackTrace();
119 | }
120 | }
121 | ```
122 |
123 |
124 | 我们在Activity的attachBaseContext方法中,对Context的mResources进行替换,这样,我们就可以加载离线apk中的布局了。
125 |
126 | ##资源文件的打包过程
127 |
128 | 如果想要做到插件化,需要了解Android资源文件的打包过程,这样可以为每一个插件进行编号,然后按照规则生成R文件。例如,以携程DynamicAPK为例,它将插件的R文件按照如下规则:
129 |
130 | - 1.R文件为int型,前8位代表插件的Id,其中两个特殊的Id:Host是0x7f,android系统自带的是以0x01开头.
131 | - 2.紧跟着的8位是区分资源类型的,比如layout,id,string,dimen等
132 | - 3.后面16位是资源的编号
133 |
134 | 按照上述规则生成对应的插件apk。然后在运行时,我们可以写一个ResourceManager类,它继承自Resource对象,然后所有的Activity,都将其context的mResource成员变量修改为ResourceManager类,然后Override其方法,然后在加载资源时,根据不同的id的前缀,查找对应插件的Resource即可。也就是说,用一个类做分发。
135 |
136 |
137 | ##源码
138 |
139 | https://github.com/nuptboyzhb/AndroidPluginFramework
140 |
141 | ##Android插件化相关资料
142 |
143 | - 1.Android动态加载基础 ClassLoader工作机制 http://segmentfault.com/a/1190000004062880
144 | - 2.Android动态加载黑科技 动态创建Activity模式 http://segmentfault.com/a/1190000004077469
145 | - 3.Android插件化框架Github总结 https://github.com/liaohuqiu/android-dynamic-load-awesome
146 | - 4.携程动态加载框架源码 https://github.com/CtripMobile/DynamicAPK
147 | - 5.携程Android App插件化和动态加载实践 http://www.infoq.com/cn/articles/ctrip-android-dynamic-loading
148 | - 6.dex分包变形记 http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=401345907&idx=1http://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=401345907&idx=1&sn=debdddf25950aaaa10f575472629b557
149 | - 7.各大热补丁方案分析和比较 http://blog.zhaiyifan.cn/2015/11/20/HotPatchCompare/
150 | - 8.Android App 线上热修复方案 http://lirenlong.github.io/hotfix/
151 | - 9.Android 热补丁动态修复框架小结 http://blog.csdn.net/lmj623565791/article/details/49883661
152 | - 10.Android热更新实现原理 http://blog.csdn.net/lzyzsd/article/details/49843581
153 | - 11.【新技能get】让App像Web一样发布新版本 http://bugly.qq.com/blog/?p=781
154 | - 12.Android动态加载技术 系列索引 http://segmentfault.com/a/1190000004086213
155 | - 13.Android对第三方类库运行时加载 http://blog.csdn.net/dzg1977/article/details/41683173
156 | - 14.关于Android如何动态加载res http://nobodycare.me/2014/11/07/about-loading-res-from-apk-directly/
157 | - 15.Android应用程序资源的编译和打包过程分析 http://blog.csdn.net/luoshengyang/article/details/8744683
158 | - 16.Android应用程序资源管理器(Asset Manager)的创建过程分析 http://blog.csdn.net/luoshengyang/article/details/8791064
159 | - 17.Android 自动编译、打包生成apk文件 1 - 命令行方式 http://blog.csdn.net/androiddevelop/article/details/10948639
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/BundleApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HelloWorld
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/src/net/mobctrl/normal/apk/FileUtils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: FileUtils.java, v 0.1 2015年12月10日 下午2:30:40 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class FileUtils {
14 |
15 | public void write(){
16 |
17 | }
18 |
19 | public void print(Context context, String name) {
20 | Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/BundleApk/src/net/mobctrl/normal/apk/Utils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: Utils.java, v 0.1 2015年12月10日 下午2:25:39 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class Utils {
14 |
15 | /**
16 | * 计算 a+b
17 | *
18 | * @param context
19 | * @param a
20 | * @param b
21 | * @param name
22 | */
23 | public int printSum(Context context,int a,int b,String name){
24 | int sum = a + b;
25 | Toast.makeText(context, name+":"+sum, Toast.LENGTH_SHORT).show();
26 | return sum;
27 | }
28 |
29 | public void printFileName(Context context,String name){
30 | new FileUtils().print(context,name);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HostApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
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 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第二课-使用DexClassLoader加载普通apk/HostApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HostApk
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/src/net/mobctrl/hostapk/AssetsManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 |
8 | import android.content.Context;
9 | import android.content.res.AssetManager;
10 | import android.util.Log;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
17 | * @Description
18 | */
19 | public class AssetsManager {
20 |
21 | public static final String TAG = "AssetsApkLoader";
22 |
23 | //从assets复制出去的apk的目标目录
24 | public static final String APK_DIR = "third_apk";
25 |
26 | //文件结尾过滤
27 | public static final String FILE_FILTER = ".apk";
28 |
29 |
30 | /**
31 | * 将资源文件中的apk文件拷贝到私有目录中
32 | *
33 | * @param context
34 | */
35 | public static void copyAllAssetsApk(Context context) {
36 |
37 | AssetManager assetManager = context.getAssets();
38 | long startTime = System.currentTimeMillis();
39 | try {
40 | File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
41 | dex.mkdir();
42 | String []fileNames = assetManager.list("");
43 | for(String fileName:fileNames){
44 | if(!fileName.endsWith(FILE_FILTER)){
45 | return;
46 | }
47 | InputStream in = null;
48 | OutputStream out = null;
49 | in = assetManager.open(fileName);
50 | File f = new File(dex, fileName);
51 | if (f.exists() && f.length() == in.available()) {
52 | Log.i(TAG, fileName+"no change");
53 | return;
54 | }
55 | Log.i(TAG, fileName+" chaneged");
56 | out = new FileOutputStream(f);
57 | byte[] buffer = new byte[2048];
58 | int read;
59 | while ((read = in.read(buffer)) != -1) {
60 | out.write(buffer, 0, read);
61 | }
62 | in.close();
63 | in = null;
64 | out.flush();
65 | out.close();
66 | out = null;
67 | Log.i(TAG, fileName+" copy over");
68 | }
69 | Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
70 | } catch (Exception e) {
71 | e.printStackTrace();
72 | }
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/src/net/mobctrl/hostapk/BundleClassLoaderManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import android.annotation.TargetApi;
9 | import android.content.Context;
10 | import android.os.Build;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @PersonalWebsite http://www.mobctrl.net
15 | * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
16 | * mochuan.zhb Exp $
17 | * @Description
18 | */
19 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
20 | public class BundleClassLoaderManager {
21 |
22 | public static List bundleDexClassLoaderList = new ArrayList();
23 |
24 | /**
25 | * 加载Assets里的apk文件
26 | * @param context
27 | */
28 | public static void install(Context context) {
29 | AssetsManager.copyAllAssetsApk(context);
30 | // 获取dex文件列表
31 | File dexDir = context.getDir(AssetsManager.APK_DIR,
32 | Context.MODE_PRIVATE);
33 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
34 |
35 | @Override
36 | public boolean accept(File dir, String filename) {
37 | return filename.endsWith(AssetsManager.FILE_FILTER);
38 | }
39 | });
40 | for (File f : szFiles) {
41 | System.out.println("debug:load file:" + f.getName());
42 | BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
43 | f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
44 | context.getClassLoader());
45 | bundleDexClassLoaderList.add(bundleDexClassLoader);
46 | }
47 | }
48 |
49 | /**
50 | * 查找类
51 | *
52 | * @param className
53 | * @return
54 | * @throws ClassNotFoundException
55 | */
56 | public static Class> loadClass(Context context,String className) throws ClassNotFoundException {
57 | try {
58 | Class> clazz = context.getClassLoader().loadClass(className);
59 | if (clazz != null) {
60 | System.out.println("debug: class find in main classLoader");
61 | return clazz;
62 | }
63 | } catch (Exception e) {
64 | e.printStackTrace();
65 | }
66 | for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
67 | try {
68 | Class> clazz = bundleDexClassLoader.loadClass(className);
69 | if (clazz != null) {
70 | System.out.println("debug: class find in bundle classLoader");
71 | return clazz;
72 | }
73 | } catch (Exception e) {
74 | e.printStackTrace();
75 | }
76 | }
77 | throw new ClassCastException(className + " not found exception");
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/src/net/mobctrl/hostapk/BundleDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import dalvik.system.DexClassLoader;
4 |
5 | /**
6 | * @Author Zheng Haibo
7 | * @Company Alibaba Group
8 | * @PersonalWebsite http://www.mobctrl.net
9 | * @version $Id: BundleDexClassLoader.java, v 0.1 2015年12月11日 下午7:12:49 mochuan.zhb Exp $
10 | * @Description bundle的类加载器
11 | */
12 | public class BundleDexClassLoader extends DexClassLoader {
13 |
14 | public BundleDexClassLoader(String dexPath, String optimizedDirectory,
15 | String libraryPath, ClassLoader parent) {
16 | super(dexPath, optimizedDirectory, libraryPath, parent);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/HostApk/src/net/mobctrl/hostapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.lang.reflect.Constructor;
4 | import java.lang.reflect.InvocationTargetException;
5 | import java.lang.reflect.Method;
6 |
7 | import android.annotation.TargetApi;
8 | import android.app.Activity;
9 | import android.content.Context;
10 | import android.os.Build;
11 | import android.os.Bundle;
12 | import android.view.View;
13 | import android.widget.TextView;
14 |
15 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
16 | public class MainActivity extends Activity {
17 |
18 | public static final String TAG = "MainActivity";
19 |
20 | private TextView invokeTv;
21 |
22 | @Override
23 | protected void onCreate(Bundle savedInstanceState) {
24 | super.onCreate(savedInstanceState);
25 | setContentView(R.layout.activity_main);
26 | BundleClassLoaderManager.install(getApplicationContext());
27 | invokeTv = (TextView) findViewById(R.id.invoke_tv);
28 | invokeTv.setOnClickListener(new View.OnClickListener() {
29 |
30 | @Override
31 | public void onClick(View view) {
32 | loadApk();
33 | }
34 | });
35 |
36 | }
37 |
38 | @Override
39 | protected void attachBaseContext(Context newBase) {
40 |
41 | super.attachBaseContext(newBase);
42 | }
43 |
44 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
45 | private void loadApk() {
46 | try {
47 | Class> clazz = BundleClassLoaderManager.loadClass(getApplicationContext(),
48 | "net.mobctrl.normal.apk.Utils");
49 | Constructor> constructor = clazz.getConstructor();
50 | Object bundleUtils = constructor.newInstance();
51 |
52 | Method printSumMethod = clazz.getMethod("printSum", Context.class,
53 | int.class, int.class, String.class);
54 | printSumMethod.setAccessible(true);
55 | Integer sum = (Integer) printSumMethod.invoke(bundleUtils,
56 | getApplicationContext(), 10, 20, "计算结果");
57 | System.out.println("debug:sum = " + sum);
58 | } catch (ClassNotFoundException e) {
59 | e.printStackTrace();
60 | } catch (SecurityException e) {
61 | e.printStackTrace();
62 | } catch (NoSuchMethodException e) {
63 | e.printStackTrace();
64 | } catch (IllegalArgumentException e) {
65 | e.printStackTrace();
66 | } catch (InstantiationException e) {
67 | e.printStackTrace();
68 | } catch (IllegalAccessException e) {
69 | e.printStackTrace();
70 | } catch (InvocationTargetException e) {
71 | e.printStackTrace();
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/第二课-使用DexClassLoader加载普通apk/README.md:
--------------------------------------------------------------------------------
1 | #Android插件化(二):使用DexClassLoader动态加载assets中的apk#
2 |
3 | ##Author:莫川
4 |
5 | ##简介
6 | 上一篇博客讲到,我们可以使用MultiDex.java加载离线的apk文件。需要注意的是,apk中的类是加载到当前的PathClassLoader当中的,如果apk文件过多,可能会出现ANR的情况。那么,我们能不能使用DexClassLoader加载apk呢?当然是可以的!首先看一下[Doc文档](http://developer.android.com/intl/zh-cn/reference/dalvik/system/DexClassLoader.html).
7 |
8 | ```html
9 | A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
10 | ```
11 |
12 | 也就是说,DexClassLoader可以加载一个含有classes.dex文件的压缩包,既可以是jar也可以是apk。那么加载一个离线的apk文件需要注意哪些呢?
13 |
14 | - 1.DexClassLoader的构造方法:
15 | DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
16 |
17 | - 2.私有目录
18 | This class loader requires an application-private, writable directory to cache optimized classes.
19 |
20 | 了解到上述两点,我们就可以根据DexClassLoader所需要的参数,动态加载assets中的apk了。
21 |
22 | ##源码
23 |
24 | ### BundleClassLoaderManager
25 | 该类主要是负责管理这些DexClassLoader的,首先,我们定义了一个叫做BundleDexClassLoader的类,它继承自DexClassLoader,用于加载离线的apk文件。每一个apk文件对应一个BundleDexClassLoader,而BundleClassLoaderManager则保存了一个List,在加载的时候,用于查找类。具体代码如下:
26 |
27 | ```java
28 | package net.mobctrl.hostapk;
29 |
30 | import java.io.File;
31 | import java.io.FilenameFilter;
32 | import java.util.ArrayList;
33 | import java.util.List;
34 |
35 | import android.annotation.TargetApi;
36 | import android.content.Context;
37 | import android.os.Build;
38 |
39 | /**
40 | * @Author Zheng Haibo
41 | * @PersonalWebsite http://www.mobctrl.net
42 | * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
43 | * mochuan.zhb Exp $
44 | * @Description
45 | */
46 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
47 | public class BundleClassLoaderManager {
48 |
49 | public static List bundleDexClassLoaderList = new ArrayList();
50 |
51 | /**
52 | * 加载Assets里的apk文件
53 | * @param context
54 | */
55 | public static void install(Context context) {
56 | AssetsManager.copyAllAssetsApk(context);
57 | // 获取dex文件列表
58 | File dexDir = context.getDir(AssetsManager.APK_DIR,
59 | Context.MODE_PRIVATE);
60 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
61 |
62 | @Override
63 | public boolean accept(File dir, String filename) {
64 | return filename.endsWith(AssetsManager.FILE_FILTER);
65 | }
66 | });
67 | for (File f : szFiles) {
68 | System.out.println("debug:load file:" + f.getName());
69 | BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
70 | f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
71 | context.getClassLoader());
72 | bundleDexClassLoaderList.add(bundleDexClassLoader);
73 | }
74 | }
75 |
76 | /**
77 | * 查找类
78 | *
79 | * @param className
80 | * @return
81 | * @throws ClassNotFoundException
82 | */
83 | public static Class> loadClass(Context context,String className) throws ClassNotFoundException {
84 | try {
85 | Class> clazz = context.getClassLoader().loadClass(className);
86 | if (clazz != null) {
87 | System.out.println("debug: class find in main classLoader");
88 | return clazz;
89 | }
90 | } catch (Exception e) {
91 | e.printStackTrace();
92 | }
93 | for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
94 | try {
95 | Class> clazz = bundleDexClassLoader.loadClass(className);
96 | if (clazz != null) {
97 | System.out.println("debug: class find in bundle classLoader");
98 | return clazz;
99 | }
100 | } catch (Exception e) {
101 | e.printStackTrace();
102 | }
103 | }
104 | throw new ClassCastException(className + " not found exception");
105 | }
106 | }
107 |
108 | ```
109 |
110 | 注意点:
111 |
112 | - 1.install方法
113 | install方法主要是将assets中的apk全部拷贝到私有目录,然后再遍历私有目录,使用BundleDexClassLoader加载apk文件,然后将这些BundleDexClassLoader保存到数组中。
114 |
115 | - 2.loadClass方法
116 | 该方法先从当前的ClassLoader中查找需要的类,如果找不到,在从List中遍历查找。
117 |
118 | ### DEMO运行
119 | 在MainActivity中,我们可以通过如下方式,调用apk类中的方法:
120 | ```java
121 | private void loadApk() {
122 | try {
123 | Class> clazz = BundleClassLoaderManager.loadClass(getApplicationContext(),
124 | "net.mobctrl.normal.apk.Utils");
125 | Constructor> constructor = clazz.getConstructor();
126 | Object bundleUtils = constructor.newInstance();
127 |
128 | Method printSumMethod = clazz.getMethod("printSum", Context.class,
129 | int.class, int.class, String.class);
130 | printSumMethod.setAccessible(true);
131 | Integer sum = (Integer) printSumMethod.invoke(bundleUtils,
132 | getApplicationContext(), 10, 20, "计算结果");
133 | System.out.println("debug:sum = " + sum);
134 | } catch (ClassNotFoundException e) {
135 | e.printStackTrace();
136 | } catch (SecurityException e) {
137 | e.printStackTrace();
138 | } catch (NoSuchMethodException e) {
139 | e.printStackTrace();
140 | } catch (IllegalArgumentException e) {
141 | e.printStackTrace();
142 | } catch (InstantiationException e) {
143 | e.printStackTrace();
144 | } catch (IllegalAccessException e) {
145 | e.printStackTrace();
146 | } catch (InvocationTargetException e) {
147 | e.printStackTrace();
148 | }
149 | }
150 | ```
151 |
152 | 与MultiDex不同时,我们是通过BundleClassLoaderManager来加载类的,而不是当前的ClassLoader。
153 |
154 | ### 改进方案
155 | 正如BundleClassLoaderManager中的loadClass方法,其实我们创建一个ClassLoader对象,通过重写当前ClassLoader的findClass方法即可,然后在Override的findClass方法中,首先从当前ClassLoader中查找类,然后再从BundleDexClassLoader中遍历查找,这样既可以在Host项目中调用Bundle中的类,也能够在Bundle中调用Host中的类。
156 |
157 | ```java
158 |
159 | mClassLoader = new ClassLoader(super.getClassLoader()) {
160 |
161 | @Override
162 | protected Class> findClass(String className)
163 | throws ClassNotFoundException {
164 | Class clazz = BundleClassLoaderManager.loadClass(context,className);
165 | if (clazz == null) {
166 | throw new ClassNotFoundException(className);
167 | }
168 | return clazz;
169 | }
170 | };
171 |
172 | ```
173 |
174 | ##总结
175 | 上一篇博客和这一篇博客将的都是类的加载。如果所需要加载的类都是工具类,不需要加载资源等,那么上面的方案都没啥问题。但是如果加载的类是Fragment或者Activity等UI,需要引用资源文件,这又改如何处理呢?
176 |
177 | 下一篇博文:Android资源的离线加载。
178 |
179 |
180 | ##源码
181 |
182 | https://github.com/nuptboyzhb/AndroidPluginFramework
183 |
184 | ##参考
185 | 1.DexClassLoader源码
186 | 2.DexClassLoader用法
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleApk5
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/ic_launcher-web.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xxhdpi/bundle_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xxhdpi/bundle_img.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/BundleApk5/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/layout/bundle_layout.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bundle
5 | I am in bundle str
6 | Settings
7 |
8 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/src/net/mobctrl/normal/apk/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.app.Activity;
4 | import android.content.res.Resources;
5 |
6 | /**
7 | * @Author Zheng Haibo (mochuan)
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: BaseActivity.java, v 0.1 2016年1月8日 下午5:32:37 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class BaseActivity extends Activity {
14 |
15 | @Override
16 | public Resources getResources() {
17 | return getApplication().getResources();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/src/net/mobctrl/normal/apk/BundleActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 |
6 | /**
7 | * @Author Zheng Haibo (mochuan)
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: BundleActivity.java, v 0.1 2016年1月8日 下午1:21:46 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class BundleActivity extends BaseActivity {
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.bundle_layout);
19 | findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() {
20 |
21 | @Override
22 | public void onClick(View view) {
23 |
24 | }
25 | });
26 | }
27 |
28 |
29 |
30 | }
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/src/net/mobctrl/normal/apk/FileUtils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Mail mochuan.zhb@alibaba-inc.com
9 | * @Company Alibaba Group
10 | * @PersonalWebsite http://www.mobctrl.net
11 | * @version $Id: FileUtils.java, v 0.1 2015年12月10日 下午2:30:40 mochuan.zhb Exp $
12 | * @Description
13 | */
14 | public class FileUtils {
15 |
16 | public void write(){
17 |
18 | }
19 |
20 | public void print(Context context, String name) {
21 | Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleApk5/src/net/mobctrl/normal/apk/Utils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Mail mochuan.zhb@alibaba-inc.com
9 | * @Company Alibaba Group
10 | * @PersonalWebsite http://www.mobctrl.net
11 | * @version $Id: Utils.java, v 0.1 2015年12月10日 下午2:25:39 mochuan.zhb Exp $
12 | * @Description
13 | */
14 | public class Utils {
15 |
16 | /**
17 | * 计算 a+b
18 | *
19 | * @param context
20 | * @param a
21 | * @param b
22 | * @param name
23 | */
24 | public int printSum(Context context,int a,int b,String name){
25 | int sum = a + b;
26 | Toast.makeText(context, name+":"+sum, Toast.LENGTH_SHORT).show();
27 | return sum;
28 | }
29 |
30 | public void printFileName(Context context,String name){
31 | new FileUtils().print(context,name);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleBuilder
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.6
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.6
12 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/1.bat:
--------------------------------------------------------------------------------
1 | D:\mochuan.zhb\android-sdks\build-tools\22.0.1\aapt_alitrip.exe package -f -m -J C:\Users\mochuan.zhb\newworkspace\BundleApk5\gen -S C:\Users\mochuan.zhb\newworkspace\BundleApk5\res -M C:\Users\mochuan.zhb\newworkspace\BundleApk5\AndroidManifest.xml -A C:\Users\mochuan.zhb\newworkspace\BundleApk5\assets -I D:\android_sdk_for_studio\platforms\android-22\android.jar --non-constant-id -x --package-id 5
2 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/2.bat:
--------------------------------------------------------------------------------
1 | javac -target 1.5 -bootclasspath D:\android_sdk_for_studio\platforms\android-22\android.jar -d C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin C:\Users\mochuan.zhb\newworkspace\BundleApk5\src\net\mobctrl\normal\apk\BaseActivity.java C:\Users\mochuan.zhb\newworkspace\BundleApk5\src\net\mobctrl\normal\apk\BundleActivity.java C:\Users\mochuan.zhb\newworkspace\BundleApk5\src\net\mobctrl\normal\apk\FileUtils.java C:\Users\mochuan.zhb\newworkspace\BundleApk5\src\net\mobctrl\normal\apk\Utils.java C:\Users\mochuan.zhb\newworkspace\BundleApk5\gen\net\mobctrl\normal\apk\BuildConfig.java C:\Users\mochuan.zhb\newworkspace\BundleApk5\gen\net\mobctrl\normal\apk\R.java -classpath C:\Users\mochuan.zhb\newworkspace\BundleApk5\libs\.*jar
2 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/3.bat:
--------------------------------------------------------------------------------
1 | D:\android_sdk_for_studio\build-tools\22.0.1\dx.bat --dex --output=C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin
2 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/4.bat:
--------------------------------------------------------------------------------
1 | D:\mochuan.zhb\android-sdks\build-tools\22.0.1\aapt_alitrip.exe package -f -M C:\Users\mochuan.zhb\newworkspace\BundleApk5\AndroidManifest.xml -S C:\Users\mochuan.zhb\newworkspace\BundleApk5\res -I D:\android_sdk_for_studio\platforms\android-22\android.jar -A C:\Users\mochuan.zhb\newworkspace\BundleApk5\assets -F C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\resources.ap_ --non-constant-id -x --package-id 5
2 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/5.bat:
--------------------------------------------------------------------------------
1 | java -cp D:\android_sdk_for_studio\tools\lib\sdklib.jar com.android.sdklib.build.ApkBuilderMain C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\unsigned.apk -v -u -z C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\resources.ap_ -f C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex -rf C:\Users\mochuan.zhb\newworkspace\BundleApk5\src
2 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/batch/build.bat:
--------------------------------------------------------------------------------
1 | call C:\Users\mochuan.zhb\newworkspace\BundleBuilder\batch\1.bat
2 | call C:\Users\mochuan.zhb\newworkspace\BundleBuilder\batch\2.bat
3 | call C:\Users\mochuan.zhb\newworkspace\BundleBuilder\batch\3.bat
4 | call C:\Users\mochuan.zhb\newworkspace\BundleBuilder\batch\4.bat
5 | call C:\Users\mochuan.zhb\newworkspace\BundleBuilder\batch\5.bat
6 |
7 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/src/com/taobao/trip/BuildApkUtils.java:
--------------------------------------------------------------------------------
1 | package com.taobao.trip;
2 |
3 | import java.io.File;
4 | import java.io.FileWriter;
5 | import java.io.IOException;
6 | import java.io.PrintWriter;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Comparator;
10 | import java.util.List;
11 |
12 | /**
13 | * @Author Zheng Haibo (mochuan)
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: BuildUtils.java, v 0.1 2016年1月8日 下午8:47:42 mochuan.zhb Exp $
17 | * @Description
18 | */
19 | public class BuildApkUtils {
20 |
21 | private static final String ANDROID_JAR_PATH = "D:\\android_sdk_for_studio\\platforms\\android-22\\android.jar";
22 |
23 | private static final String AAPT_PATH = "D:\\mochuan.zhb\\android-sdks\\build-tools\\22.0.1\\aapt_alitrip.exe";
24 |
25 | private static final String DX_PATH = "D:\\android_sdk_for_studio\\build-tools\\22.0.1\\dx.bat";
26 |
27 | private static final String SDK_LIB_JAR_PATH = "D:\\android_sdk_for_studio\\tools\\lib\\sdklib.jar";
28 |
29 | private static final String batchDir = System.getProperty("user.dir")
30 | + "\\batch\\";
31 |
32 | private String projectDir;
33 | private int packageId = 127;
34 |
35 | public BuildApkUtils() {
36 |
37 | }
38 |
39 | public BuildApkUtils(String projectDir) {
40 | this.projectDir = projectDir;
41 | }
42 |
43 | public BuildApkUtils(String projectDir, int packageId) {
44 | this.projectDir = projectDir;
45 | this.packageId = packageId;
46 | }
47 |
48 | public void buildUnsingedApk() {
49 | clearDir(batchDir);
50 | clearDir(projectDir + "\\bin");
51 | generateR(projectDir, packageId);
52 | compileJavaFiles(projectDir);
53 | buildDexFile(projectDir);
54 | complieResources(projectDir, packageId);
55 | buildUnsignedApk(projectDir, "unsigned.apk");
56 | mergeExeBatchFiles();
57 | }
58 |
59 | /**
60 | * 第一步:产生R文件
61 | *
62 | * @param projectDir
63 | * @param packageId
64 | */
65 | private static void generateR(String projectDir, int packageId) {
66 | StringBuffer command = new StringBuffer();
67 | command.append(AAPT_PATH).append(" package -f -m -J ")
68 | .append(projectDir).append("\\gen ").append("-S ")
69 | .append(projectDir).append("\\res ").append("-M ")
70 | .append(projectDir).append("\\AndroidManifest.xml ")
71 | .append(" -A ").append(projectDir).append("\\assets ")
72 | .append("-I ").append(ANDROID_JAR_PATH)
73 | .append(" --non-constant-id -x --package-id ")
74 | .append(packageId);
75 | buildExeBatchFiles(command.toString(), "1.bat");
76 | }
77 |
78 | /**
79 | * 编译java文件
80 | *
81 | * @param projectDir
82 | */
83 | private static void compileJavaFiles(String projectDir) {
84 | StringBuffer command = new StringBuffer();
85 | command.append("javac -target 1.5 -bootclasspath ")
86 | .append(ANDROID_JAR_PATH).append(" -d ").append(projectDir)
87 | .append("\\bin ");
88 | List javaFilePaths = new ArrayList();
89 | findJavaFiles(projectDir + "\\src", javaFilePaths);
90 | findJavaFiles(projectDir + "\\gen", javaFilePaths);
91 | for (String javaPath : javaFilePaths) {
92 | command.append(javaPath).append(" ");
93 | }
94 | command.append("-classpath ").append(projectDir)
95 | .append("\\libs\\.*jar");
96 | buildExeBatchFiles(command.toString(), "2.bat");
97 | }
98 |
99 | /**
100 | * 创建dex文件
101 | *
102 | * @param projectDir
103 | */
104 | private static void buildDexFile(String projectDir) {
105 | StringBuffer command = new StringBuffer();
106 | command.append(DX_PATH).append(" --dex --output=").append(projectDir)
107 | .append("\\bin\\classes.dex").append(" ").append(projectDir)
108 | .append("\\bin");
109 | buildExeBatchFiles(command.toString(), "3.bat");
110 | }
111 |
112 | /**
113 | * 编译资源文件
114 | *
115 | * @param projectDir
116 | */
117 | private static void complieResources(String projectDir, int packageId) {
118 | StringBuffer command = new StringBuffer();
119 | command.append(AAPT_PATH).append(" package -f -M ").append(projectDir)
120 | .append("\\AndroidManifest.xml ").append("-S ")
121 | .append(projectDir).append("\\res ").append("-I ")
122 | .append(ANDROID_JAR_PATH).append(" -A ").append(projectDir)
123 | .append("\\assets ").append(" -F ").append(projectDir)
124 | .append("\\bin\\resources.ap_")
125 | .append(" --non-constant-id -x --package-id ")
126 | .append(packageId);
127 | buildExeBatchFiles(command.toString(), "4.bat");
128 | }
129 |
130 | /**
131 | * 生成未签名的apk
132 | *
133 | * @param projectDir
134 | * @param apkName
135 | */
136 | private static void buildUnsignedApk(String projectDir, String apkName) {
137 | StringBuffer command = new StringBuffer();
138 | command.append("java -cp ").append(SDK_LIB_JAR_PATH)
139 | .append(" com.android.sdklib.build.ApkBuilderMain ")
140 | .append(projectDir).append("\\bin\\").append(apkName)
141 | .append(" -v -u -z ").append(projectDir)
142 | .append("\\bin\\resources.ap_").append(" -f ")
143 | .append(projectDir).append("\\bin\\classes.dex")
144 | .append(" -rf ").append(projectDir).append("\\src");
145 | buildExeBatchFiles(command.toString(), "5.bat");
146 | }
147 |
148 | /**
149 | * 递归查找
150 | *
151 | * @param projectDir
152 | * @param javaFilePaths
153 | */
154 | private static void findJavaFiles(String projectDir,
155 | List javaFilePaths) {
156 | File file = new File(projectDir);
157 | File[] files = file.listFiles();
158 | if (files == null || files.length == 0) {
159 | return;
160 | }
161 | for (File f : files) {
162 | if (f.isDirectory()) {
163 | findJavaFiles(f.getAbsolutePath(), javaFilePaths);
164 | } else {
165 | if (f.getAbsolutePath().endsWith(".java")) {
166 | javaFilePaths.add(f.getAbsolutePath());
167 | }
168 | }
169 | }
170 | }
171 |
172 | /**
173 | * 清理目录
174 | *
175 | * @param projectDir
176 | */
177 | private static void clearDir(String projectDir) {
178 | File file = new File(projectDir);
179 | File[] files = file.listFiles();
180 | if (files == null || files.length == 0) {
181 | return;
182 | }
183 | for (File f : files) {
184 | if (f.isDirectory()) {
185 | clearDir(f.getAbsolutePath());
186 | } else {
187 | f.delete();
188 | }
189 | }
190 | }
191 |
192 | /**
193 | * 创建批处理文件
194 | *
195 | * @param command
196 | * @param file
197 | */
198 | private static void buildExeBatchFiles(String command, String fileName) {
199 | System.out.println(command);
200 | if (!new File(batchDir).exists()) {
201 | new File(batchDir).mkdirs();
202 | }
203 | String filePath = batchDir + fileName;
204 | try {
205 | writeFile(filePath, command);
206 | } catch (IOException e) {
207 | e.printStackTrace();
208 | }
209 | }
210 |
211 | private static void mergeExeBatchFiles() {
212 | File file = new File(batchDir);
213 | System.out.println("debug:current path = " + file.getAbsolutePath());
214 | File[] files = file.listFiles();
215 | if (files == null || files.length == 0) {
216 | return;
217 | }
218 | Arrays.sort(files, new Comparator() {
219 | public int compare(File file1, File file2) {
220 | if (file1.lastModified() > file2.lastModified()) {
221 | return 1;
222 | } else if (file1.lastModified() < file2.lastModified()) {
223 | return -1;
224 | } else {
225 | return 0;
226 | }
227 | }
228 | });
229 | StringBuffer command = new StringBuffer();
230 | for (File f : files) {
231 | command.append("call ").append(f.getAbsolutePath()).append("\n");
232 | }
233 | try {
234 | String filePath = batchDir + "build.bat";
235 | writeFile(filePath, command.toString());
236 | Runtime.getRuntime().exec("cmd /c start " + filePath);
237 | } catch (Exception e) {
238 | e.printStackTrace();
239 | }
240 | }
241 |
242 | /**
243 | * 写文件
244 | *
245 | * @param filePath
246 | * @param sets
247 | * @throws IOException
248 | */
249 | private static void writeFile(String filePath, String content)
250 | throws IOException {
251 | FileWriter fw = new FileWriter(filePath);
252 | PrintWriter out = new PrintWriter(fw);
253 | out.write(content);
254 | out.println();
255 | fw.close();
256 | out.close();
257 | }
258 |
259 | }
260 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/BundleBuilder/src/com/taobao/trip/BuildMain.java:
--------------------------------------------------------------------------------
1 | package com.taobao.trip;
2 |
3 |
4 | /**
5 | * @Author Zheng Haibo (mochuan)
6 | * @Company Alibaba Group
7 | * @PersonalWebsite http://www.mobctrl.net
8 | * @version $Id: BuildMain.java, v 0.1 2016年1月7日 下午9:40:06 mochuan.zhb Exp $
9 | * @Description
10 | */
11 | public class BuildMain {
12 |
13 | private static final String PROJECT_PATH = "C:\\Users\\mochuan.zhb\\newworkspace\\BundleApk5";
14 |
15 | private static final int PACKAGE_ID = 5;
16 |
17 | public static void main(String[] args) {
18 | new BuildApkUtils(PROJECT_PATH, PACKAGE_ID).buildUnsingedApk();
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HostApk5
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
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 |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/ic_launcher-web.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/HostApk5/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HostApk
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.os.Build;
8 | import android.os.Bundle;
9 | import android.view.View;
10 | import android.widget.ImageView;
11 | import android.widget.TextView;
12 |
13 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
14 | public class MainActivity extends Activity {
15 |
16 | public static final String TAG = "MainActivity";
17 |
18 | private TextView invokeTv;
19 | private ImageView imageView;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 |
26 | invokeTv = (TextView) findViewById(R.id.invoke_tv);
27 | invokeTv.setOnClickListener(new View.OnClickListener() {
28 |
29 | @Override
30 | public void onClick(View view) {
31 | try {
32 | startActivity(new Intent(MainActivity.this,Class.forName("net.mobctrl.normal.apk.BundleActivity")));
33 | } catch (ClassNotFoundException e) {
34 | e.printStackTrace();
35 | }
36 | }
37 | });
38 | imageView = (ImageView)findViewById(R.id.image_view_iv);
39 |
40 | }
41 |
42 | @Override
43 | protected void attachBaseContext(Context newBase) {
44 | // TODO Auto-generated method stub
45 | super.attachBaseContext(newBase);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/application/HostApplication.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.application;
2 |
3 | import net.mobctrl.hostapk.classloader.AssetsMultiDexLoader;
4 | import net.mobctrl.hostapk.resource.BundlerResourceLoader;
5 | import android.app.Application;
6 | import android.content.res.AssetManager;
7 | import android.content.res.Resources;
8 |
9 | /**
10 | * @Author Zheng Haibo (mochuan)
11 | * @Company Alibaba Group
12 | * @PersonalWebsite http://www.mobctrl.net
13 | * @version $Id: HostApplication.java, v 0.1 2016年1月7日 上午11:20:53 mochuan.zhb
14 | * Exp $
15 | * @Description HostApplication
16 | */
17 | public class HostApplication extends Application {
18 |
19 | private Resources mAppResources = null;
20 | private Resources mOldResources = null;
21 |
22 | @Override
23 | public void onCreate() {
24 | super.onCreate();
25 | mOldResources = super.getResources();
26 | AssetsMultiDexLoader.install(this);// 加载assets中的apk
27 | System.out.println("debug:HostApplication onCreate");
28 | installResource();
29 | }
30 |
31 | @Override
32 | public Resources getResources() {
33 | if(mAppResources == null){
34 | return mOldResources;
35 | }
36 | return this.mAppResources;
37 | }
38 |
39 | private void installResource() {
40 | if (mAppResources == null) {
41 | mAppResources = BundlerResourceLoader.getAppResource(this);// 加载assets中的资源对象
42 | }
43 | }
44 |
45 | @Override
46 | public AssetManager getAssets() {
47 | if (this.mAppResources == null) {
48 | return super.getAssets();
49 | }
50 | return this.mAppResources.getAssets();
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/bundle/BundleInfo.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.bundle;
2 |
3 | import java.io.Serializable;
4 |
5 | /**
6 | * @Author Zheng Haibo (mochuan)
7 | * @Company Alibaba Group
8 | * @PersonalWebsite http://www.mobctrl.net
9 | * @version $Id: BundleInfo.java, v 0.1 2016年1月7日 上午11:38:10 mochuan.zhb Exp $
10 | * @Description 每个Bundle的信息
11 | */
12 | public class BundleInfo implements Serializable{
13 |
14 | private static final long serialVersionUID = 1L;
15 |
16 | private String packageName;//包名
17 |
18 | private String apkPath;//apk路径
19 |
20 | private int packageId;//包ID
21 |
22 | public String getPackageName() {
23 | return packageName;
24 | }
25 |
26 | public void setPackageName(String packageName) {
27 | this.packageName = packageName;
28 | }
29 |
30 | public String getApkPath() {
31 | return apkPath;
32 | }
33 |
34 | public void setApkPath(String apkPath) {
35 | this.apkPath = apkPath;
36 | }
37 |
38 | public int getPackageId() {
39 | return packageId;
40 | }
41 |
42 | public void setPackageId(int packageId) {
43 | this.packageId = packageId;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/classloader/BundleClassLoaderManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.classloader;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import net.mobctrl.hostapk.utils.AssetsManager;
9 | import android.annotation.TargetApi;
10 | import android.content.Context;
11 | import android.os.Build;
12 |
13 | /**
14 | * @Author Zheng Haibo
15 | * @Mail mochuan.zhb@alibaba-inc.com
16 | * @Company Alibaba Group
17 | * @PersonalWebsite http://www.mobctrl.net
18 | * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
19 | * mochuan.zhb Exp $
20 | * @Description
21 | */
22 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
23 | public class BundleClassLoaderManager {
24 |
25 | public static List bundleDexClassLoaderList = new ArrayList();
26 |
27 | /**
28 | * 加载Assets里的apk文件
29 | * @param context
30 | */
31 | public static void install(Context context) {
32 | AssetsManager.copyAllAssetsApk(context);
33 | // 获取dex文件列表
34 | File dexDir = context.getDir(AssetsManager.APK_DIR,
35 | Context.MODE_PRIVATE);
36 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
37 |
38 | @Override
39 | public boolean accept(File dir, String filename) {
40 | return filename.endsWith(AssetsManager.FILE_FILTER);
41 | }
42 | });
43 | if(szFiles == null || szFiles.length == 0){
44 | return;
45 | }
46 | for (File f : szFiles) {
47 | System.out.println("debug:load file:" + f.getName());
48 | //加载apk,生成对应的ClassLoader
49 | BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
50 | f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
51 | context.getClassLoader());
52 | bundleDexClassLoaderList.add(bundleDexClassLoader);
53 | }
54 | }
55 |
56 | /**
57 | * 查找类
58 | *
59 | * @param className
60 | * @return
61 | * @throws ClassNotFoundException
62 | */
63 | public static Class> loadClass(Context context,String className) throws ClassNotFoundException {
64 | try {
65 | Class> clazz = context.getClassLoader().loadClass(className);
66 | if (clazz != null) {
67 | System.out.println("debug: class find in main classLoader");
68 | return clazz;
69 | }
70 | } catch (Exception e) {
71 | e.printStackTrace();
72 | }
73 | for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
74 | try {
75 | Class> clazz = bundleDexClassLoader.loadClass(className);
76 | if (clazz != null) {
77 | System.out.println("debug: class find in bundle classLoader");
78 | return clazz;
79 | }
80 | } catch (Exception e) {
81 | e.printStackTrace();
82 | }
83 | }
84 | throw new ClassCastException(className + " not found exception");
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/classloader/BundleDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.classloader;
2 |
3 | import dalvik.system.DexClassLoader;
4 |
5 | /**
6 | * @Author Zheng Haibo
7 | * @Mail mochuan.zhb@alibaba-inc.com
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: BundleDexClassLoader.java, v 0.1 2015年12月11日 下午7:12:49 mochuan.zhb Exp $
11 | * @Description bundle的类加载器
12 | */
13 | public class BundleDexClassLoader extends DexClassLoader {
14 |
15 | public BundleDexClassLoader(String dexPath, String optimizedDirectory,
16 | String libraryPath, ClassLoader parent) {
17 | super(dexPath, optimizedDirectory, libraryPath, parent);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/resource/AppResource.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.resource;
2 |
3 | import android.content.res.AssetManager;
4 | import android.content.res.Configuration;
5 | import android.content.res.Resources;
6 | import android.util.DisplayMetrics;
7 |
8 | /**
9 | * @Author Zheng Haibo (mochuan)
10 | * @Company Alibaba Group
11 | * @PersonalWebsite http://www.mobctrl.net
12 | * @version $Id: ManagerResource.java, v 0.1 2016年1月7日 下午5:19:10 mochuan.zhb Exp $
13 | * @Description 整个AppResource的托管
14 | */
15 | public class AppResource extends Resources {
16 |
17 | public AppResource(AssetManager assets, DisplayMetrics metrics,
18 | Configuration config) {
19 | super(assets, metrics, config);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/resource/BundlerResourceLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.resource;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import net.mobctrl.hostapk.utils.AssetsManager;
9 | import android.content.Context;
10 | import android.content.res.AssetManager;
11 | import android.content.res.Resources;
12 | import android.util.Log;
13 |
14 | /**
15 | * @Author Zheng Haibo
16 | * @Mail mochuan.zhb@alibaba-inc.com
17 | * @Company Alibaba Group
18 | * @PersonalWebsite http://www.mobctrl.net
19 | * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
20 | * Exp $
21 | * @Description 动态加载资源的管理器
22 | */
23 | public class BundlerResourceLoader {
24 |
25 | private static final String TAG = "BundlerResourceLoader";
26 |
27 | /**
28 | * 创建AssetManager
29 | *
30 | * @param apkPaths
31 | * @return
32 | */
33 | public static AssetManager createAssetManager(List apkPaths) {
34 | if (apkPaths == null || apkPaths.size() == 0) {
35 | return null;
36 | }
37 | try {
38 | AssetManager assetManager = AssetManager.class.newInstance();
39 | return modifyAssetManager(assetManager, apkPaths);
40 | } catch (Throwable th) {
41 | System.out.println("debug:createAssetManager :" + th.getMessage());
42 | th.printStackTrace();
43 | }
44 | return null;
45 | }
46 |
47 | /**
48 | * 修改AssetManager
49 | *
50 | * @param assetManager
51 | * @param apkPaths
52 | * @return
53 | */
54 | private static AssetManager modifyAssetManager(AssetManager assetManager,
55 | List apkPaths) {
56 | if (apkPaths == null || apkPaths.size() == 0) {
57 | return null;
58 | }
59 | try {
60 | for (String apkPath : apkPaths) {
61 | try {
62 | AssetManager.class.getDeclaredMethod("addAssetPath",
63 | String.class).invoke(assetManager, apkPath);
64 | } catch (Throwable th) {
65 | System.out.println("debug:createAssetManager :"
66 | + th.getMessage());
67 | th.printStackTrace();
68 | }
69 | }
70 | return assetManager;
71 | } catch (Throwable th) {
72 | System.out.println("debug:createAssetManager :" + th.getMessage());
73 | th.printStackTrace();
74 | }
75 | return null;
76 | }
77 |
78 | /**
79 | * 获取整个App的资源管理器中的资源
80 | *
81 | * @param context
82 | * @param apkPath
83 | * @return
84 | */
85 | public static Resources getAppResource(Context context) {
86 | System.out.println("debug:getAppResource ...");
87 | AssetsManager.copyAllAssetsApk(context);
88 | // 获取dex文件列表
89 | File dexDir = context.getDir(AssetsManager.APK_DIR,
90 | Context.MODE_PRIVATE);
91 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
92 |
93 | @Override
94 | public boolean accept(File dir, String filename) {
95 | return filename.endsWith(AssetsManager.FILE_FILTER);
96 | }
97 | });
98 | if (szFiles == null || szFiles.length == 0) {
99 | return context.getResources();
100 | }
101 | System.out.println("debug:getAppResource szFiles = "+szFiles.length);
102 | List apkPaths = new ArrayList();
103 | for (File f : szFiles) {
104 | Log.i(TAG, "load file:" + f.getName());
105 | apkPaths.add(f.getAbsolutePath());
106 | System.out.println("debug:apkPath = " + f.getAbsolutePath());
107 | }
108 | AssetManager assetManager = modifyAssetManager(context.getAssets(),
109 | apkPaths);
110 | AppResource resources = new AppResource(
111 | assetManager, context.getResources().getDisplayMetrics(),
112 | context.getResources().getConfiguration());
113 | return resources;
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/HostApk5/src/net/mobctrl/hostapk/utils/AssetsManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk.utils;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 |
8 | import android.content.Context;
9 | import android.content.res.AssetManager;
10 | import android.util.Log;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Mail mochuan.zhb@alibaba-inc.com
15 | * @Company Alibaba Group
16 | * @PersonalWebsite http://www.mobctrl.net
17 | * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
18 | * @Description
19 | */
20 | public class AssetsManager {
21 |
22 | public static final String TAG = "AssetsApkLoader";
23 |
24 | //从assets复制出去的apk的目标目录
25 | public static final String APK_DIR = "third_apk";
26 |
27 | //文件结尾过滤
28 | public static final String FILE_FILTER = ".apk";
29 |
30 |
31 | /**
32 | * 将资源文件中的apk文件拷贝到私有目录中
33 | *
34 | * @param context
35 | */
36 | public static void copyAllAssetsApk(Context context) {
37 |
38 | AssetManager assetManager = context.getAssets();
39 | long startTime = System.currentTimeMillis();
40 | try {
41 | File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
42 | dex.mkdir();
43 | String []fileNames = assetManager.list("");
44 | for(String fileName:fileNames){
45 | System.out.println("debug:fileName = "+fileName);
46 | if(!fileName.endsWith(FILE_FILTER)){
47 | return;
48 | }
49 | InputStream in = null;
50 | OutputStream out = null;
51 | in = assetManager.open(fileName);
52 | File f = new File(dex, fileName);
53 | if (f.exists() && f.length() == in.available()) {
54 | Log.i(TAG, fileName+"no change");
55 | return;
56 | }
57 | Log.i(TAG, fileName+" chaneged");
58 | out = new FileOutputStream(f);
59 | byte[] buffer = new byte[2048];
60 | int read;
61 | while ((read = in.read(buffer)) != -1) {
62 | out.write(buffer, 0, read);
63 | }
64 | in.close();
65 | in = null;
66 | out.flush();
67 | out.close();
68 | out = null;
69 | Log.i(TAG, fileName+" copy over");
70 | }
71 | Log.i(TAG,"debug:copyAssets time = "+(System.currentTimeMillis() - startTime));
72 | } catch (Exception e) {
73 | e.printStackTrace();
74 | }
75 |
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/decode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第五课-动态启动插件中的Activity/decode.jpg
--------------------------------------------------------------------------------
/第五课-动态启动插件中的Activity/readme.md:
--------------------------------------------------------------------------------
1 | #Android插件化基础(4),动态启动插件中的Activity
2 |
3 |
4 | ##Author:莫川
5 |
6 | ##简介
7 | 如何动态启动插件中的Activity呢?我们首先分析,启动插件中的Activity需要做那些准备?
8 |
9 | - 1.插件中Activity类的加载
10 | 也就是ClassLoader的问题。由第一节课中的MultiDex可以知道,我们可以动态的加载apk,然后将插件中的class加载到当前的ClassLoader当中。这样,插件中的class和宿主中的class同属一个ClassLoader,它们之间的相互调用问题也就解决了。
11 | - 2.插件中Activity在AndroidManifest.xml中的注册问题
12 | 由于插件apk有自己的AndroidManifest文件,为了能够在运行时,动态启动插件中的Activity,需要在打包时,将插件apk的Activity注册移动到宿主apk的AndroidManifest文件中。
13 | - 3.插件中Activity的资源加载问题
14 | 处理插件中的资源加载问题,是插件化最难的问题之一!我们需要考虑很多问题:
15 | 1.插件Activity运行时如何实时获取Resources对象,并且能够根据插件包名对应下的R文件的id,查找到Resources中的资源。
16 | 2.插件apk中的资源文件与其他插件及宿主之间的资源名称冲突如何解决?
17 | 3.宿主及各插件的资源如何统一并且方便的管理?
18 |
19 | ##问题的解决:
20 |
21 | ###1.类的加载
22 | 我们使用之前我们改造的AssetsMultiDexLoader,来加载assets目录下的apk。由于之前的博客已经说明了问题,再次不在赘述。
23 |
24 | ###2.插件中的Activity在宿主AndroidManifest中注册
25 | 这件事情需要在打包时处理,也就是说,我们需要改造我们的打包工具,在打包时,将各个插件的AndroidManifest文件合并到宿主AndroidManifest文件中。
26 |
27 | ###3.插件资源的加载问题
28 |
29 | 我们需要在编译过程和运行过程分别做处理:
30 |
31 | ####3.1 编译过程
32 | 我们首先回顾一下Android打包的过程:
33 | 1.生成R.java文件
34 | 比如:
35 | ```xml
36 | aapt package -f -m -J ./gen -S res -M AndroidManifest.xml -I D:\android_sdk_for_studio\platforms\android-22\android.jar
37 | ```
38 | 2.清空bin目录
39 | 清空上次生成的文件
40 | 3.编译java文件和jar包
41 | ```xml
42 | javac -encoding GBK -target 1.5 -bootclasspath D:\android_sdk_for_studio\platforms\android-22\android.jar -d bin src\net\mobctrl\normal\apk\*.java gen\net\mobctrl\normal\apk\R.java -classpath libs\*.jar
43 | ```
44 | 4.使用dx工具打包成classes.dex
45 | ```xml
46 | dx --dex --output=C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\classes.dex C:\Users\mochuan.zhb\newworkspace\BundleApk5\bin\
47 | ```
48 | 5.编译成资源文件
49 | ```xml
50 | aapt package -f -M AndroidManifest.xml -S res -I D:\android_sdk_for_studio\platforms\android-22\android.jar -F bin\resources.ap_ --non-constant-id
51 | ```
52 | 6.使用sdklib.jar工具生成未签名的apk
53 | ```
54 | java -cp D:\android_sdk_for_studio\tools\lib\sdklib.jar com.android.sdklib.build.ApkBuilderMain bin\MyCommond.apk -v -u -z bin\resources.ap_ -f bin\classes.dex -rf C:\Users\mochuan.zhb\newworkspace\BundleApk5\src
55 | ```
56 | 7.使用jarsigner对apk进行签名
57 | jarsigner -verbose -keystore C:\test.keystore -storepass 123456 -keypass 123456 -signedjar C:\projectdemo-signed.apk C:\test.apk test
58 |
59 |
60 | ###3.2编译过程的修改
61 | 为了解决插件与插件之间以及宿主之间的资源冲突问题,我们需要对插件进行编号,修改R文件的生成过程。我们知道,R文件中的ID是一个int类型,总共32位。那么这32位分别代表什么含义呢?
62 | 1.前8位代表插件的packageId,其中两个特殊的Id:Host是0x7f,android系统自带的是以0x01开头.
63 | 2.紧跟着的8位是区分资源类型的,比如layout,id,string,dimen等
64 | 3.后面16位是资源的编号
65 |
66 | 为了解决资源的命名冲突,一般由以下2中方法:
67 | ####1.约定
68 | 团队在开发时,对资源的命名进行约定,比如各业务线按照一定的规则命名,大家准守规则,避免重复。
69 | 然后在打包时,我们对各个插件的资源进行合并,统一生成R文件,所有插件和宿主的R文件内容都是完全一样的,资源都保存在宿主项目的资源中。
70 | 说明:Google使用的Android打包过程,就是这样的。比如主project依赖lib_project1,lib_project2等,在编译主project的时候,它就将各个lib项目的资源都复制到主project中,然后使用aapt进行统一生成R文件,生成多个不同包名的R文件,但是R文件的内容是完全一样的。
71 | ####2.修改aapt
72 | 为了从机制上避免资源名称重复的问题,我们可以通过修改aapt的源码,让其可以根据不同的packageId生成不同的id。也就是说,R文件中的id由以下32位组成:
73 | [packageId(8)][resourceType(8)][resourcesSeq(16)]
74 | 我们为每一个插件分配一个packageId,范围是(1,127).
75 | 修改aapt的源码之后,我们需要改动打包过程中的第一步和第五步,在生成R文件和编译资源的时候,使用我们改造后的aapt。这样,最终生成的apk,其R文件就是按照我们分配的方式。
76 | 我们可以通过反编译,查看test.apk中的R文件,如下图所示,我们设定的packageId是5:
77 | 
78 | 【test.apk的反编译图】
79 | 反编译之后的id需要转化为16进制显示。
80 | *注*:
81 | 反编译步骤:
82 | 1.解压apk文件
83 | 2.使用d2j-dex2jar工具,将dex转化为jar
84 | 3.使用Java-Decompiler反编译jar
85 |
86 | ###3.3运行过程中资源的加载
87 |
88 | ####1.将插件apk加载到当前的AssetsManager中
89 | 核心代码:
90 |
91 | ```java
92 | /**
93 | * 修改AssetManager
94 | *
95 | * @param assetManager
96 | * @param apkPaths
97 | * @return
98 | */
99 | private static AssetManager modifyAssetManager(AssetManager assetManager,
100 | List apkPaths) {
101 | if (apkPaths == null || apkPaths.size() == 0) {
102 | return null;
103 | }
104 | try {
105 | for (String apkPath : apkPaths) {
106 | try {
107 | AssetManager.class.getDeclaredMethod("addAssetPath",
108 | String.class).invoke(assetManager, apkPath);
109 | } catch (Throwable th) {
110 | System.out.println("debug:createAssetManager :"
111 | + th.getMessage());
112 | th.printStackTrace();
113 | }
114 | }
115 | return assetManager;
116 | } catch (Throwable th) {
117 | System.out.println("debug:createAssetManager :" + th.getMessage());
118 | th.printStackTrace();
119 | }
120 | return null;
121 | }
122 |
123 | /**
124 | * 获取整个App的资源管理器中的资源
125 | *
126 | * @param context
127 | * @param apkPath
128 | * @return
129 | */
130 | public static Resources getAppResource(Context context) {
131 | System.out.println("debug:getAppResource ...");
132 | AssetsManager.copyAllAssetsApk(context);
133 | // 获取dex文件列表
134 | File dexDir = context.getDir(AssetsManager.APK_DIR,
135 | Context.MODE_PRIVATE);
136 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
137 |
138 | @Override
139 | public boolean accept(File dir, String filename) {
140 | return filename.endsWith(AssetsManager.FILE_FILTER);
141 | }
142 | });
143 | if (szFiles == null || szFiles.length == 0) {
144 | return context.getResources();
145 | }
146 | System.out.println("debug:getAppResource szFiles = "+szFiles.length);
147 | List apkPaths = new ArrayList();
148 | for (File f : szFiles) {
149 | Log.i(TAG, "load file:" + f.getName());
150 | apkPaths.add(f.getAbsolutePath());
151 | System.out.println("debug:apkPath = " + f.getAbsolutePath());
152 | }
153 | AssetManager assetManager = modifyAssetManager(context.getAssets(),
154 | apkPaths);
155 | AppResource resources = new AppResource(
156 | assetManager, context.getResources().getDisplayMetrics(),
157 | context.getResources().getConfiguration());
158 | return resources;
159 | }
160 |
161 | ```
162 |
163 | ####2.Application中Resources的加载
164 | 核心代码
165 | ```java
166 |
167 | public class HostApplication extends Application {
168 |
169 | private Resources mAppResources = null;
170 | private Resources mOldResources = null;
171 |
172 | @Override
173 | public void onCreate() {
174 | super.onCreate();
175 | mOldResources = super.getResources();
176 | AssetsMultiDexLoader.install(this);// 加载assets中的apk
177 | installResource();
178 | }
179 |
180 | @Override
181 | public Resources getResources() {
182 | if(mAppResources == null){
183 | return mOldResources;
184 | }
185 | return this.mAppResources;
186 | }
187 |
188 | private void installResource() {
189 | if (mAppResources == null) {
190 | mAppResources = BundlerResourceLoader.getAppResource(this);// 加载assets中的资源对象
191 | }
192 | }
193 |
194 | @Override
195 | public AssetManager getAssets() {
196 | if (this.mAppResources == null) {
197 | return super.getAssets();
198 | }
199 | return this.mAppResources.getAssets();
200 | }
201 |
202 | }
203 |
204 | ```
205 |
206 | ####3.插件Activity替换Resource对象
207 | 以BundleActivity为例:
208 | ```java
209 | public class BundleActivity extends BaseActivity {
210 |
211 | @Override
212 | protected void onCreate(Bundle savedInstanceState) {
213 | super.onCreate(savedInstanceState);
214 | setContentView(R.layout.bundle_layout);
215 | findViewById(R.id.text_view).setOnClickListener(new View.OnClickListener() {
216 |
217 | @Override
218 | public void onClick(View view) {
219 | Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_LONG).show();
220 | }
221 | });
222 | }
223 |
224 | @Override
225 | public Resources getResources() {
226 | return getApplication().getResources();
227 | }
228 |
229 | }
230 | ```
231 | 最关键的是,重写getResources()方法,使用Application的Resource替换当前的Resource方法。
232 |
233 | #参考
234 | [1.关于Android如何动态加载res](http://nobodycare.me/2014/11/07/about-loading-res-from-apk-directly/)
235 | [2.Android应用程序资源的编译和打包过程分析](http://blog.csdn.net/luoshengyang/article/details/8744683)
236 | [3.Android应用程序资源管理器(Asset Manager)的创建过程分析](http://blog.csdn.net/luoshengyang/article/details/8791064)
237 | [4.Android 自动编译、打包生成apk文件 1 - 命令行方式](http://blog.csdn.net/androiddevelop/article/details/10948639)
238 | [5.使用ANT打包Android应用](http://blog.csdn.net/liuhe688/article/details/6679879)
239 | [6.如何修改android aapt源码实现自定义package ID ](http://blog.chinaunix.net/uid-28764633-id-5395048.html)
240 |
241 |
242 |
243 |
244 |
245 |
246 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | BundleApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xxhdpi/bundle_img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xxhdpi/bundle_img.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/layout/bundle_layout.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
24 |
25 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bundle
5 | I am in bundle str
6 | Settings
7 |
8 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/src/net/mobctrl/normal/apk/FileUtils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: FileUtils.java, v 0.1 2015年12月10日 下午2:30:40 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class FileUtils {
14 |
15 | public void write(){
16 |
17 | }
18 |
19 | public void print(Context context, String name) {
20 | Toast.makeText(context, name, Toast.LENGTH_SHORT).show();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/BundleApk/src/net/mobctrl/normal/apk/Utils.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.normal.apk;
2 |
3 | import android.content.Context;
4 | import android.widget.Toast;
5 |
6 | /**
7 | * @Author Zheng Haibo
8 | * @Company Alibaba Group
9 | * @PersonalWebsite http://www.mobctrl.net
10 | * @version $Id: Utils.java, v 0.1 2015年12月10日 下午2:25:39 mochuan.zhb Exp $
11 | * @Description
12 | */
13 | public class Utils {
14 |
15 | /**
16 | * 计算 a+b
17 | *
18 | * @param context
19 | * @param a
20 | * @param b
21 | * @param name
22 | */
23 | public int printSum(Context context,int a,int b,String name){
24 | int sum = a + b;
25 | Toast.makeText(context, name+":"+sum, Toast.LENGTH_SHORT).show();
26 | return sum;
27 | }
28 |
29 | public void printFileName(Context context,String name){
30 | new FileUtils().print(context,name);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | HostApk
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
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 |
59 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/ic_launcher-web.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-22
15 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nuptboyzhb/AndroidPluginFramework/50e3192d1d3fd890741656f7c18c9ef2778abfed/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
17 |
18 |
24 |
25 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 64dp
9 |
10 |
11 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HostApk
5 | Hello world!
6 | Settings
7 |
8 |
9 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/AssetsManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.InputStream;
6 | import java.io.OutputStream;
7 |
8 | import android.content.Context;
9 | import android.content.res.AssetManager;
10 | import android.util.Log;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: AssetsManager.java, v 0.1 2015年12月11日 下午4:41:10 mochuan.zhb Exp $
17 | * @Description
18 | */
19 | public class AssetsManager {
20 |
21 | public static final String TAG = "AssetsApkLoader";
22 |
23 | //从assets复制出去的apk的目标目录
24 | public static final String APK_DIR = "third_apk";
25 |
26 | //文件结尾过滤
27 | public static final String FILE_FILTER = ".apk";
28 |
29 |
30 | /**
31 | * 将资源文件中的apk文件拷贝到私有目录中
32 | *
33 | * @param context
34 | */
35 | public static void copyAllAssetsApk(Context context) {
36 |
37 | AssetManager assetManager = context.getAssets();
38 | long startTime = System.currentTimeMillis();
39 | try {
40 | File dex = context.getDir(APK_DIR, Context.MODE_PRIVATE);
41 | dex.mkdir();
42 | String []fileNames = assetManager.list("");
43 | for(String fileName:fileNames){
44 | if(!fileName.endsWith(FILE_FILTER)){
45 | return;
46 | }
47 | InputStream in = null;
48 | OutputStream out = null;
49 | in = assetManager.open(fileName);
50 | File f = new File(dex, fileName);
51 | if (f.exists() && f.length() == in.available()) {
52 | Log.i(TAG, fileName+"no change");
53 | return;
54 | }
55 | Log.i(TAG, fileName+" chaneged");
56 | out = new FileOutputStream(f);
57 | byte[] buffer = new byte[2048];
58 | int read;
59 | while ((read = in.read(buffer)) != -1) {
60 | out.write(buffer, 0, read);
61 | }
62 | in.close();
63 | in = null;
64 | out.flush();
65 | out.close();
66 | out = null;
67 | Log.i(TAG, fileName+" copy over");
68 | }
69 | Log.i(TAG,"###copyAssets time = "+(System.currentTimeMillis() - startTime));
70 | } catch (Exception e) {
71 | e.printStackTrace();
72 | }
73 |
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/BundleActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import android.app.Activity;
6 | import android.content.Context;
7 | import android.content.res.Resources;
8 | import android.os.Bundle;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: BundleActivity.java, v 0.1 2015年12月16日 下午3:49:00 mochuan.zhb Exp $
17 | * @Description
18 | */
19 | public class BundleActivity extends Activity{
20 |
21 | private Resources mBundleResources;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | System.out.println("debug:BundleActivity onCreate...");
27 | //设置Bundle Layout中的ID:bundle_layout=0x7f030001;
28 | int bundleLayoutId = 0x7f030001;
29 | View bundleView = LayoutInflater.from(this).inflate(bundleLayoutId, null);
30 | setContentView(bundleView);
31 | }
32 |
33 | @Override
34 | protected void attachBaseContext(Context context) {
35 | replaceContextResources(context);
36 | super.attachBaseContext(context);
37 | }
38 |
39 | /**
40 | * 使用反射的方式,使用Bundle的Resource对象,替换Context的mResources对象
41 | * @param context
42 | */
43 | public void replaceContextResources(Context context){
44 | try {
45 | Field field = context.getClass().getDeclaredField("mResources");
46 | field.setAccessible(true);
47 | if (null == mBundleResources) {
48 | mBundleResources = BundlerResourceLoader.bundleResMap.get("bundle_apk");
49 | }
50 | field.set(context, mBundleResources);
51 | System.out.println("debug:repalceResources succ");
52 | } catch (Exception e) {
53 | System.out.println("debug:repalceResources error");
54 | e.printStackTrace();
55 | }
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/BundleClassLoaderManager.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.io.FilenameFilter;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import android.annotation.TargetApi;
9 | import android.content.Context;
10 | import android.os.Build;
11 |
12 | /**
13 | * @Author Zheng Haibo
14 | * @Company Alibaba Group
15 | * @PersonalWebsite http://www.mobctrl.net
16 | * @version $Id: BundleClassLoaderManager.java, v 0.1 2015年12月11日 下午7:30:59
17 | * mochuan.zhb Exp $
18 | * @Description
19 | */
20 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
21 | public class BundleClassLoaderManager {
22 |
23 | public static List bundleDexClassLoaderList = new ArrayList();
24 |
25 | /**
26 | * 加载Assets里的apk文件
27 | * @param context
28 | */
29 | public static void install(Context context) {
30 | AssetsManager.copyAllAssetsApk(context);
31 | // 获取dex文件列表
32 | File dexDir = context.getDir(AssetsManager.APK_DIR,
33 | Context.MODE_PRIVATE);
34 | File[] szFiles = dexDir.listFiles(new FilenameFilter() {
35 |
36 | @Override
37 | public boolean accept(File dir, String filename) {
38 | return filename.endsWith(AssetsManager.FILE_FILTER);
39 | }
40 | });
41 | for (File f : szFiles) {
42 | System.out.println("debug:load file:" + f.getName());
43 | //加载apk,生成对应的ClassLoader
44 | BundleDexClassLoader bundleDexClassLoader = new BundleDexClassLoader(
45 | f.getAbsolutePath(), dexDir.getAbsolutePath(), null,
46 | context.getClassLoader());
47 | bundleDexClassLoaderList.add(bundleDexClassLoader);
48 | }
49 | }
50 |
51 | /**
52 | * 查找类
53 | *
54 | * @param className
55 | * @return
56 | * @throws ClassNotFoundException
57 | */
58 | public static Class> loadClass(Context context,String className) throws ClassNotFoundException {
59 | try {
60 | Class> clazz = context.getClassLoader().loadClass(className);
61 | if (clazz != null) {
62 | System.out.println("debug: class find in main classLoader");
63 | return clazz;
64 | }
65 | } catch (Exception e) {
66 | e.printStackTrace();
67 | }
68 | for (BundleDexClassLoader bundleDexClassLoader : bundleDexClassLoaderList) {
69 | try {
70 | Class> clazz = bundleDexClassLoader.loadClass(className);
71 | if (clazz != null) {
72 | System.out.println("debug: class find in bundle classLoader");
73 | return clazz;
74 | }
75 | } catch (Exception e) {
76 | e.printStackTrace();
77 | }
78 | }
79 | throw new ClassCastException(className + " not found exception");
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/BundleDexClassLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import dalvik.system.DexClassLoader;
4 |
5 | /**
6 | * @Author Zheng Haibo
7 | * @Company Alibaba Group
8 | * @PersonalWebsite http://www.mobctrl.net
9 | * @version $Id: BundleDexClassLoader.java, v 0.1 2015年12月11日 下午7:12:49 mochuan.zhb Exp $
10 | * @Description bundle的类加载器
11 | */
12 | public class BundleDexClassLoader extends DexClassLoader {
13 |
14 | public BundleDexClassLoader(String dexPath, String optimizedDirectory,
15 | String libraryPath, ClassLoader parent) {
16 | super(dexPath, optimizedDirectory, libraryPath, parent);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/BundlerResourceLoader.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.io.File;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import android.content.Context;
8 | import android.content.res.AssetManager;
9 | import android.content.res.Resources;
10 |
11 | /**
12 | * @Author Zheng Haibo
13 | * @Company Alibaba Group
14 | * @PersonalWebsite http://www.mobctrl.net
15 | * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
16 | * Exp $
17 | * @Description 动态加载资源的管理器
18 | */
19 | public class BundlerResourceLoader {
20 |
21 | public static Map bundleResMap = new HashMap();
22 |
23 | private static AssetManager createAssetManager(String apkPath) {
24 | try {
25 | AssetManager assetManager = AssetManager.class.newInstance();
26 | try {
27 | AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
28 | assetManager, apkPath);
29 | } catch (Throwable th) {
30 | System.out.println("debug:createAssetManager :"+th.getMessage());
31 | th.printStackTrace();
32 | }
33 | return assetManager;
34 | } catch (Throwable th) {
35 | System.out.println("debug:createAssetManager :"+th.getMessage());
36 | th.printStackTrace();
37 | }
38 | return null;
39 | }
40 |
41 | /**
42 | * 获取Bundle中的资源
43 | * @param context
44 | * @param apkPath
45 | * @return
46 | */
47 | public static Resources getBundleResource(Context context){
48 | AssetsManager.copyAllAssetsApk(context);
49 | File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
50 | String apkPath = dir.getAbsolutePath()+"/BundleApk.apk";
51 | System.out.println("debug:apkPath = "+apkPath+",exists="+(new File(apkPath).exists()));
52 | AssetManager assetManager = createAssetManager(apkPath);
53 | Resources resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
54 | bundleResMap.put("bundle_apk", resources);
55 | return resources;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/HostApk/src/net/mobctrl/hostapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package net.mobctrl.hostapk;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import android.annotation.TargetApi;
6 | import android.app.Activity;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.content.res.Resources;
10 | import android.graphics.drawable.Drawable;
11 | import android.os.Build;
12 | import android.os.Bundle;
13 | import android.view.LayoutInflater;
14 | import android.view.View;
15 | import android.widget.ImageView;
16 | import android.widget.TextView;
17 | import android.widget.Toast;
18 |
19 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
20 | public class MainActivity extends Activity {
21 |
22 | public static final String TAG = "MainActivity";
23 |
24 | private TextView invokeTv;
25 | private ImageView imageView;
26 |
27 | @Override
28 | protected void onCreate(Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | setContentView(R.layout.activity_main);
31 | BundleClassLoaderManager.install(getApplicationContext());
32 | invokeTv = (TextView) findViewById(R.id.invoke_tv);
33 | invokeTv.setOnClickListener(new View.OnClickListener() {
34 |
35 | @Override
36 | public void onClick(View view) {
37 | startActivity(new Intent(MainActivity.this,BundleActivity.class));
38 | }
39 | });
40 | BundlerResourceLoader.getBundleResource(getApplicationContext());
41 | imageView = (ImageView)findViewById(R.id.image_view_iv);
42 |
43 | }
44 |
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/第四课-使用LayoutInflate加载离线的布局及资源/README.md:
--------------------------------------------------------------------------------
1 | #Android加载插件apk中的Resource资源
2 | 见第三课的说明
--------------------------------------------------------------------------------