├── .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 | ![multidex](multidex.png) 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 | ![ClassLoader加载路路径](bundle_path_load.png) 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 | ![Resource的加载和动态替换](resource_load_replace.PNG) 209 | 210 | ###6.插件Activity在宿主AndroidManifest中的预注册 211 | 每个插件的Activity,必须在宿主的AndroidManifest.xml中进行注册。 212 | 213 | ##DynamicAPK源码导读: 214 | 源代码的目录结构图 215 | ![源码目录](src_dir.png) 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 | ![de](decode.jpg) 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 | 见第三课的说明 --------------------------------------------------------------------------------