installedPluginMaps = getInstalledPlugin();
710 | if (installedPluginMaps.isEmpty()) {
711 | isIniteInstallPlugins = true;
712 | return;
713 | }
714 | //获取classloader设置classloader
715 | ZeusClassLoader classLoader = null;
716 | boolean isNeedLoadResource = false;
717 |
718 | for (String pluginId : installedPluginMaps.keySet()) {
719 | if(!isSupport(pluginId))continue;
720 | if (PluginUtil.isPlugin(pluginId)) {
721 | if (classLoader == null) {
722 | String nativeLibraryDir = null;
723 | ApplicationInfo applicationInfo = mBaseContext.getApplicationInfo();
724 | if (applicationInfo != null) {
725 | nativeLibraryDir = applicationInfo.nativeLibraryDir;
726 | }
727 | classLoader = new ZeusClassLoader(mBaseContext.getPackageCodePath(), mBaseContext.getClassLoader(), nativeLibraryDir);
728 | }
729 | String pathInfo = PluginUtil.getInstalledPathInfo(pluginId);
730 | classLoader.addAPKPath(pluginId, PluginUtil.getAPKPath(pluginId, pathInfo), PluginUtil.getLibFileInside(pluginId));
731 | putLoadedPlugin(pluginId, installedPluginMaps.get(pluginId), pathInfo);
732 | isNeedLoadResource = true;
733 | }
734 | if (PluginUtil.isHotFix(pluginId)) {
735 | loadHotfixPluginClassLoader(pluginId);
736 | isNeedLoadResource = true;
737 | }
738 | }
739 | clearViewConstructorCache();
740 | if (!isNeedLoadResource) {
741 | isIniteInstallPlugins = true;
742 | return;
743 | }
744 | //设置原始APK所使用的ClassLoader
745 | if (classLoader != null) {
746 | PluginUtil.setField(mPackageInfo, "mClassLoader", classLoader);
747 | Thread.currentThread().setContextClassLoader(classLoader);
748 | mNowClassLoader = classLoader;
749 | }
750 | reloadInstalledPluginResources();
751 | isIniteInstallPlugins = true;
752 | }
753 | }
754 |
755 | /**
756 | * Application中以及activity中的getResources()方法都应该调用这个方法
757 | *
758 | * @return 返回正在使用的resources
759 | */
760 | public static Resources getResources() {
761 | return mNowResources;
762 | }
763 |
764 | /**
765 | * 获取最新插件对象,不存在就生成一个
766 | *
767 | * @param pluginId 插件的名称
768 | * @return 获取某个插件对象
769 | */
770 | public static ZeusPlugin getPlugin(String pluginId) {
771 | ZeusPlugin plugin = null;
772 | try {
773 | plugin = mPluginMap.get(pluginId);
774 | if (plugin != null) {
775 | return plugin;
776 | }
777 | if (PluginUtil.iszeusPlugin(pluginId)) {
778 | plugin = new ZeusPlugin(pluginId);
779 | }
780 | mPluginMap.put(pluginId, plugin);
781 | } catch (Exception e) {
782 | e.printStackTrace();
783 | }
784 | return plugin;
785 | }
786 |
787 |
788 | public static void startActivity(Activity activity, Intent intent) {
789 | ComponentName componentName = intent.getComponent();
790 | intent.setClassName(componentName.getPackageName(), PluginConstant.PLUGIN_ACTIVITY_FOR_STANDARD);
791 | intent.putExtra(PluginConstant.PLUGIN_REAL_ACTIVITY, componentName.getClassName());
792 | activity.startActivity(intent);
793 | }
794 |
795 | public static void startActivity(Intent intent) {
796 | ComponentName componentName = intent.getComponent();
797 | intent.setClassName(componentName.getPackageName(), PluginConstant.PLUGIN_ACTIVITY_FOR_STANDARD);
798 | intent.putExtra(PluginConstant.PLUGIN_REAL_ACTIVITY, componentName.getClassName());
799 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
800 | mBaseContext.startActivity(intent);
801 | }
802 | }
803 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/PluginManifest.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | /**
9 | * 插件的配置,对应文件存放在插件apk下的assets/plugin.meta,这也是可配置的
10 | * 之所以没有放在androidManifest.xml中是因为有些手机使用无法获取插件中对应的meta data数据。
11 | * 其实还有一套完整强安全校验方案,想到绝大部分人用不到,就删掉了,想要的可以在单独联系。
12 | */
13 | public class PluginManifest {
14 | public static final String PLUG_NAME = "name";
15 | public static final String PLUG_MIN_VERSION = "minVersion";
16 | public static final String PLUG_MAX_VERSION = "maxVersion";
17 | public static final String PLUG_VERSION = "version";
18 | public static final String PLUG_MAINCLASS = "mainClass";
19 | public static final String PLUG_OTHER_INFO = "otherInfo";
20 | public static final String PLUG_FLAG = "flag";
21 | public static final int FLAG_WITHOUT_RESOURCES = 0x1;
22 | public static final int FLAG_WITHOUT_SO_FILE = 0x2;
23 | /**
24 | * 插件显示的名称
25 | */
26 | public String name = "";
27 | /**
28 | * 插件依赖的最低平台版本号
29 | */
30 | public String minVersion = "";
31 | /**
32 | * 插件依赖的最大平台版本号
33 | */
34 | public String maxVersion = "";
35 | /**
36 | * 插件版本号
37 | */
38 | public String version = "";
39 | /**
40 | * 插件入口的类名
41 | */
42 | public String mainClass = "";
43 | /**
44 | * 插件的其他信息,可扩展,可是其他json格式字符串
45 | */
46 | public String otherInfo = "";
47 |
48 | public String flag = "";
49 |
50 | public PluginManifest() {
51 | }
52 |
53 | public PluginManifest(String manifest) {
54 | try {
55 | JSONObject jsonObject = new JSONObject(manifest);
56 | name = jsonObject.optString(PLUG_NAME);
57 | minVersion = jsonObject.optString(PLUG_MIN_VERSION);
58 | maxVersion = jsonObject.optString(PLUG_MAX_VERSION);
59 | version = jsonObject.optString(PLUG_VERSION);
60 | mainClass = jsonObject.optString(PLUG_MAINCLASS);
61 | otherInfo = jsonObject.optString(PLUG_OTHER_INFO);
62 | flag = jsonObject.optString(PLUG_FLAG);
63 | } catch (JSONException e) {
64 | e.printStackTrace();
65 | }
66 | }
67 |
68 | public int getVersion() {
69 | if (!TextUtils.isEmpty(version)) {
70 | try {
71 | return Integer.valueOf(version);
72 | } catch (Throwable e) {
73 | return 0;
74 | }
75 | } else {
76 | return 0;
77 | }
78 | }
79 |
80 | public int getFlag() {
81 | return TextUtils.isEmpty(flag) ? 0 : Integer.valueOf(flag);
82 | }
83 |
84 | public boolean hasResoures() {
85 | return (getFlag() & FLAG_WITHOUT_RESOURCES) != FLAG_WITHOUT_RESOURCES;
86 | }
87 |
88 | public boolean hasSoLibrary(){
89 | return (getFlag() & FLAG_WITHOUT_SO_FILE) != FLAG_WITHOUT_SO_FILE;
90 | }
91 | @Override
92 | public String toString() {
93 | JSONObject jsonObject = new JSONObject();
94 | try {
95 | jsonObject.put(PLUG_NAME, name);
96 | jsonObject.put(PLUG_MIN_VERSION, minVersion);
97 | jsonObject.put(PLUG_MAX_VERSION, maxVersion);
98 | jsonObject.put(PLUG_VERSION, version);
99 | jsonObject.put(PLUG_MAINCLASS, mainClass);
100 | jsonObject.put(PLUG_OTHER_INFO, otherInfo);
101 | jsonObject.put(PLUG_OTHER_INFO, flag);
102 | } catch (JSONException e) {
103 | e.printStackTrace();
104 | }
105 | return jsonObject.toString();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/PluginUtil.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.content.res.AssetManager;
4 | import android.text.TextUtils;
5 |
6 | import java.io.BufferedInputStream;
7 | import java.io.BufferedReader;
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.Closeable;
10 | import java.io.File;
11 | import java.io.FileInputStream;
12 | import java.io.FileOutputStream;
13 | import java.io.InputStream;
14 | import java.io.InputStreamReader;
15 | import java.lang.reflect.Field;
16 | import java.lang.reflect.Method;
17 | import java.util.zip.ZipEntry;
18 | import java.util.zip.ZipInputStream;
19 |
20 | /**
21 | * 插件的工具类,用到的工具静态方法在这里,包括zip、file、路径配置等等
22 | *
23 | * Created by huangjian on 2016/6/21.
24 | */
25 | public class PluginUtil {
26 |
27 | private final static int BYTE_IN_SIZE = 4096;
28 | private static final int BUF_SIZE = 8192;
29 |
30 | private static final int CPU_AMR = 1;
31 | private static final int CPU_X86 = 2;
32 | private static final int CPU_MIPS = 3;
33 |
34 | private static String mInsidePluginPath = null;
35 | //start========================获取插件相关目录的方法======================
36 |
37 | /**
38 | * 获取某个插件的安装目录
39 | *
40 | * @param plugId 插件id
41 | * @return 插件的安装目录
42 | */
43 | public static String getPlugDir(String plugId) {
44 | return getInsidePluginPath() + plugId + "/";
45 | }
46 |
47 | /**
48 | * 获取插件模块的目录
49 | *
50 | * @return 插件模块的目录
51 | */
52 | public static String getInsidePluginPath() {
53 | if (mInsidePluginPath != null) {
54 | return mInsidePluginPath;
55 | }
56 | return mInsidePluginPath = PluginManager.mBaseContext.getFilesDir().getPath() + "/plugins/";
57 | }
58 |
59 | /**
60 | * 获取安装后的某个插件文件路径
61 | *
62 | * @param pluginName 插件id
63 | * @return 安装后的某个插件文件路径
64 | */
65 | public static String getAPKPath(String pluginName) {
66 | return getAPKPath(pluginName, getInstalledPathInfo(pluginName));
67 | }
68 |
69 | public static String getAPKPath(String pluginName, String pathifo){
70 | return PluginUtil.getPlugDir(pluginName)+ pathifo + PluginConstant.PLUGIN_SUFF;
71 | }
72 |
73 | /**
74 | * 获取安装前某个插件文件的路径
75 | *
76 | * @param pluginName 插件id
77 | * @return 安装前某个插件文件的路径
78 | */
79 | public static String getZipPath(String pluginName) {
80 | return getPlugDir(pluginName) + pluginName;
81 | }
82 |
83 | /**
84 | * 获取dex优化后的文件地址
85 | * @param pluingid 插件id
86 | * @param apkName apk的名称
87 | * @return dex优化后的文件地址
88 | */
89 | public static String getDexCacheFilePath(String pluingid, String apkName) {
90 | return getDexCacheParentDirectPath(pluingid) + apkName + ".dex";
91 | }
92 |
93 | /**
94 | * 获取某个插件id的dex优化后路径
95 | * @param pluginid 插件id
96 | * @return 某个插件id的dex优化后路径
97 | */
98 | public static String getDexCacheParentDirectPath(String pluginid) {
99 | String path;
100 | if(TextUtils.isEmpty(pluginid)){
101 | path = getDexCacheParentDirectPath();
102 | }else {
103 | path = getDexCacheParentDirectPath() + pluginid + "/";
104 | }
105 | if(!isDirExist(path)){
106 | createDirWithFile(path);
107 | }
108 |
109 | return path;
110 | }
111 |
112 | /**
113 | * 判断某个文件是否是文件夹
114 | * @param filePathName
115 | * @return
116 | */
117 | public static boolean isDirExist(String filePathName) {
118 | if(TextUtils.isEmpty(filePathName)) return false;
119 | if(!filePathName.endsWith("/")) filePathName +="/";
120 | File file = new File(filePathName);
121 | return (file.isDirectory() && file.exists());
122 | }
123 | /**
124 | * 优化后的odex/opt文件的文件夹路径
125 | *
126 | * @return 优化后的odex/opt文件的文件夹路径
127 | */
128 | private static String getDexCacheParentDirectPath() {
129 | return getInsidePluginPath() + "dalvik-cache/";
130 | }
131 |
132 | /**
133 | * 获取某个插件安装后so文件存放目录
134 | *
135 | * @param pluginName 插件id
136 | * @return 某个插件安装后so文件存放目录
137 | */
138 | public static String getLibFileInside(String pluginName) {
139 | return getInsidePluginPath() + pluginName + "/" + getInstalledPathInfo(pluginName) + "/" + getLibFile(getCpuArchitecture());
140 | }
141 | //end========================获取插件相关目录的方法======================end
142 |
143 | /**
144 | * 是否是插件或者补丁
145 | *
146 | * @param pluginId 插件id
147 | * @return 是否是插件或者补丁
148 | */
149 | public static boolean iszeusPlugin(String pluginId) {
150 | return !TextUtils.isEmpty(pluginId) &&
151 | (pluginId.startsWith(PluginConstant.EXP_PLUG_PREFIX) ||
152 | isHotFix(pluginId));
153 | }
154 |
155 | /**
156 | * 是否是插件
157 | *
158 | * @param pluginId 插件id
159 | * @return 是否是插件或者补丁
160 | */
161 | public static boolean isPlugin(String pluginId) {
162 | return !TextUtils.isEmpty(pluginId) && pluginId.startsWith(PluginConstant.EXP_PLUG_PREFIX);
163 | }
164 |
165 | /**
166 | * 是否是补丁文件
167 | *
168 | * @param pluginId 插件id
169 | * @return 是否是补丁文件
170 | */
171 | public static boolean isHotFix(String pluginId) {
172 | return !TextUtils.isEmpty(pluginId) && pluginId.startsWith(PluginConstant.EXP_PLUG_HOT_FIX_PREFIX);
173 | }
174 |
175 | /**
176 | * 关闭流
177 | *
178 | * @param closeable closeable
179 | */
180 | public static void close(Closeable closeable) {
181 | try {
182 | if (closeable != null) closeable.close();
183 | } catch (Throwable e) {
184 | e.printStackTrace();
185 | }
186 | }
187 |
188 |
189 | //start========================获取cpu类型的方法========================start
190 |
191 | /**
192 | * 获取cpu类型和架构
193 | *
194 | * @return 返回CPU的指令集类型,仅支持arm,x86和mips这三种,arm中不区分armv6,armv7和neon,有需要自行添加.
195 | */
196 | public static int getCpuArchitecture() {
197 | try {
198 | InputStream is = new FileInputStream("/proc/cpuinfo");
199 | InputStreamReader ir = new InputStreamReader(is);
200 | BufferedReader br = new BufferedReader(ir);
201 | try {
202 | String nameProcessor = "Processor";
203 | String nameModel = "model name";
204 | while (true) {
205 | String line = br.readLine();
206 | String[] pair;
207 | if (line == null) {
208 | break;
209 | }
210 | pair = line.split(":");
211 | if (pair.length != 2)
212 | continue;
213 | String key = pair[0].trim();
214 | String val = pair[1].trim();
215 | if (key.compareTo(nameProcessor) == 0) {
216 | if (val.contains("ARM")) {
217 | return CPU_AMR;
218 | }
219 | }
220 |
221 | if (key.compareToIgnoreCase(nameModel) == 0) {
222 | if (val.contains("Intel")) {
223 | return CPU_X86;
224 | }
225 | }
226 |
227 | if (key.compareToIgnoreCase(nameProcessor) == 0) {
228 | if (val.contains("MIPS")) {
229 | return CPU_MIPS;
230 | }
231 | }
232 | }
233 | } finally {
234 | close(br);
235 | close(ir);
236 | close(is);
237 | }
238 | } catch (Exception e) {
239 | e.printStackTrace();
240 | }
241 |
242 | return CPU_AMR;
243 | }
244 |
245 | /**
246 | * 获取当前应当执行的so文件的存放文件夹
247 | */
248 | public static String getLibFile(int cpuType) {
249 | switch (cpuType) {
250 | case CPU_AMR:
251 | return "lib/armeabi";
252 | case CPU_X86:
253 | return "lib/x86/";
254 | case CPU_MIPS:
255 | return "lib/mips/";
256 | default:
257 | return "lib/armeabi/";
258 | }
259 | }
260 | //end========================获取cpu类型的方法========================end
261 |
262 |
263 | //start========================文件相关的方法========================start
264 |
265 | /**
266 | * 创建文件夹
267 | *
268 | * @param dirPath 文件夹路径
269 | * @return 是否成功
270 | */
271 | public static boolean createDir(String dirPath) {
272 | File file = new File(dirPath);
273 | return !file.exists() && file.mkdirs();
274 | }
275 |
276 | /**
277 | * 删除文件夹
278 | *
279 | * @param file 文件对象
280 | */
281 | public static void deleteDirectory(File file) {
282 | if (!file.isDirectory()) {
283 | return;
284 | }
285 | File[] paths = file.listFiles();
286 | for (File pathF : paths) {
287 | if (pathF.isDirectory()) {
288 | deleteDirectory(pathF);
289 | } else {
290 | deleteFile(pathF);
291 | }
292 | }
293 | deleteFile(file);
294 | }
295 |
296 | /**
297 | * 为防止创建一个正在被删除的文件夹,所以在删除前先重命名该文件夹
298 | * 可以解决很多快速创建删除而产生的0字节大小文件问题
299 | *
300 | * @param file 文件对象
301 | * @return 是否成功
302 | */
303 | public static boolean deleteFile(File file) {
304 | File to = new File(file.getAbsolutePath() + System.currentTimeMillis());
305 | file.renameTo(to);
306 | return to.delete();
307 | }
308 |
309 | /**
310 | * 重命名
311 | *
312 | * @param filePathName 原始文件路径
313 | * @param newPathName 新的文件路径
314 | * @return 是否成功
315 | */
316 | public static boolean rename(String filePathName, String newPathName) {
317 | if (TextUtils.isEmpty(filePathName)) return false;
318 | if (TextUtils.isEmpty(newPathName)) return false;
319 |
320 | delete(newPathName);
321 |
322 | File file = new File(filePathName);
323 | File newFile = new File(newPathName);
324 | if (!file.exists()) {
325 | return false;
326 | }
327 | File parentFile = newFile.getParentFile();
328 | if (!parentFile.exists()) {
329 | parentFile.mkdirs();
330 | }
331 | return file.renameTo(newFile);
332 | }
333 |
334 | /**
335 | * 创建目录,整个路径上的目录都会创建
336 | *
337 | * @param path 路径
338 | * @return 文件
339 | */
340 | public static File createDirWithFile(String path) {
341 | File file = new File(path);
342 | if (!path.endsWith("/")) {
343 | file = file.getParentFile();
344 | }
345 | if (!file.exists()) {
346 | file.mkdirs();
347 | }
348 | return file;
349 | }
350 |
351 | /**
352 | * 删除文件
353 | */
354 | public static boolean delete(String filePathName) {
355 | if (TextUtils.isEmpty(filePathName)) return false;
356 | File file = new File(filePathName);
357 | return file.isFile() && file.exists() && file.delete();
358 | }
359 |
360 | /**
361 | * 文件是否存在
362 | *
363 | * @param filePathName 文件路径
364 | * @return 文件是否存在
365 | */
366 | public static boolean exists(String filePathName) {
367 | if (TextUtils.isEmpty(filePathName)) return false;
368 | File file = new File(filePathName);
369 | return (!file.isDirectory() && file.exists());
370 | }
371 | //end========================文件相关的方法========================
372 |
373 | //start========================压缩解压相关方法========================
374 |
375 | /**
376 | * 读取zip文件中某个文件为字符串
377 | *
378 | * @param zipFile 压缩文件
379 | * @param fileNameReg 需要获取的文件名
380 | * @return 获取的字符串
381 | */
382 | public static String readZipFileString(String zipFile, String fileNameReg) {
383 | String result = null;
384 | byte[] buffer = new byte[BUF_SIZE];
385 | InputStream in = null;
386 | ZipInputStream zipIn = null;
387 | ByteArrayOutputStream bos = null;
388 | try {
389 | File file = new File(zipFile);
390 | if (!file.exists()) return null;
391 | in = new FileInputStream(file);
392 | zipIn = new ZipInputStream(in);
393 | ZipEntry entry;
394 | while (null != (entry = zipIn.getNextEntry())) {
395 | String zipName = entry.getName();
396 | if (zipName.equals(fileNameReg)) {
397 | int bytes;
398 | int count = 0;
399 | bos = new ByteArrayOutputStream();
400 |
401 | while ((bytes = zipIn.read(buffer, 0, BUF_SIZE)) != -1) {
402 | bos.write(buffer, 0, bytes);
403 | count += bytes;
404 | }
405 | if (count > 0) {
406 | result = bos.toString();
407 | break;
408 | }
409 | }
410 | }
411 | } catch (Exception e) {
412 | e.printStackTrace();
413 | } finally {
414 | try {
415 | close(in);
416 | close(zipIn);
417 | close(bos);
418 | } catch (Exception e2) {
419 | e2.printStackTrace();
420 | }
421 | }
422 | return result;
423 | }
424 |
425 | /**
426 | * 将压缩文件中的某个文件夹拷贝到指定文件夹中
427 | *
428 | * @param zipFile 压缩文件
429 | * @param toDir 指定一个存放解压缩文件的文件夹,或者直接指定文件名方法自动识别
430 | * @param fileNameReg 需要解压的文件夹路径如:res/drawable-hdpi/
431 | * @return 是否成功
432 | */
433 | public static boolean unzipFile(String zipFile, String toDir, String fileNameReg) {
434 | boolean result = false;
435 | byte[] buffer = new byte[BUF_SIZE];
436 | InputStream in = null;
437 | ZipInputStream zipIn = null;
438 | try {
439 | File file = new File(zipFile);
440 | in = new FileInputStream(file);
441 | zipIn = new ZipInputStream(in);
442 | ZipEntry entry;
443 | while (null != (entry = zipIn.getNextEntry())) {
444 | String zipName = entry.getName();
445 | if (zipName.startsWith(fileNameReg)) {
446 | String relName = toDir + zipName;
447 | File unzipFile = new File(toDir);
448 | if (unzipFile.isDirectory()) {
449 | createDirWithFile(relName);
450 | unzipFile = new File(relName);
451 | }
452 | FileOutputStream out = new FileOutputStream(unzipFile);
453 | int bytes;
454 |
455 | while ((bytes = zipIn.read(buffer, 0, BUF_SIZE)) != -1) {
456 | out.write(buffer, 0, bytes);
457 | }
458 | close(out);
459 | }
460 | }
461 | result = true;
462 | } catch (Exception e) {
463 | e.printStackTrace();
464 | result = false;
465 | } finally {
466 | close(in);
467 | close(zipIn);
468 | }
469 | return result;
470 | }
471 |
472 | /**
473 | * 复制assets下文件到一个路径下
474 | * @param assetsFileName 要复制的assets的文件名
475 | * @param filePath 复制后的文件的绝对路径
476 | * @return true表示成功了
477 | */
478 | public static boolean copyAssetsFile(String assetsFileName, String filePath){
479 | FileOutputStream out = null;
480 | InputStream in = null;
481 | try {
482 | AssetManager am = PluginManager.mBaseResources.getAssets();
483 | in = am.open(assetsFileName);
484 | PluginUtil.createDirWithFile(filePath);
485 | out = new FileOutputStream(filePath, false);
486 | byte[] temp = new byte[2048];
487 | int len;
488 | while ((len = in.read(temp)) > 0) {
489 | out.write(temp, 0, len);
490 | }
491 | } catch (Exception e) {
492 | e.printStackTrace();
493 | return false;
494 | } finally {
495 | close(in);
496 | close(out);
497 | }
498 | return true;
499 | }
500 | //end========================压缩解压相关方法========================
501 |
502 | /**
503 | * 获取某个插件的安装的随机路径信息
504 | *
505 | * @param pluginId 插件id
506 | * @return 某个插件的安装的随机路径信息
507 | */
508 | public static String getInstalledPathInfo(String pluginId) {
509 | String result = null;
510 | String libFileInfoPath = getPlugDir(pluginId) + PluginConstant.PLUGIN_INSTALLED_INFO_PATH;
511 | BufferedInputStream bis = null;
512 | ByteArrayOutputStream baos = null;
513 | try {
514 | if (!exists(libFileInfoPath)) return null;
515 | bis = new BufferedInputStream(new FileInputStream(libFileInfoPath));
516 | baos = new ByteArrayOutputStream();
517 | byte[] buffer = new byte[BYTE_IN_SIZE];
518 | int length;
519 | while ((length = bis.read(buffer, 0, BYTE_IN_SIZE)) > -1) {
520 | baos.write(buffer, 0, length);
521 | }
522 | result = new String(baos.toByteArray(), "UTF-8");
523 | } catch (Exception e) {
524 | e.printStackTrace();
525 | } finally {
526 | close(bis);
527 | close(baos);
528 | }
529 | return result;
530 | }
531 |
532 | /**
533 | * 保存某个插件的安装随机路径信息
534 | *
535 | * @param pluginId 插件id
536 | * @param installedPathInfo 插件的安装随机路径信息
537 | * @return 是否成功
538 | */
539 | public static boolean writePathInfo(String pluginId, String installedPathInfo) {
540 | String infoPath = PluginUtil.getPlugDir(pluginId) + PluginConstant.PLUGIN_INSTALLED_INFO_PATH;
541 | File file = new File(infoPath);
542 | FileOutputStream out = null;
543 | try {
544 | if (!file.exists()) {
545 | file.createNewFile();
546 | }
547 | out = new FileOutputStream(file);
548 | out.write(installedPathInfo.getBytes());
549 | } catch (Exception e) {
550 | e.printStackTrace();
551 | return false;
552 | } finally {
553 | close(out);
554 | }
555 | return true;
556 | }
557 |
558 | //start========================反射相关方法========================
559 |
560 | /**
561 | * 反射的方式设置某个类的成员变量的值
562 | *
563 | * @param paramClass 类对象
564 | * @param paramString 域的名称
565 | * @param newClass 新的对象
566 | */
567 | public static void setField(Object paramClass, String paramString,
568 | Object newClass) {
569 | if (paramClass == null || TextUtils.isEmpty(paramString)) return;
570 | Field field = null;
571 | Class cl = paramClass.getClass();
572 | for (; field == null && cl != null; ) {
573 | try {
574 | field = cl.getDeclaredField(paramString);
575 | if (field != null) {
576 | field.setAccessible(true);
577 | }
578 | } catch (Throwable ignored) {
579 |
580 | }
581 | if (field == null) {
582 | cl = cl.getSuperclass();
583 | }
584 | }
585 | if (field != null) {
586 | try {
587 | field.set(paramClass, newClass);
588 | } catch (Throwable e) {
589 | e.printStackTrace();
590 | }
591 | } else {
592 | System.err.print(paramString + " is not found in " + paramClass.getClass().getName());
593 | }
594 | }
595 |
596 | /**
597 | * 设置paramClass中所有名称为paramString的成员变量的值为newClass
598 | * @param paramClass 类对象
599 | * @param paramString 域的名称
600 | * @param newClass 新的对象
601 | */
602 | public static void setFieldAllClass(Object paramClass, String paramString,
603 | Object newClass) {
604 | if (paramClass == null || TextUtils.isEmpty(paramString)) return;
605 | Field field;
606 | Class cl = paramClass.getClass();
607 | for (; cl != null; ) {
608 | try {
609 | field = cl.getDeclaredField(paramString);
610 | if (field != null) {
611 | field.setAccessible(true);
612 | field.set(paramClass, newClass);
613 | }
614 | } catch (Throwable e) {
615 |
616 | }
617 | cl = cl.getSuperclass();
618 | }
619 |
620 | return;
621 | }
622 |
623 | /**
624 | * 反射的方式获取某个类的方法
625 | *
626 | * @param cl 类的class
627 | * @param name 方法名称
628 | * @param parameterTypes 方法对应的输入参数类型
629 | * @return 方法
630 | */
631 | public static Method getMethod(Class cl, String name, Class... parameterTypes) {
632 | Method method = null;
633 | for (; method == null && cl != null; ) {
634 | try {
635 | method = cl.getDeclaredMethod(name, parameterTypes);
636 | if (method != null) {
637 | method.setAccessible(true);
638 | }
639 | } catch (Exception ignored) {
640 |
641 | }
642 | if (method == null) {
643 | cl = cl.getSuperclass();
644 | }
645 | }
646 | return method;
647 | }
648 |
649 | /**
650 | * 反射的方式获取某个类的某个成员变量值
651 | *
652 | * @param paramClass 类对象
653 | * @param paramString field的名字
654 | * @return field对应的值
655 | */
656 | public static Object getField(Object paramClass, String paramString) {
657 | if (paramClass == null) return null;
658 | Field field = null;
659 | Object object = null;
660 | Class cl = paramClass.getClass();
661 | for (; field == null && cl != null; ) {
662 | try {
663 | field = cl.getDeclaredField(paramString);
664 | if (field != null) {
665 | field.setAccessible(true);
666 | }
667 | } catch (Exception ignored) {
668 |
669 | }
670 | if (field == null) {
671 | cl = cl.getSuperclass();
672 | }
673 | }
674 | try {
675 | if (field != null)
676 | object = field.get(paramClass);
677 | } catch (IllegalArgumentException | IllegalAccessException e) {
678 | e.printStackTrace();
679 | }
680 | return object;
681 | }
682 | //end========================反射相关方法========================
683 | }
684 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusBaseActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.res.Resources;
6 |
7 | /**
8 | * 基础的activity
9 | * Created by huangjian on 2016/6/21.
10 | */
11 | public class ZeusBaseActivity extends Activity {
12 |
13 | //---------------------插件相关的代码-----------------------start
14 | ZeusHelper helper = new ZeusHelper();
15 |
16 | @Override
17 | public Object getSystemService(String name) {
18 | return helper.getSystemService(this, super.getSystemService(name), name);
19 | }
20 |
21 | @Override
22 | protected void attachBaseContext(Context newBase) {
23 | super.attachBaseContext(newBase);
24 | helper.attachBaseContext(newBase,this);
25 | }
26 |
27 | @Override
28 | public Resources getResources() {
29 | return PluginManager.getResources();
30 | }
31 |
32 | /**
33 | * 解决有时插件通过inflate找不到资源的问题
34 | * @return Resources.Theme
35 | */
36 | @Override
37 | public Resources.Theme getTheme() {
38 | return helper.getTheme(this);
39 | }
40 |
41 | /**
42 | * 给ZeusHelper调用的获取原始theme的方法
43 | * @return
44 | */
45 | public Resources.Theme getSuperTheme() {
46 | return super.getTheme();
47 | }
48 | //---------------------------插件相关代码-------------------------end
49 | }
50 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusBaseApplication.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.content.res.Configuration;
6 | import android.content.res.Resources;
7 |
8 | /**
9 | * 基础的Application
10 | * Created by huangjian on 2016/6/21.
11 | */
12 | public class ZeusBaseApplication extends Application {
13 |
14 | //---------------------插件相关的代码-----------------------start
15 | public ZeusHelper helper = new ZeusHelper();
16 |
17 | @Override
18 | public Object getSystemService(String name) {
19 | return helper.getSystemService(this, super.getSystemService(name), name);
20 | }
21 |
22 | @Override
23 | public Resources getResources() {//这里需要返回插件框架的resources
24 | return PluginManager.getResources();
25 | }
26 |
27 | @Override
28 | public void onConfigurationChanged(Configuration newConfig) {
29 | super.onConfigurationChanged(newConfig);
30 | //支持切换语言
31 | ZeusHelper.onConfigurationChanged();
32 | }
33 | //---------------------插件相关的代码-----------------------end
34 | }
35 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusClassLoader.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.File;
6 |
7 | import dalvik.system.PathClassLoader;
8 |
9 | import static java.lang.System.arraycopy;
10 |
11 | /***
12 | * 这是一个空ClassLoader,主要是个容器
13 | *
14 | * Created by huangjian on 2016/6/21.
15 | */
16 | class ZeusClassLoader extends PathClassLoader {//DexClassLoader {
17 | //这里每个插件对应着一个ClassLoader,一旦插件更新了,则classLoader也会使用新的。
18 | //这样java的class就会从新的classLoader中查找,而不会去使用旧的classLoader的缓存
19 | private ZeusPluginClassLoader[] mClassLoader = null;
20 | private String mNativeLibraryPath = null;
21 |
22 | public ZeusClassLoader(String dexPath, ClassLoader parent, String nativeLibraryPath) {
23 | super(dexPath, parent);
24 | mNativeLibraryPath = nativeLibraryPath;
25 | }
26 |
27 | public ZeusPluginClassLoader[] getClassLoaders() {
28 | return mClassLoader;
29 | }
30 |
31 | /**
32 | * 添加一个插件到当前的classLoader中
33 | *
34 | * @param pluginId 插件名称
35 | * @param dexPath dex文件路径
36 | * @param libPath so文件夹路径
37 | */
38 | protected void addAPKPath(String pluginId, String dexPath, String libPath) {
39 | if (mClassLoader == null) {
40 | mClassLoader = new ZeusPluginClassLoader[1];
41 | } else {
42 | int oldLenght = mClassLoader.length;
43 | Object[] old = mClassLoader;
44 | mClassLoader = new ZeusPluginClassLoader[oldLenght + 1];
45 | arraycopy(old, 0, mClassLoader, 0, oldLenght);
46 | }
47 | mClassLoader[mClassLoader.length - 1] = new ZeusPluginClassLoader(pluginId, dexPath,
48 | PluginUtil.getDexCacheParentDirectPath(pluginId),
49 | libPath,
50 | getParent());
51 | }
52 |
53 | /**
54 | * 移除一个插件classLoader
55 | *
56 | * @param pluginId 插件id
57 | */
58 | protected void removePlugin(String pluginId) {
59 | if (mClassLoader == null || TextUtils.isEmpty(pluginId)) return;
60 | for (int i = 0; i < mClassLoader.length; i++) {
61 | ZeusPluginClassLoader cl = mClassLoader[i];
62 | if (pluginId.equals(cl.getPluginId())) {
63 | if (mClassLoader.length == 1) {
64 | mClassLoader = null;
65 | return;
66 | }
67 | int oldLength = mClassLoader.length;
68 | Object[] old = mClassLoader;
69 | mClassLoader = new ZeusPluginClassLoader[oldLength - 1];
70 | if (i != 0) {
71 | arraycopy(old, 0, mClassLoader, 0, i);
72 | }
73 | if (i != oldLength - 1) {
74 | arraycopy(old, i + 1, mClassLoader, i, oldLength - i - 1);
75 | }
76 | return;
77 | }
78 | }
79 | }
80 |
81 | @Override
82 | protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
83 | Class> clazz = null;
84 | try {
85 | //先查找parent classLoader,这里实际就是系统帮我们创建的classLoader,目标对应为宿主apk
86 | clazz = getParent().loadClass(className);
87 | } catch (ClassNotFoundException ignored) {
88 |
89 | }
90 |
91 | if (clazz != null) {
92 | return clazz;
93 | }
94 |
95 | //挨个的到插件里进行查找
96 | if (mClassLoader != null) {
97 | for (ZeusPluginClassLoader classLoader : mClassLoader) {
98 | if (classLoader == null) continue;
99 | try {
100 | //这里只查找插件它自己的apk,不需要查parent,避免多次无用查询,提高性能
101 | clazz = classLoader.loadClassByself(className);
102 | if (clazz != null) {
103 | return clazz;
104 | }
105 | } catch (ClassNotFoundException ignored) {
106 |
107 | }
108 | }
109 | }
110 | throw new ClassNotFoundException(className + " in loader " + this);
111 | }
112 |
113 | @Override
114 | public String findLibrary(String LibraryName) {
115 | String pathName = super.findLibrary(LibraryName);
116 | if (!TextUtils.isEmpty(pathName)) return pathName;
117 | if (!TextUtils.isEmpty(mNativeLibraryPath)) {
118 | String fileName = System.mapLibraryName(LibraryName);
119 | File libraryFile = new File(mNativeLibraryPath, fileName);
120 | if (libraryFile.exists()) {
121 | return libraryFile.getAbsolutePath();
122 | }
123 | return null;
124 | }
125 | return null;
126 | }
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusHelper.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.content.Context;
4 | import android.content.ContextWrapper;
5 | import android.content.res.Resources;
6 | import android.view.LayoutInflater;
7 |
8 | /**
9 | * 一些重复的方法放到这里。
10 | * Created by huangjian on 2016/7/14.
11 | */
12 | public class ZeusHelper {
13 |
14 | //---------------------插件相关的代码-----------------------start
15 | /**
16 | * 一旦插件resources发生变化,这个resources就可以用来比较了
17 | */
18 | private Resources mMyResources = null;
19 |
20 | /**
21 | * 配置LAYOUT_INFLATER_SERVICE时的一些参数
22 | *
23 | * @param context 调用着的context
24 | * @param systemServcie systemServer对象
25 | * @param name server的名字
26 | * @return systemServer对象
27 | */
28 | public static Object getSystemService(Context context, Object systemServcie, String name) {
29 | if (Context.LAYOUT_INFLATER_SERVICE.equals(name)) {
30 | LayoutInflater inflater = (LayoutInflater) systemServcie;
31 | inflater.cloneInContext(context);
32 | //使用某些加固之后该inflater里的mContext变量一直是系统的context,根本不是当前Context
33 | //所以这里手动设置一次
34 | PluginUtil.setField(inflater, "mContext", context);
35 | return inflater;
36 | }
37 | return systemServcie;
38 | }
39 |
40 | /**
41 | * 当系统调用attachBaseContext时,进行一些参数的设置
42 | *
43 | * @param newBase base的context即ContextImpl
44 | * @param context 调用者自己
45 | */
46 | public void attachBaseContext(Context newBase, ContextWrapper context) {
47 | //某些手机中的是mOuterContext作为context来用
48 | //这样写还可以防止某些手机的内存泄漏,有些手机会记录它启动当前界面的activity作为mOuterContext,
49 | //而如果之前的activity被finish,那么它也不能被GC回收
50 | PluginUtil.setField(newBase, "mOuterContext", context);
51 | //中兴手机是个奇葩,不知道它怎么实现的又重新生成了一个resources,这里得再次替换
52 | PluginUtil.setField(newBase, "mResources", PluginManager.mNowResources);
53 | mMyResources = PluginManager.mNowResources;
54 | }
55 |
56 | /**
57 | * 解决有时插件通过inflate找不到资源的问题
58 | *
59 | * @return Resources.Theme 调用者自己生成的theme
60 | */
61 | public Resources.Theme getTheme(ZeusBaseActivity zeusBaseActivity) {
62 | Resources localResources = PluginManager.mNowResources;
63 | if ( localResources != null && mMyResources != localResources) {
64 | mMyResources = localResources;
65 |
66 | //中兴的rom中会将zeusBaseActivity.getBaseContext()的一个父类中添加mResources和mTheme,导致设置不全,这里全部设置
67 | PluginUtil.setFieldAllClass(zeusBaseActivity.getBaseContext(), "mResources", localResources);
68 | PluginUtil.setFieldAllClass(zeusBaseActivity.getBaseContext(), "mTheme", null);
69 |
70 | //AppCompatActivity包含了一个Resouces,这里设置为null让其再次生成一遍
71 | PluginUtil.setField(zeusBaseActivity, "mResources", null);
72 | //原始的theme指向的Resources是老的Resources,无法访问新插件,这里设置为null,
73 | // 系统会再次使用新的Resouces来生成一次theme,新的theme才能访问新的插件资源
74 | PluginUtil.setField(zeusBaseActivity, "mTheme", null);
75 | }
76 | return zeusBaseActivity.getSuperTheme();
77 | }
78 |
79 | /**
80 | * 系统配置改变时的回调,是为了支持插件的语言、地区、字体、字号等的切换
81 | */
82 | public static void onConfigurationChanged() {
83 | if (PluginManager.mNowResources != null
84 | && PluginManager.mBaseResources != null
85 | && PluginManager.mNowResources != PluginManager.mBaseResources) {
86 | PluginManager.mNowResources.updateConfiguration(PluginManager.mBaseResources.getConfiguration(),
87 | PluginManager.mBaseResources.getDisplayMetrics());
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusHotfixClassLoader.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.lang.reflect.Method;
8 | import java.util.ArrayList;
9 | import java.util.zip.ZipFile;
10 |
11 | import dalvik.system.DexFile;
12 |
13 | import static java.lang.System.arraycopy;
14 |
15 | /***
16 | * 加载任意后缀的zip格式的classLoader,主要用来加载热修复补丁
17 | * 热修复补丁与插件的区别是热修复补丁只能软件启动的时候加载一次,插件可以随时加载、卸载。
18 | *
19 | * Created by huangjian on 2016/6/21.
20 | */
21 | class ZeusHotfixClassLoader extends ZeusPluginClassLoader {
22 | private ClassLoader mChild = null;
23 | private Method findClassMethod = null;
24 | private Method findLoadedClassMethod = null;
25 |
26 | public ZeusHotfixClassLoader(String dexPath, String dexOutputDir, String libPath,
27 | ClassLoader parent) {
28 | super(null, dexPath, dexOutputDir, libPath,parent);
29 | }
30 |
31 | protected void setOrgAPKClassLoader(ClassLoader child) {
32 | mChild = child;
33 | findLoadedClassMethod = PluginUtil.getMethod(mChild.getClass(), "findLoadedClass", String.class);
34 | findClassMethod = PluginUtil.getMethod(mChild.getClass(), "findClass", String.class);
35 | }
36 |
37 | protected void addAPKPath(String dexPath, String libPath) {
38 | if(mDexs == null){
39 | ensureInit();
40 | }
41 | int oldLength = mDexs.length;
42 | int index = oldLength + 1;
43 |
44 | Object[] old = mDexs;
45 | mDexs = new DexFile[index];
46 | arraycopy(old, 0, mDexs, 0, index - 1);
47 |
48 | old = mFiles;
49 | mFiles = new File[index];
50 | arraycopy(old, 0, mFiles, 0, index - 1);
51 |
52 | old = mZips;
53 | mZips = new ZipFile[index];
54 | arraycopy(old, 0, mZips, 0, index - 1);
55 |
56 | if (!TextUtils.isEmpty(libPath)) {
57 | String pathSep = System.getProperty("path.separator", ":");
58 | if (mRawLibPath.endsWith(pathSep)) {
59 | mRawLibPath = mRawLibPath + libPath;
60 | } else {
61 | mRawLibPath = mRawLibPath + pathSep + libPath;
62 | }
63 | generateLibPath();
64 |
65 | }
66 |
67 | File pathFile = new File(dexPath);
68 | mFiles[oldLength] = pathFile;
69 | if (pathFile.isFile()) {
70 | try {
71 | mZips[oldLength] = new ZipFile(pathFile);
72 | } catch (IOException ioex) {
73 | System.out.println("Failed opening '" + pathFile
74 | + "': " + ioex);
75 | }
76 | }
77 |
78 | try {
79 | String outputName =
80 | generateOutputName(dexPath, mDexOutputPath);
81 | mDexs[oldLength] = DexFile.loadDex(dexPath, outputName, 0);
82 | } catch (IOException e) {
83 | e.printStackTrace();
84 | }
85 | }
86 |
87 | /***
88 | * 每个插件里也可能有多个dex文件,挨个的查找dex文件
89 | */
90 | @Override
91 | protected Class> findClass(String name) throws ClassNotFoundException {
92 | ensureInit();
93 | Class clazz;
94 | int length = mFiles.length;
95 | for (int i = 0; i < length; i++) {
96 |
97 | if (mDexs[i] != null) {
98 | String slashName = name.replace('.', '/');
99 | clazz = mDexs[i].loadClass(slashName, mChild);
100 | if (clazz != null) {
101 | return clazz;
102 | }
103 | }
104 | }
105 | throw new ClassNotFoundException(name + " in loader " + this);
106 | }
107 |
108 | @Override
109 | protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
110 | //先查找补丁自己已经加载过的有没有
111 | Class> clazz = findLoadedClass(className);
112 |
113 | if (clazz == null) {
114 | try {
115 | //查查parent中有没有,也就是android系统中的
116 | clazz = getParent().loadClass(className);
117 | } catch (ClassNotFoundException ignored) {
118 |
119 | }
120 |
121 | if (clazz == null) {
122 | try {
123 | //查查自己有没有,就是补丁中有没有
124 | clazz = findClass(className);
125 | } catch (ClassNotFoundException ignored) {
126 |
127 | }
128 | }
129 | }
130 | //查查child中有没有,child是设置进来的,实际就是宿主apk中有没有
131 | if (clazz == null && mChild != null) {
132 | try {
133 | if (findLoadedClassMethod != null) {
134 | clazz = (Class>) findLoadedClassMethod.invoke(mChild, className);
135 | }
136 | if (clazz != null) return clazz;
137 | if (findClassMethod != null) {
138 | clazz = (Class>) findClassMethod.invoke(mChild, className);
139 | return clazz;
140 | }
141 | } catch (Exception ignored) {
142 |
143 | }
144 | }
145 | if (clazz == null) {
146 | throw new ClassNotFoundException(className + " in loader " + this);
147 | }
148 | return clazz;
149 | }
150 | }
151 |
152 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusInstrumentation.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.app.Activity;
4 | import android.app.Instrumentation;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.text.TextUtils;
8 |
9 | /**
10 | * Created by huangjian on 2016/7/28.
11 | */
12 | public class ZeusInstrumentation extends Instrumentation{
13 |
14 | @Override
15 | public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
16 | if(intent != null){
17 | Bundle bundle = intent.getExtras();
18 | if(bundle != null){
19 | //给Bundle设置classLoader以使Bundle中序列化对象可以直接转化为插件中的对象
20 | //类似于在宿主中这么使用:TestInPlugin testInPlugin = (TestInPlugin)bundle.get("TestInPlugin");
21 | //TestInPlugin是在插件中定义的,如果不这么设置则会找不到TestInPlugin类
22 | bundle.setClassLoader(PluginManager.mNowClassLoader);
23 | if(className.equals("com.zeus.ZeusActivityForStandard")) {
24 | String realActivity = bundle.getString(PluginConstant.PLUGIN_REAL_ACTIVITY);
25 | if (!TextUtils.isEmpty(realActivity)) {
26 | return super.newActivity(cl, realActivity, intent);
27 | }
28 | }
29 | }
30 | }
31 | return super.newActivity(cl, className, intent);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusPlugin.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.content.res.AssetManager;
4 | import android.text.TextUtils;
5 |
6 | import org.json.JSONObject;
7 |
8 | import java.io.File;
9 | import java.io.FileOutputStream;
10 | import java.io.InputStream;
11 |
12 | import dalvik.system.DexClassLoader;
13 |
14 | /**
15 | * 插件类,包括插件的安装、卸载、清除
16 | * Created by huangjian on 2016/6/21.
17 | */
18 | public class ZeusPlugin {
19 | private String mPluginId; //使用插件的安装目录作为插件id
20 | private String mInstalledPathInfo = ""; //安装插件的随机路径信息
21 |
22 | private boolean isInstalling = false;
23 | private boolean isAssetInstalling = false;
24 |
25 | protected ZeusPlugin(String pluginId) {
26 | mPluginId = pluginId;
27 | }
28 |
29 | public synchronized boolean install() {
30 | isInstalling = true;
31 | //创建插件安装目录
32 | PluginUtil.createDir(PluginUtil.getPlugDir(mPluginId));
33 |
34 | //将当前时间记录为插件的随机数,等效于android系统后面~1、~2等
35 | mInstalledPathInfo = String.valueOf(System.nanoTime());
36 |
37 | //获取插件apk文件
38 | String path = PluginUtil.getZipPath(mPluginId);
39 |
40 | //插件文件不存在,则安装asset中的默认插件。
41 | if (!PluginUtil.exists(path)) {
42 | if (PluginUtil.iszeusPlugin(mPluginId)) {
43 | isInstalling = false;
44 | mInstalledPathInfo = getInstalledPathInfoNoCache();
45 | return false;
46 | }
47 | return installAssetPlugin();
48 | }
49 | //把下载路径下的插件文件,直接重命名到安装目录,不需要耗时的拷贝过程。
50 | boolean ret = PluginUtil.rename(PluginUtil.getZipPath(mPluginId), getAPKPath(mPluginId));
51 | if (!ret) {
52 | isInstalling = false;
53 | mInstalledPathInfo = getInstalledPathInfoNoCache();
54 | return false;
55 | }
56 |
57 | //校验是否下载的是正确文件,如果插件下载错误则获取这个配置文件就会失败。
58 | PluginManifest meta = getPluginMeta();
59 | if (meta == null) {
60 | PluginUtil.deleteFile(new File(getAPKPath(mPluginId)));
61 | mInstalledPathInfo = getInstalledPathInfoNoCache();
62 | isInstalling = false;
63 | return false;
64 | }
65 |
66 | //拷贝so文件,一些插件是没有so文件,而这个方法耗时还稍微高点,所以对于没有so的插件和补丁是不会拷贝的。
67 | if (((meta.getFlag() & PluginManifest.FLAG_WITHOUT_SO_FILE) != PluginManifest.FLAG_WITHOUT_SO_FILE )&&
68 | !copySoFile(mInstalledPathInfo, PluginUtil.getCpuArchitecture())) {
69 | isInstalling = false;
70 | mInstalledPathInfo = getInstalledPathInfoNoCache();
71 | return false;
72 | }
73 |
74 | if (!PluginUtil.writePathInfo(mPluginId, mInstalledPathInfo)) {
75 | isInstalling = false;
76 | mInstalledPathInfo = getInstalledPathInfoNoCache();
77 | return false;
78 | }
79 | try {
80 | //预优化补丁dex的加载
81 | new DexClassLoader(getAPKPath(mPluginId), PluginUtil.getDexCacheParentDirectPath(mPluginId), "", PluginManager.mBaseClassLoader);
82 | }catch (Throwable e){
83 | e.printStackTrace();
84 | }
85 | PluginManager.addInstalledPlugin(mPluginId, meta);
86 | isInstalling = false;
87 | return true;
88 | }
89 |
90 | /**
91 | * 安装assets中的插件,assets中插件的文件名要为 mPluginId +".apk"
92 | *
93 | * @return 是否成功
94 | */
95 | public boolean installAssetPlugin() {
96 | PluginManifest meta;
97 | synchronized (this) {
98 | isAssetInstalling = true;
99 | Integer version = PluginManager.getDefaultPlugin().get(mPluginId);
100 | if (version == null || PluginManager.isInstall(mPluginId, version)) {
101 | isInstalling = false;
102 | return true;
103 | }
104 | PluginUtil.createDir(PluginUtil.getPlugDir(mPluginId));
105 | mInstalledPathInfo = String.valueOf(System.nanoTime());
106 |
107 | FileOutputStream out = null;
108 | InputStream in = null;
109 | try {
110 | AssetManager am = PluginManager.mBaseResources.getAssets();
111 | in = am.open(mPluginId + PluginConstant.PLUGIN_SUFF);
112 | PluginUtil.createDirWithFile(getAPKPath(mPluginId));
113 | out = new FileOutputStream(getAPKPath(mPluginId), false);
114 | byte[] temp = new byte[2048];
115 | int len;
116 | while ((len = in.read(temp)) > 0) {
117 | out.write(temp, 0, len);
118 | }
119 | } catch (Exception e) {
120 | e.printStackTrace();
121 | mInstalledPathInfo = getInstalledPathInfoNoCache();
122 | isInstalling = false;
123 | return false;
124 | } finally {
125 | PluginUtil.close(in);
126 | PluginUtil.close(out);
127 | }
128 |
129 | meta = getPluginMeta();
130 | if (meta == null) {
131 | PluginUtil.deleteFile(new File(getAPKPath(mPluginId)));
132 | isAssetInstalling = false;
133 | mInstalledPathInfo = getInstalledPathInfoNoCache();
134 | return false;
135 | }
136 |
137 | //拷贝so文件
138 | if (!copySoFile(mInstalledPathInfo, PluginUtil.getCpuArchitecture())) {
139 | isInstalling = false;
140 | mInstalledPathInfo = getInstalledPathInfoNoCache();
141 | return false;
142 | }
143 |
144 | if (!PluginUtil.writePathInfo(mPluginId, mInstalledPathInfo)) {
145 | isAssetInstalling = false;
146 | mInstalledPathInfo = getInstalledPathInfoNoCache();
147 | return false;
148 | }
149 | isAssetInstalling = false;
150 | }
151 | try {
152 | //预优化补丁dex的加载
153 | new DexClassLoader(getAPKPath(mPluginId), PluginUtil.getDexCacheParentDirectPath(mPluginId), "", PluginManager.mBaseClassLoader);
154 | }catch (Throwable e){
155 | e.printStackTrace();
156 | }
157 | PluginManager.addInstalledPlugin(mPluginId, meta);
158 | return true;
159 | }
160 |
161 | /**
162 | * 清除之前版本的旧数据
163 | */
164 | public synchronized void clearOldPlugin() {
165 | if (getInstalledPathInfo() == null || isAssetInstalling || isInstalling) return;
166 | File pluginDir = new File(PluginUtil.getPlugDir(mPluginId));
167 | String installedPathInfo = getInstalledPathInfoNoCache();
168 | if (TextUtils.isEmpty(installedPathInfo)) return;
169 | if (pluginDir.exists() && pluginDir.isDirectory()) {
170 | File[] list = pluginDir.listFiles();
171 | if (list == null) return;
172 | for (File f : list) {
173 | String fileFullName = f.getName();
174 | if (fileFullName.endsWith(PluginConstant.PLUGIN_JAR_SUFF) || fileFullName.endsWith(PluginConstant.PLUGIN_SUFF)) {
175 | String fileName = fileFullName.substring(0, fileFullName.lastIndexOf("."));
176 | if (!fileName.equalsIgnoreCase(installedPathInfo)) {
177 | f.delete();
178 | File dir = new File(f.getParent() + "/" + fileName);
179 | PluginUtil.deleteDirectory(dir);
180 |
181 | File cacheFile = new File(PluginUtil.getDexCacheFilePath(mPluginId, fileName));
182 | if (cacheFile.exists()) {
183 | cacheFile.delete();
184 | }
185 | }
186 | }
187 | }
188 | }
189 | }
190 |
191 | /**
192 | * 将插件中的lib库拷贝入手机内存中。
193 | * 根据当前手机cpu的类型拷贝合适的lib库入手机内存中
194 | *
195 | * @param installedPathInfo 安装的随机路径信息
196 | * @param cpuType CPU_AMR:1 CPU_X86:2 CPU_MIPS:3 具体见{@link PluginUtil}中的getCpuArchitecture()和getLibFile()方法
197 | * @return 是否成功
198 | */
199 | protected boolean copySoFile(String installedPathInfo, int cpuType) {
200 | String insideLibPath = PluginUtil.getInsidePluginPath() + mPluginId + "/" + installedPathInfo + "/";
201 | PluginUtil.createDir(insideLibPath);
202 | String apkLibPath = PluginUtil.getLibFile(cpuType);
203 | //首先将apk中libs文件夹下的一级so文件拷贝
204 | return PluginUtil.unzipFile(getAPKPath(mPluginId), insideLibPath, apkLibPath);
205 | }
206 |
207 | /**
208 | * 获取插件已经安装的apk路径
209 | *
210 | * @param pluginName 插件id
211 | * @return 插件已经安装的apk路径
212 | */
213 | public String getAPKPath(String pluginName) {
214 | return PluginUtil.getPlugDir(pluginName) + getInstalledPathInfo() + PluginConstant.PLUGIN_SUFF;
215 | }
216 |
217 | /**
218 | * 获取当前安装的随机路径信息,不使用缓存,直接读取文件
219 | *
220 | * @return 当前安装的随机路径信息
221 | */
222 | public String getInstalledPathInfoNoCache() {
223 | return PluginUtil.getInstalledPathInfo(mPluginId);
224 | }
225 |
226 | /**
227 | * 获取插件清单文件信息,不使用缓存,读取速度很快
228 | *
229 | * @return 插件清单文件信息
230 | */
231 | public PluginManifest getPluginMeta() {
232 | PluginManifest meta = null;
233 | String result = readMeta();
234 | if (!TextUtils.isEmpty(result)) {
235 | meta = parserMeta(result);
236 | }
237 | return meta;
238 | }
239 |
240 | /**
241 | * 解析清单文件
242 | *
243 | * @param metaString meta字符串
244 | * @return PluginManifest对象
245 | */
246 | private PluginManifest parserMeta(String metaString) {
247 | PluginManifest meta = new PluginManifest();
248 | try {
249 | JSONObject jObject = new JSONObject(metaString.replaceAll("\r|\n", ""));
250 | meta.name = jObject.optString(PluginManifest.PLUG_NAME);
251 | meta.minVersion = jObject.optString(PluginManifest.PLUG_MIN_VERSION);
252 | meta.maxVersion = jObject.optString(PluginManifest.PLUG_MAX_VERSION);
253 | meta.version = jObject.optString(PluginManifest.PLUG_VERSION);
254 | meta.mainClass = jObject.optString(PluginManifest.PLUG_MAINCLASS);
255 | meta.otherInfo = jObject.optString(PluginManifest.PLUG_OTHER_INFO);
256 | } catch (Exception e) {
257 | e.printStackTrace();
258 | return null;
259 | }
260 | return meta;
261 | }
262 |
263 | /**
264 | * 卸载某个插件,通常情况下不需要卸载,除非需要显示调用
265 | *
266 | * @return 是否成功
267 | */
268 | public boolean uninstall() {
269 | try {
270 | PluginManager.unInstalledPlugin(mPluginId);
271 | //删除手机内存中/data/data/packageName/plugins/mPluginName下的文件
272 | File baseModulePathF = new File(PluginUtil.getPlugDir(mPluginId));
273 | PluginUtil.deleteDirectory(baseModulePathF);
274 | } catch (Exception e) {
275 | e.printStackTrace();
276 | return false;
277 | }
278 | return true;
279 | }
280 |
281 | /**
282 | * 读取meta文件
283 | *
284 | * @return meta字符串
285 | */
286 | private String readMeta() {
287 | return PluginUtil.readZipFileString(getAPKPath(mPluginId), PluginConstant.PLUGINWEB_MAINIFEST_FILE);
288 | }
289 |
290 | /**
291 | * 获取当前安装的随机路径信息,有缓存则使用缓存
292 | *
293 | * @return 当前安装的随机路径信息
294 | */
295 | private String getInstalledPathInfo() {
296 | if (!TextUtils.isEmpty(mInstalledPathInfo)) {
297 | return mInstalledPathInfo;
298 | }
299 | mInstalledPathInfo = PluginUtil.getInstalledPathInfo(mPluginId);
300 | return mInstalledPathInfo;
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/ZeusPlugin/src/main/java/zeus/plugin/ZeusPluginClassLoader.java:
--------------------------------------------------------------------------------
1 | package zeus.plugin;
2 |
3 | import android.text.TextUtils;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.zip.ZipFile;
8 |
9 | import dalvik.system.DexFile;
10 |
11 | /***
12 | * 加载任意后缀的zip格式的classLoader,主要用来加载apk文件
13 | * 如果插件是放在SD卡目录下,最好的格式是jar,有些手机的DexFile只能识别apk或者jar后缀的,因为apk格式会被清理软件当作未安装apk文件清理掉
14 | *
15 | * Created by huangjian on 2016/6/21.
16 | */
17 | class ZeusPluginClassLoader extends ClassLoader {
18 |
19 | protected String mRawLibPath;
20 | protected final String mDexOutputPath;
21 | protected File[] mFiles;
22 | protected ZipFile[] mZips;
23 | protected DexFile[] mDexs;
24 | protected String[] mLibPaths;
25 |
26 | private boolean mInitialized;
27 | public final String mRawDexPath;
28 | final private String mPluginId;
29 |
30 | public ZeusPluginClassLoader(String pluginId, String dexPath, String dexOutputDir, String libPath,
31 | ClassLoader parent) {
32 |
33 | super(parent);
34 | if (dexPath == null || dexOutputDir == null)
35 | throw new NullPointerException();
36 | mPluginId = pluginId;
37 | mRawDexPath = dexPath;
38 | mDexOutputPath = dexOutputDir;
39 | mRawLibPath = libPath;
40 | }
41 |
42 | public String getPluginId() {
43 | return mPluginId;
44 | }
45 |
46 | /***
47 | * 初始化
48 | */
49 | protected synchronized void ensureInit() {
50 | if (mInitialized) {
51 | return;
52 | }
53 |
54 | String[] dexPathList;
55 |
56 | mInitialized = true;
57 |
58 | dexPathList = mRawDexPath.split(":");
59 | int length = dexPathList.length;
60 |
61 | mFiles = new File[length];
62 | mZips = new ZipFile[length];
63 | mDexs = new DexFile[length];
64 |
65 | for (int i = 0; i < length; i++) {
66 | File pathFile = new File(dexPathList[i]);
67 | mFiles[i] = pathFile;
68 |
69 | if (pathFile.isFile()) {
70 | try {
71 | mZips[i] = new ZipFile(pathFile);
72 | } catch (IOException ioex) {
73 | System.out.println("Failed opening '" + pathFile
74 | + "': " + ioex);
75 | }
76 | try {
77 | String outputName =
78 | generateOutputName(dexPathList[i], mDexOutputPath);
79 | mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);
80 | } catch (IOException e) {
81 | e.printStackTrace();
82 | }
83 | }
84 | }
85 | generateLibPath();
86 | }
87 |
88 | protected void generateLibPath() {
89 | //优先查找本地添加的lib库,然后在去系统级别的去找,防止自己的插件中so跟系统存在的so重名了
90 | String pathList;
91 | String systemPathList = System.getProperty("java.library.path", ".");
92 | String pathSep = System.getProperty("path.separator", ":");
93 | String fileSep = System.getProperty("file.separator", "/");
94 |
95 | if (mRawLibPath != null) {
96 | if (systemPathList.length() > 0) {
97 | if (mRawLibPath.endsWith(pathSep)) {
98 | pathList = mRawLibPath + systemPathList;
99 | } else {
100 | pathList = mRawLibPath + pathSep + systemPathList;
101 | }
102 | } else {
103 | pathList = mRawLibPath;
104 | }
105 | } else {
106 | pathList = systemPathList;
107 | }
108 |
109 | mLibPaths = pathList.split(pathSep);
110 | int length = mLibPaths.length;
111 |
112 | for (int i = 0; i < length; i++) {
113 | if (!mLibPaths[i].endsWith(fileSep))
114 | mLibPaths[i] += fileSep;
115 | }
116 | }
117 |
118 | protected static String generateOutputName(String sourcePathName,
119 | String outputDir) {
120 | StringBuilder newStr = new StringBuilder(80);
121 |
122 | newStr.append(outputDir);
123 | if (!outputDir.endsWith("/"))
124 | newStr.append("/");
125 |
126 | String sourceFileName;
127 | int lastSlash = sourcePathName.lastIndexOf("/");
128 | if (lastSlash < 0)
129 | sourceFileName = sourcePathName;
130 | else
131 | sourceFileName = sourcePathName.substring(lastSlash + 1);
132 |
133 | int lastDot = sourceFileName.lastIndexOf(".");
134 | if (lastDot < 0)
135 | newStr.append(sourceFileName);
136 | else
137 | newStr.append(sourceFileName, 0, lastDot);
138 | newStr.append(".dex");
139 |
140 | return newStr.toString();
141 | }
142 |
143 | protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
144 | Class> clazz = findLoadedClass(className);
145 |
146 | if (clazz == null) {
147 | try {
148 | clazz = getParent().loadClass(className);
149 | } catch (ClassNotFoundException e) {
150 | }
151 |
152 | if (clazz == null) {
153 | try {
154 | clazz = findClass(className);
155 | } catch (ClassNotFoundException e) {
156 | throw e;
157 | }
158 | }
159 | }
160 |
161 | return clazz;
162 | }
163 |
164 | /***
165 | * 每个插件里也可能有多个dex文件,挨个的查找dex文件
166 | */
167 | @Override
168 | protected Class> findClass(String name) throws ClassNotFoundException {
169 | ensureInit();
170 | Class clazz;
171 | int length = mFiles.length;
172 | for (int i = 0; i < length; i++) {
173 |
174 | if (mDexs[i] != null) {
175 | String slashName = name.replace('.', '/');
176 | clazz = mDexs[i].loadClass(slashName, this);
177 | if (clazz != null) {
178 | return clazz;
179 | }
180 | }
181 | }
182 | throw new ClassNotFoundException(name + " in loader " + this);
183 | }
184 |
185 |
186 | /**
187 | * 只查找插件自己是否存在该class
188 | *
189 | * @param className 类名字
190 | * @return 类对象
191 | * @throws ClassNotFoundException
192 | */
193 | public Class> loadClassByself(String className) throws ClassNotFoundException {
194 | Class> clazz = findLoadedClass(className);
195 |
196 | if (clazz == null) {
197 | clazz = findClass(className);
198 | }
199 |
200 | return clazz;
201 | }
202 |
203 | @Override
204 | protected String findLibrary(String libname) {//根据插件的pathInfo查找对应的so
205 | ensureInit();
206 |
207 | String fileName = System.mapLibraryName(libname);
208 | for (String libPath : mLibPaths) {
209 | String pathName = libPath + fileName;
210 | File test = new File(pathName);
211 |
212 | if (test.exists()) {
213 | return pathName;
214 | }
215 | }
216 | return null;
217 | }
218 |
219 | }
220 |
221 |
222 |
--------------------------------------------------------------------------------
/aapt/aapt(linux64位版):
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/aapt/aapt(linux64位版)
--------------------------------------------------------------------------------
/aapt/aapt(mac版):
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/aapt/aapt(mac版)
--------------------------------------------------------------------------------
/aapt/aapt(windows版).exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/aapt/aapt(windows版).exe
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | //start----------补丁相关,使用gradle插件,如果宿主是只运行于android 4.4及以上可以不添加这些代码------------------
4 | apply plugin: 'patch-gradle-plugin'
5 | //end------------补丁相关,使用gradle插件,如果宿主是只运行于android 4.4及以上可以不添加这些代码------------------
6 | apply plugin: 'jar-gradle-plugin'//生成jar包插件
7 |
8 | //为了可以使用public.xml来固定宿主的资源ID
9 | apply from: 'public-xml.gradle'
10 |
11 | android {
12 | compileSdkVersion 23
13 | buildToolsVersion '23.0.2'
14 |
15 | defaultConfig {
16 | applicationId "zeus.test"
17 | minSdkVersion 8
18 | targetSdkVersion 23
19 | versionCode 1
20 | versionName "1.0"
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | /**
29 | * --PLUG-resoure-proguard 表示资源混淆,去掉则资源不混淆
30 | * --PLUG-resoure-id 表示资源id命名(Package ID),后面参数表示对应资源id,去掉则为系统应用默认id'0x7f'
31 | */
32 | //去掉为了让demo不需要替换aapt就可以运行
33 | // aaptOptions.additionalParameters '--PLUG-resoure-proguard', '--PLUG-resoure-id', '0x7f'
34 | }
35 |
36 | dependencies {
37 | compile fileTree(include: ['*.jar'], dir: 'libs')
38 | compile project(':ZeusPlugin')
39 | }
40 |
41 |
42 | //start----------补丁相关,使用gradle插件,如果宿主是只运行于android 4.4及以上可以不添加这些代码------------------
43 | //如果enable为true则表明打出的包会在每个类的构造函数中添加如下代码:if (Boolean.FALSE.booleanValue())System.out.println(Predicate.class);
44 | patchPlugin{
45 | enable = true
46 | }
47 | //end------------补丁相关,使用gradle插件,如果宿主是只运行于android 4.4及以上可以不添加这些代码------------------
48 |
49 | //start----------插件相关的,用来生成sdk-jar的------------------
50 | //具体使用参见https://github.com/iReaderAndroid/buildJar
51 | BuildJar{
52 | //输出目录
53 | outputFileDir= project.buildDir.path+"/jar"
54 | //输出原始jar包名
55 | outputFileName="sdk.jar"
56 | //输出混淆jar包名
57 | outputProguardFileName="sdk_proguard.jar"
58 | //混淆配置
59 | proguardConfigFile="proguard-rules.pro"
60 | //是否需要默认的混淆配置proguard-android.txt
61 | needDefaultProguard=true
62 | // applyMappingFile="originMapping/mapping.txt"
63 | //需要输出jar的包名列表,当此参数为空时,则默认全项目输出,支持多包,如 includePackage=['com/adison/testjarplugin/include','com/adison/testjarplugin/include1'...]
64 | // includePackage=['com/adison/testjarplugin/include']
65 | // //不需要输出jar的jar包列表,如['baidu.jar','baidu1.jar'...]
66 | // excludeJar=[]
67 | // //不需要输出jar的类名列表,如['baidu.calss','baidu1.class'...]
68 | // excludeClass=['com/adison/testjarplugin/TestExcude.class']
69 | // //不需要输出jar的包名列表,如 excludePackage=['com/adison/testjarplugin/exclude','com/adison/testjarplugin/exclude1'...]
70 | // excludePackage=['com/adison/testjarplugin/exclude']
71 | }
72 |
73 | //end----------插件相关的,用来生成sdk-jar的------------------
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/public-xml.gradle:
--------------------------------------------------------------------------------
1 | afterEvaluate {
2 | for (variant in android.applicationVariants) {
3 | def scope = variant.getVariantData().getScope()
4 | String mergeTaskName = scope.getMergeResourcesTask().name
5 | def mergeTask = tasks.getByName(mergeTaskName)
6 |
7 | mergeTask.doLast {
8 | copy {
9 | int i=0
10 | from(android.sourceSets.main.res.srcDirs) {
11 | include 'values/public.xml'
12 | rename 'public.xml', (i++ == 0? "public.xml": "public_${i}.xml")
13 | }
14 |
15 | into(mergeTask.outputDir)
16 | }
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/assets/zeushotfix_test.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/app/src/main/assets/zeushotfix_test.apk
--------------------------------------------------------------------------------
/app/src/main/assets/zeusplugin_test.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/app/src/main/assets/zeusplugin_test.apk
--------------------------------------------------------------------------------
/app/src/main/assets/zeusplugin_test_version2.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/app/src/main/assets/zeusplugin_test_version2.apk
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/MainActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.test;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.res.AssetManager;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.Toast;
9 |
10 | import java.io.FileOutputStream;
11 | import java.io.InputStream;
12 |
13 | import zeus.plugin.PluginManager;
14 | import zeus.plugin.PluginUtil;
15 | import zeus.plugin.ZeusBaseActivity;
16 | import zeus.plugin.ZeusPlugin;
17 | import zeus.test.hotfix.TestHotfixActivity1;
18 | import zeus.test.plugin.TestPluginActivity;
19 |
20 |
21 | /**
22 | * Created by huangjian on 2016/6/21.
23 | */
24 | public class MainActivity extends ZeusBaseActivity {
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_main);
30 | }
31 |
32 | /**
33 | * 插件测试
34 | *
35 | * @param view
36 | */
37 | public void testPlugin(View view) {
38 | Intent intent = new Intent(MainActivity.this, TestPluginActivity.class);
39 | startActivity(intent);
40 | }
41 |
42 | /**
43 | * 补丁测试
44 | *
45 | * @param view
46 | */
47 | public void testHotfix(View view) {
48 | //第一次启动是宿主,应用补丁后就是补丁。
49 | Intent intent = new Intent(MainActivity.this, TestHotfixActivity1.class);
50 | startActivity(intent);
51 | }
52 |
53 | /**
54 | * 应用补丁
55 | *
56 | * @param view
57 | */
58 | public void applyHotfix(View view) {
59 | if(PluginManager.isInstall(MyApplication.HOTFIX_TEST)){
60 | Toast.makeText(this, "补丁"+MyApplication.HOTFIX_TEST+"已经被安装,不用再次安装", Toast.LENGTH_SHORT).show();
61 | return;
62 | }
63 | ZeusPlugin zeusPlugin = PluginManager.getPlugin(MyApplication.HOTFIX_TEST);
64 | FileOutputStream out = null;
65 | InputStream in = null;
66 | try {
67 | AssetManager am = PluginManager.mBaseResources.getAssets();
68 | in = am.open("zeushotfix_test.apk");
69 | PluginUtil.createDirWithFile(PluginUtil.getZipPath(MyApplication.HOTFIX_TEST));
70 | out = new FileOutputStream(PluginUtil.getZipPath(MyApplication.HOTFIX_TEST), false);
71 | byte[] temp = new byte[2048];
72 | int len;
73 | while ((len = in.read(temp)) > 0) {
74 | out.write(temp, 0, len);
75 | }
76 | } catch (Exception e) {
77 | e.printStackTrace();
78 | } finally {
79 | PluginUtil.close(in);
80 | PluginUtil.close(out);
81 | }
82 | boolean result= zeusPlugin.install();
83 | if (result) {
84 | Toast.makeText(this, "补丁"+MyApplication.HOTFIX_TEST+"安装成功,下次启动生效", Toast.LENGTH_SHORT).show();
85 | }
86 | }
87 |
88 | @Override
89 | protected void onDestroy() {
90 | super.onDestroy();
91 | android.os.Process.killProcess(android.os.Process.myPid());
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/MyApplication.java:
--------------------------------------------------------------------------------
1 | package zeus.test;
2 |
3 | import android.app.Application;
4 |
5 | import java.util.HashMap;
6 |
7 | import zeus.plugin.PluginManager;
8 | import zeus.plugin.ZeusBaseApplication;
9 |
10 | /**
11 | * Created by huangjain on 2016/6/21.
12 | */
13 | public class MyApplication extends ZeusBaseApplication {
14 | public static final String PLUGIN_TEST = "zeusplugin_test"; //插件测试demo
15 | public static final String HOTFIX_TEST = "zeushotfix_test"; //热修复补丁测试demo
16 | @Override
17 | public void onCreate() {
18 | super.onCreate();
19 | HashMap defaultList = new HashMap<>();
20 | /**
21 | * apk自带的插件的列表,每次添加内置插件的时候需要添加到这里,格式(pluginName,pluginVersion)
22 | * pluginVersion一定要与插件中PLUGINWEB_MAINIFEST_FILE文件里的version保持一致。
23 | * 对于插件还可以使用diff补丁的形式下载增量包,这样可以降低文件下载的大小。
24 | */
25 | //补丁必须以EXP_PLUG_HOT_FIX_PREFIX开头
26 | //插件必须以PluginUtil.EXP_PLUG_PREFIX开头,否则不会识别为插件
27 | defaultList.put(PLUGIN_TEST, 1);
28 | PluginManager.init(this, defaultList);
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/StringConstant.java:
--------------------------------------------------------------------------------
1 | package zeus.test;
2 |
3 | /**
4 | * Created by huangjian on 2016/12/6.
5 | * 这里把所有宿主向插件暴露的资源id都列举,其值应与public.xml中的配置保持一致
6 | */
7 | public class StringConstant {
8 | public static final int string1 = 0x7f050024;
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/hotfix/TestHotFix.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfix;
2 |
3 | /**
4 | * Created by huangjian on 2016/8/22.
5 | */
6 | public class TestHotFix {
7 | public String getTestString(){
8 | return "这是宿主";
9 | }
10 |
11 | public String getTestString2(){
12 | return getTestString() + TestHotFixActivity.getString();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/hotfix/TestHotFixActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfix;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.Gravity;
6 | import android.widget.TextView;
7 | import android.widget.Toast;
8 |
9 | import zeus.plugin.ZeusBaseActivity;
10 | import zeus.test.MainActivity;
11 | import zeus.test.R;
12 | import zeus.test.hotfixTest.MyInterface;
13 |
14 | /**
15 | * 补丁测试页面
16 | *
17 | * @author adison
18 | * @date 16/8/21
19 | * @time 上午2:04
20 | */
21 | public class TestHotFixActivity extends ZeusBaseActivity {
22 |
23 | private MyInterface test = new MyInterface() {
24 | @Override
25 | public String getString() {
26 | return MainActivity.class.getName();
27 | }
28 | };
29 |
30 | private MyInterface test1 = new MyInterface() {
31 | @Override
32 | public String getString() {
33 | return MainActivity.class.getName();
34 | }
35 | };
36 |
37 | private MyInterface test2 = new MyInterface() {
38 | @Override
39 | public String getString() {
40 | return MainActivity.class.getName();
41 | }
42 | };
43 |
44 | private MyInterface test3 = new MyInterface() {
45 | @Override
46 | public String getString() {
47 | return MainActivity.class.getName();
48 | }
49 | };
50 |
51 | private MyInterface test4 = new MyInterface() {
52 | @Override
53 | public String getString() {
54 | return MainActivity.class.getName();
55 | }
56 | };
57 |
58 | private MyInterface test5 = new MyInterface() {
59 | @Override
60 | public String getString() {
61 | return MainActivity.class.getName();
62 | }
63 | };
64 |
65 | private MyInterface test6 = new MyInterface() {
66 | @Override
67 | public String getString() {
68 | return MainActivity.class.getName();
69 | }
70 | };
71 |
72 | private MyInterface test7 = new MyInterface() {
73 | @Override
74 | public String getString() {
75 | return MainActivity.class.getName();
76 | }
77 | };
78 |
79 | private MyInterface test8 = new MyInterface() {
80 | @Override
81 | public String getString() {
82 | return MainActivity.class.getName();
83 | }
84 | };
85 |
86 | @Override
87 | protected void onCreate(Bundle savedInstanceState) {
88 | super.onCreate(savedInstanceState);
89 | setContentView(R.layout.activity_testhotfix);
90 | TextView textView = (TextView) findViewById(R.id.text_view);
91 | textView.setTextColor(getResources().getColor(android.R.color.black));
92 | textView.setTextSize(18);
93 | textView.setGravity(Gravity.CENTER);
94 | textView.setText(new TestHotFix().getTestString());
95 | setContentView(textView);
96 | setTitle(new TestHotFix().getTestString2());
97 | Toast.makeText(this, test.getString(), Toast.LENGTH_LONG).show();
98 | if(test1 == test2 ||
99 | test3 == test4||
100 | test5 == test6||
101 | test7 == test8
102 | ){
103 | int a = 0;
104 | int b =a;
105 | }
106 | }
107 |
108 | public static String getString(){
109 | return "页面";
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/hotfix/TestHotfixActivity1.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfix;
2 |
3 | /**
4 | * Created by jimor on 2017/3/15.
5 | */
6 | public class TestHotfixActivity1 extends TestHotFixActivity{
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/hotfixTest/MyInterface.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfixTest;
2 |
3 | /**
4 | * Created by jimor on 2017/3/15.
5 | */
6 | public interface MyInterface {
7 |
8 | String getString();
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/plugin/TestPluginActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.test.plugin;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.res.AssetManager;
6 | import android.os.Bundle;
7 | import android.view.View;
8 | import android.widget.Toast;
9 |
10 | import java.io.FileOutputStream;
11 | import java.io.InputStream;
12 |
13 | import zeus.plugin.PluginManager;
14 | import zeus.plugin.PluginUtil;
15 | import zeus.plugin.ZeusBaseActivity;
16 | import zeus.plugin.ZeusPlugin;
17 | import zeus.test.MyApplication;
18 | import zeus.test.R;
19 |
20 | /**
21 | * 插件测试页面
22 | *
23 | * @author adison
24 | * @date 16/8/21
25 | * @time 上午1:09
26 | */
27 | public class TestPluginActivity extends ZeusBaseActivity {
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | setContentView(R.layout.activity_plugin);
32 | setTitle("插件测试");
33 | findViewById(R.id.plugin_test).setOnClickListener(new View.OnClickListener() {
34 | @Override
35 | public void onClick(View v) {
36 | startPlugin();
37 | }
38 | });
39 |
40 | findViewById(R.id.plugin_install).setOnClickListener(new View.OnClickListener() {
41 | @Override
42 | public void onClick(View v) {
43 | installPlugin();
44 | }
45 | });
46 | }
47 |
48 | /**
49 | * 启动插件
50 | *
51 | */
52 | public void startPlugin() {
53 | PluginManager.loadLastVersionPlugin(MyApplication.PLUGIN_TEST);
54 | try {
55 | Class cl = PluginManager.mNowClassLoader.loadClass(PluginManager.getPlugin(MyApplication.PLUGIN_TEST).getPluginMeta().mainClass);
56 | Intent intent = new Intent(this, cl);
57 | //这种方式为通过在宿主AndroidManifest.xml中预埋activity实现
58 | // startActivity(intent);
59 | //这种方式为通过欺骗android系统的activity存在性校验的方式实现
60 | PluginManager.startActivity(this,intent);
61 | } catch (ClassNotFoundException e) {
62 | e.printStackTrace();
63 | }
64 | }
65 |
66 | /**
67 | * 安装assets中高版本插件plugin_test_version2.apk
68 | * 先拷贝到PluginUtil.getZipPath(PluginConfig.PLUGIN_TEST)
69 | * 然后调用install()安装。
70 | *
71 | */
72 | public void installPlugin() {
73 | ZeusPlugin zeusPlugin = PluginManager.getPlugin(MyApplication.PLUGIN_TEST);
74 | FileOutputStream out = null;
75 | InputStream in = null;
76 | try {
77 | AssetManager am = PluginManager.mBaseResources.getAssets();
78 | in = am.open("zeusplugin_test_version2.apk");
79 | PluginUtil.createDirWithFile(PluginUtil.getZipPath(MyApplication.PLUGIN_TEST));
80 | out = new FileOutputStream(PluginUtil.getZipPath(MyApplication.PLUGIN_TEST), false);
81 | byte[] temp = new byte[2048];
82 | int len;
83 | while ((len = in.read(temp)) > 0) {
84 | out.write(temp, 0, len);
85 | }
86 | } catch (Exception e) {
87 | e.printStackTrace();
88 | } finally {
89 | PluginUtil.close(in);
90 | PluginUtil.close(out);
91 | }
92 |
93 | boolean installed=zeusPlugin.install();
94 | if(installed){
95 | Toast.makeText(PluginManager.mBaseContext,"高版本插件安装成功",Toast.LENGTH_SHORT).show();
96 | }
97 | }
98 |
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/zeus/test/plugin/TestView.java:
--------------------------------------------------------------------------------
1 | package zeus.test.plugin;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.widget.TextView;
6 |
7 | import zeus.test.R;
8 |
9 | /**
10 | * Created by huangjian on 2016/12/6.
11 | * 这是用来测试把宿主中的控件作为公共组件提供给插件
12 | */
13 | public class TestView extends TextView{
14 | public TestView(Context context) {
15 | super(context);
16 | setBackgroundResource(R.color.test_view_background);
17 | }
18 |
19 | public TestView(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public TestView(Context context, AttributeSet attrs, int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/app/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
32 |
33 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_testhotfix.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #11eeee
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ZeusPlugin
3 | 这是宿主给插件提供的字符串
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | //设置为2.0以上可能会打开instant run,那么补丁要想调试需要进行代码上的更改,具体请看PluginManager.java中的loadHotfixPluginClassLoader()
10 | classpath 'com.android.tools.build:gradle:1.5.0'
11 |
12 | //start-----补丁插件--------
13 | //这里是使用远程依赖的gradle插件来执行插庄过程,源码在https://github.com/iReaderAndroid/PatchPluginForZeus
14 | classpath 'zeusplugin:patch-gradle-plugin:1.0.0'
15 | // end------补丁插件---------
16 |
17 | //start----------插件相关的,用来生成sdk-jar的------------------
18 | //这是使用buildJar任务的远程依赖,用来打sdk的jar用的,源码在https://github.com/iReaderAndroid/buildJar
19 | classpath 'com.adison.gradleplugin:jar:1.0.1'
20 | // end----------插件相关的,用来生成sdk-jar的------------------
21 |
22 | }
23 | }
24 |
25 | allprojects {
26 | repositories {
27 | jcenter()
28 | mavenCentral()
29 | }
30 | }
31 |
32 | task clean(type: Delete) {
33 | delete rootProject.buildDir
34 | }
35 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Thu Sep 08 11:23:54 CST 2016
16 | systemProp.http.proxyHost=59.151.100.77
17 | systemProp.http.proxyPort=33100
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/screenshot/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/screenshot/demo.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':testplugin', ':testhotfix', ':ZeusPlugin'
2 |
--------------------------------------------------------------------------------
/testhotfix/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/testhotfix/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '23.0.2'
6 |
7 | defaultConfig {
8 | applicationId "zeus.testhotfix"
9 | minSdkVersion 8
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | //start-----补丁相关-------------------
21 | aaptOptions.additionalParameters '--PLUG-resoure-id', '0x7a'
22 | //end-------补丁相关-------------------
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | //start-----补丁相关-------------------
28 | provided fileTree(include: ['*.jar'], dir: 'sdk-jars')
29 | //end-------补丁相关-------------------
30 | // provided 'com.android.support:appcompat-v7:23.1.1'
31 | }
32 |
--------------------------------------------------------------------------------
/testhotfix/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/testhotfix/sdk-jars/hotfix_sdk.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/testhotfix/sdk-jars/hotfix_sdk.jar
--------------------------------------------------------------------------------
/testhotfix/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/testhotfix/src/main/assets/zeusplugin.meta:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zeushotfix_test",
3 | "minVersion": "1",
4 | "version": "1",
5 | "otherInfo": "",
6 | "flag": ""
7 | }
--------------------------------------------------------------------------------
/testhotfix/src/main/java/zeus/test/hotfix/TestHotFix.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfix;
2 |
3 | /**
4 | * Created by huangjian on 2016/8/22.
5 | */
6 | public class TestHotFix {
7 | String test = "补丁";
8 | public String getTestString(){
9 | return "这是" + getTestString3();
10 | }
11 |
12 | public String getTestString2(){
13 | return getTestString() + TestHotFixActivity.getString();
14 | }
15 |
16 | public String getTestString3(){
17 | return test;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/testhotfix/src/main/java/zeus/test/hotfix/TestHotFixActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.test.hotfix;
2 |
3 | import android.os.Bundle;
4 | import android.view.Gravity;
5 | import android.widget.TextView;
6 | import android.widget.Toast;
7 |
8 | import zeus.plugin.ZeusBaseActivity;
9 | import zeus.test.MainActivity;
10 | import zeus.test.R;
11 | import zeus.test.hotfixTest.MyInterface;
12 |
13 | /**
14 | * 补丁测试页面
15 | *
16 | * @author adison
17 | * @date 16/8/21
18 | * @time 上午2:04
19 | */
20 | public class TestHotFixActivity extends ZeusBaseActivity {
21 |
22 | private MyInterface test = new MyInterface() {
23 | @Override
24 | public String getString() {
25 | return MainActivity.class.getName();
26 | }
27 | };
28 |
29 | private MyInterface test1 = new MyInterface() {
30 | @Override
31 | public String getString() {
32 | return TestHotFixActivity.this.getString();
33 | }
34 | };
35 |
36 | private MyInterface test2 = new MyInterface() {
37 | @Override
38 | public String getString() {
39 | return TestHotFixActivity.this.getString();
40 | }
41 | };
42 |
43 | private MyInterface test3 = new MyInterface() {
44 | @Override
45 | public String getString() {
46 | return TestHotFixActivity.this.getString();
47 | }
48 | };
49 |
50 | private MyInterface test4 = new MyInterface() {
51 | @Override
52 | public String getString() {
53 | return TestHotFixActivity.this.getString();
54 | }
55 | };
56 |
57 | private MyInterface test5 = new MyInterface() {
58 | @Override
59 | public String getString() {
60 | return TestHotFixActivity.this.getString();
61 | }
62 | };
63 |
64 | private MyInterface test6 = new MyInterface() {
65 | @Override
66 | public String getString() {
67 | return TestHotFixActivity.this.getString();
68 | }
69 | };
70 |
71 | private MyInterface test7 = new MyInterface() {
72 | @Override
73 | public String getString() {
74 | return TestHotFixActivity.this.getString();
75 | }
76 | };
77 |
78 | private MyInterface test8 = new MyInterface() {
79 | @Override
80 | public String getString() {
81 | return TestHotFixActivity.this.getString();
82 | }
83 | };
84 |
85 | @Override
86 | protected void onCreate(Bundle savedInstanceState) {
87 | super.onCreate(savedInstanceState);
88 | setContentView(R.layout.activity_main);
89 | TextView textView = (TextView) findViewById(R.id.text_view);
90 | //如果是红色说明走的是补丁类
91 | //activity_main中textSize也变大了,是25dp
92 | textView.setTextColor(getResources().getColor(android.R.color.holo_red_light));
93 | textView.setGravity(Gravity.CENTER);
94 | textView.setText(new TestHotFix().getTestString());
95 | Toast.makeText(this, test.getString(), Toast.LENGTH_LONG).show();
96 | if (test1 == test2 ||
97 | test3 == test4 ||
98 | test5 == test6 ||
99 | test7 == test8
100 | ) {
101 | int a = 0;
102 | int b = a;
103 | }
104 | }
105 |
106 | public static String getString() {
107 | return "页面1";
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/testhotfix/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/testhotfix/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/testhotfix/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | testHotfix
3 |
4 |
--------------------------------------------------------------------------------
/testhotfix/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/testplugin/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/testplugin/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '23.0.2'
6 |
7 | defaultConfig {
8 | applicationId "zeus.testplugin"
9 | minSdkVersion 8
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 |
15 | //start-----插件相关-------------------
16 | aaptOptions.additionalParameters '--PLUG-resoure-proguard', '--PLUG-resoure-id', '0x7d'
17 | //end-------插件相关-------------------
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | lintOptions {
26 | abortOnError false
27 | }
28 | }
29 |
30 | dependencies {
31 | compile fileTree(dir: 'libs', include: ['*.jar'])
32 |
33 | //start-----插件相关-------------------
34 | provided fileTree(include: ['*.jar'], dir: 'sdk-jars')
35 | //end-------插件相关-------------------
36 | }
37 |
--------------------------------------------------------------------------------
/testplugin/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/testplugin/sdk-jars/plugin_sdk.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iReaderAndroid/ZeusPlugin/ce1008040165602ed9a9544571529af5b6b83d79/testplugin/sdk-jars/plugin_sdk.jar
--------------------------------------------------------------------------------
/testplugin/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/testplugin/src/main/assets/zeusplugin.meta:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zeusplugin_asset",
3 | "minVersion": "1",
4 | "version": "2",
5 | "mainClass": "zeus.testplugin.MainActivity",
6 | "otherInfo": "",
7 | "flag": ""
8 | }
--------------------------------------------------------------------------------
/testplugin/src/main/java/zeus/testplugin/MainActivity.java:
--------------------------------------------------------------------------------
1 | package zeus.testplugin;
2 |
3 | import android.os.Bundle;
4 |
5 | import zeus.plugin.ZeusBaseActivity;
6 | import zeus.test.StringConstant;
7 | import zeus.test.plugin.TestView;
8 |
9 | /**
10 | * Created by huangjian on 2016/7/8.
11 | */
12 | public class MainActivity extends ZeusBaseActivity {
13 |
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.activity_main);
18 | // String version = "1";
19 | String version = "2";
20 |
21 | TestView textView = (TestView) findViewById(R.id.action0);
22 | //内置插件zeusplugin_test, 记得assets下的zeusplugin.meta里的version也得改为对应的版本
23 | textView.setText("这是插件,版本为" + version + " \n" + getResources().getString(StringConstant.string1));
24 | setTitle("插件,版本为" + version);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/testplugin/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/testplugin/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/testplugin/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/testplugin/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TestPlugin
3 |
4 |
--------------------------------------------------------------------------------
/testplugin/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------