diff --git a/aapt_linux b/aapt_linux
new file mode 100755
index 0000000..f6cadbb
Binary files /dev/null and b/aapt_linux differ
diff --git a/aapt_mac b/aapt_mac
new file mode 100755
index 0000000..92fc631
Binary files /dev/null and b/aapt_mac differ
diff --git a/aapt_win.exe b/aapt_win.exe
new file mode 100755
index 0000000..a9ca930
Binary files /dev/null and b/aapt_win.exe differ
diff --git a/apk_module_config.xml b/apk_module_config.xml
new file mode 100644
index 0000000..348ca2f
--- /dev/null
+++ b/apk_module_config.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..d28c38c
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,52 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ //jcenter()
+ maven { url "http://mirrors.ibiblio.org/maven2"}
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:1.3.1'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+ext{
+
+ //这是工程根目录
+ ctripRoot = project(":").projectDir
+
+ // local.properties 来自于AS自动创建
+ // 可以保存所有本地工程配置
+ // 不允许上传git库
+ Properties properties = new Properties()
+ properties.load(project.rootProject.file('local.properties').newDataInputStream())
+
+ // 从系统环境变量中或者local.properties配置文件中读取SDK位置
+ if(System.getenv("ANDROID_HOME")!=null){
+ sdkDir = System.getenv("ANDROID_HOME")
+ }
+ else{ // 在local.properties中定义
+ sdkDir = properties.getProperty('sdk.dir')
+ }
+
+ //Debug代码:开发人员可以手动改为false,这样工程就是标准Android工程,可供开发调试。
+ //改为true,请使用 gradle assembleRelease bundleRelease repackAll 命令打出多apk的release包。
+ solidMode = true
+ //可以在local.properties里修改这个值。添加一行 solidMode=false 即可。
+ solidModeConfigValue = properties.getProperty('solidMode')
+ if('true'.equalsIgnoreCase(solidModeConfigValue)){
+ solidMode = true
+ }
+ else if ('false'.equalsIgnoreCase(solidModeConfigValue)){
+ solidMode = false
+ }
+}
+
+allprojects {
+ repositories {
+ jcenter()
+ }
+}
diff --git a/bundle/.gitignore b/bundle/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/bundle/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/bundle/AndroidManifest.xml b/bundle/AndroidManifest.xml
new file mode 100644
index 0000000..382fa2c
--- /dev/null
+++ b/bundle/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/bundle/assets/.gitignore b/bundle/assets/.gitignore
new file mode 100644
index 0000000..eb03f4b
--- /dev/null
+++ b/bundle/assets/.gitignore
@@ -0,0 +1,5 @@
+# Ignore everything in this directory
+*
+# Except this file
+!.gitignore
+
diff --git a/bundle/build.gradle b/bundle/build.gradle
new file mode 100644
index 0000000..3540858
--- /dev/null
+++ b/bundle/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.library'
+apply from: '../global_config.gradle'
+version "1.0"
+android {
+ defaultConfig {
+ versionCode 1
+ versionName project.version
+ }
+}
+
+dependencies {
+ compile fileTree(dir: 'libs', include: ['*.jar'])
+}
diff --git a/bundle/proguard-rules.pro b/bundle/proguard-rules.pro
new file mode 100644
index 0000000..f0baf96
--- /dev/null
+++ b/bundle/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/yb.wang/Downloads/android_SDK/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/bundle/res/values/strings.xml b/bundle/res/values/strings.xml
new file mode 100644
index 0000000..85a73ab
--- /dev/null
+++ b/bundle/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ bundle
+
diff --git a/bundle/src/ctrip/android/bundle/framework/Bundle.java b/bundle/src/ctrip/android/bundle/framework/Bundle.java
new file mode 100755
index 0000000..9c44fa0
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/Bundle.java
@@ -0,0 +1,18 @@
+package ctrip.android.bundle.framework;
+
+import java.io.InputStream;
+
+public interface Bundle {
+
+
+ long getBundleId();
+
+
+ String getLocation();
+
+
+ int getState();
+
+
+ void update(InputStream inputStream) throws BundleException;
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/BundleCore.java b/bundle/src/ctrip/android/bundle/framework/BundleCore.java
new file mode 100644
index 0000000..7cd6ef4
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/BundleCore.java
@@ -0,0 +1,190 @@
+package ctrip.android.bundle.framework;
+
+import android.app.Application;
+import android.content.res.Resources;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import ctrip.android.bundle.hack.AndroidHack;
+import ctrip.android.bundle.hack.SysHacks;
+import ctrip.android.bundle.log.Logger;
+import ctrip.android.bundle.log.LoggerFactory;
+import ctrip.android.bundle.runtime.BundleInstalledListener;
+import ctrip.android.bundle.runtime.DelegateResources;
+import ctrip.android.bundle.runtime.InstrumentationHook;
+import ctrip.android.bundle.runtime.RuntimeArgs;
+
+/**
+ * Created by yb.wang on 15/1/5.
+ * Bundle机制外部核心类
+ * 采用单例模式封装了外部调用方法
+ */
+public class BundleCore {
+
+ public static final String LIB_PATH = "assets/baseres/";
+ protected static BundleCore instance;
+ static final Logger log;
+ private List bundleDelayListeners;
+ private List bundleSyncListeners;
+
+ static {
+ log = LoggerFactory.getLogcatLogger("BundleCore");
+
+ }
+
+ private BundleCore() {
+ bundleDelayListeners = new ArrayList();
+ bundleSyncListeners = new ArrayList();
+ }
+
+ public static synchronized BundleCore getInstance() {
+ if (instance == null)
+ instance = new BundleCore();
+ return instance;
+ }
+
+
+ public void ConfigLogger(boolean isOpenLog, int level) {
+ LoggerFactory.isNeedLog = isOpenLog;
+ LoggerFactory.minLevel = Logger.LogLevel.getValue(level);
+ }
+
+ public void init(Application application) throws Exception {
+ SysHacks.defineAndVerify();
+ RuntimeArgs.androidApplication = application;
+ RuntimeArgs.delegateResources = application.getResources();
+ AndroidHack.injectInstrumentationHook(new InstrumentationHook(AndroidHack.getInstrumentation(), application.getBaseContext()));
+ }
+
+ public void startup(Properties properties) {
+ try {
+ Framework.startup(properties);
+ } catch (Exception e) {
+ log.log("Bundle Dex installation failure", Logger.LogLevel.ERROR, e);
+ throw new RuntimeException("Bundle dex installation failed (" + e.getMessage() + ").");
+ }
+ }
+
+ public void run() {
+ try {
+
+ log.log("run", Logger.LogLevel.ERROR);
+ for (Bundle bundle : BundleCore.getInstance().getBundles()) {
+
+ BundleImpl bundleImpl = (BundleImpl) bundle;
+ try {
+ bundleImpl.optDexFile();
+ } catch (Exception e) {
+ e.printStackTrace();
+ log.log("Error while dexopt >>>", Logger.LogLevel.ERROR, e);
+ }
+ }
+ notifySyncBundleListers();
+ DelegateResources.newDelegateResources(RuntimeArgs.androidApplication, RuntimeArgs.delegateResources);
+
+ } catch (Exception e) {
+ Log.e("Bundleinstall", "Bundle Dex installation failure", e);
+ throw new RuntimeException("Bundle dex installation failed (" + e.getMessage() + ").");
+ }
+ System.setProperty("BUNDLES_INSTALLED", "true");
+ }
+
+
+ private void notifyDelayBundleListers() {
+ if (!bundleDelayListeners.isEmpty()) {
+ for (BundleInstalledListener bundleInstalledListener : bundleDelayListeners) {
+ bundleInstalledListener.onBundleInstalled();
+ }
+ }
+
+ }
+
+ private void notifySyncBundleListers() {
+ if (!bundleSyncListeners.isEmpty()) {
+ for (BundleInstalledListener bundleInstalledListener : bundleSyncListeners) {
+ bundleInstalledListener.onBundleInstalled();
+ }
+ }
+ }
+
+ public Bundle getBundle(String bundleName) {
+ return Framework.getBundle(bundleName);
+ }
+
+ public Bundle installBundle(String location, InputStream inputStream) throws BundleException {
+ return Framework.installNewBundle(location, inputStream);
+ }
+
+
+ public void updateBundle(String location, InputStream inputStream) throws BundleException {
+ Bundle bundle = Framework.getBundle(location);
+ if (bundle != null) {
+ bundle.update(inputStream);
+ return;
+ }
+ throw new BundleException("Could not update bundle " + location + ", because could not find it");
+ }
+
+
+ public void uninstallBundle(String location) throws BundleException {
+ Bundle bundle = Framework.getBundle(location);
+ if (bundle != null) {
+ BundleImpl bundleImpl = (BundleImpl) bundle;
+ try {
+ bundleImpl.getArchive().purge();
+
+ } catch (Exception e) {
+ log.log("uninstall bundle error: " + location + e.getMessage(), Logger.LogLevel.ERROR);
+ }
+ }
+ }
+
+ public List getBundles() {
+ return Framework.getBundles();
+ }
+
+ public Resources getDelegateResources() {
+ return RuntimeArgs.delegateResources;
+ }
+
+
+
+ public File getBundleFile(String location) {
+ Bundle bundle = Framework.getBundle(location);
+ return bundle != null ? ((BundleImpl) bundle).archive.getArchiveFile() : null;
+ }
+
+ public InputStream openAssetInputStream(String location, String fileName) throws IOException {
+ Bundle bundle = Framework.getBundle(location);
+ return bundle != null ? ((BundleImpl) bundle).archive.openAssetInputStream(fileName) : null;
+ }
+
+ public InputStream openNonAssetInputStream(String location, String str2) throws IOException {
+ Bundle bundle = Framework.getBundle(location);
+ return bundle != null ? ((BundleImpl) bundle).archive.openNonAssetInputStream(str2) : null;
+ }
+
+ public void addBundleDelayListener(BundleInstalledListener bundleListener) {
+ bundleDelayListeners.add(bundleListener);
+ }
+
+ public void removeBundleDelayListener(BundleInstalledListener bundleListener) {
+ bundleDelayListeners.remove(bundleListener);
+ }
+
+ public void addBundleSyncListener(BundleInstalledListener bundleListener) {
+ bundleSyncListeners.add(bundleListener);
+ }
+
+ public void removeBundleSyncListener(BundleInstalledListener bundleListener) {
+ bundleSyncListeners.remove(bundleListener);
+ }
+
+
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/BundleException.java b/bundle/src/ctrip/android/bundle/framework/BundleException.java
new file mode 100755
index 0000000..461a175
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/BundleException.java
@@ -0,0 +1,19 @@
+package ctrip.android.bundle.framework;
+
+public class BundleException extends Exception {
+ private transient Throwable throwable;
+
+ public BundleException(String str, Throwable th) {
+ super(str);
+ this.throwable = th;
+ }
+
+ public BundleException(String str) {
+ super(str);
+ this.throwable = null;
+ }
+
+ public Throwable getNestedException() {
+ return this.throwable;
+ }
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/BundleImpl.java b/bundle/src/ctrip/android/bundle/framework/BundleImpl.java
new file mode 100644
index 0000000..c7af0a4
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/BundleImpl.java
@@ -0,0 +1,163 @@
+package ctrip.android.bundle.framework;
+
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import ctrip.android.bundle.framework.storage.Archive;
+import ctrip.android.bundle.framework.storage.BundleAchive;
+import ctrip.android.bundle.log.Logger;
+import ctrip.android.bundle.log.LoggerFactory;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ * Bundle接口实现类,管理Bundle的生命周期。
+ * meta文件存储BundleId,Location等
+ */
+public final class BundleImpl implements Bundle {
+ static final Logger log;
+ Archive archive;
+ final File bundleDir;
+ final String location;
+ final long bundleID;
+ int state;
+ //是否dex优化
+ volatile boolean isOpt;
+
+ static {
+ log = LoggerFactory.getLogcatLogger("BundleImpl");
+ }
+
+ BundleImpl(File bundleDir) throws Exception {
+ this.isOpt = false;
+
+
+ DataInputStream dataInputStream = new DataInputStream(new FileInputStream(new File(bundleDir, "meta")));
+ this.bundleID = dataInputStream.readLong();
+ this.location = dataInputStream.readUTF();
+
+ dataInputStream.close();
+
+
+ this.bundleDir = bundleDir;
+ try {
+ this.archive = new BundleAchive(bundleDir);
+ Framework.bundles.put(this.location, this);
+
+
+ } catch (Exception e) {
+ new BundleException("Could not load bundle " + this.location, e.getCause());
+ }
+ }
+
+ BundleImpl(File bundleDir, String location, long bundleID, InputStream inputStream) throws BundleException {
+ this.isOpt = false;
+ this.bundleID = bundleID;
+ this.location = location;
+ this.bundleDir = bundleDir;
+ if (inputStream == null) {
+ throw new BundleException("Arg InputStream is null.Bundle:" + location);
+
+ } else {
+ try {
+ this.archive = new BundleAchive(bundleDir, inputStream);
+ } catch (Exception e) {
+ Framework.deleteDirectory(bundleDir);
+ throw new BundleException("Can not install bundle " + location, e);
+ }
+ }
+ this.updateMetadata();
+ Framework.bundles.put(location, this);
+
+ }
+
+
+ public boolean getIsOpt() {
+ return this.isOpt;
+ }
+
+ public Archive getArchive() {
+ return this.archive;
+ }
+
+
+ @Override
+ public long getBundleId() {
+ return this.bundleID;
+ }
+
+ @Override
+ public String getLocation() {
+ return this.location;
+ }
+
+
+ @Override
+ public int getState() {
+ return this.state;
+ }
+
+
+ @Override
+ public synchronized void update(InputStream inputStream) throws BundleException {
+ try {
+ this.archive.newRevision(this.bundleDir, inputStream);
+ } catch (Throwable e) {
+ throw new BundleException("Could not update bundle " + toString(), e);
+ }
+ }
+
+
+ public synchronized void optDexFile() throws Exception {
+ if (!isOpt) {
+ long startTime = System.currentTimeMillis();
+ getArchive().optDexFile();
+ isOpt = true;
+ log.log("执行:" + getLocation() + ",时间-----" + String.valueOf(System.currentTimeMillis() - startTime), Logger.LogLevel.ERROR);
+ }
+ }
+
+ public synchronized void purge() throws BundleException {
+ try {
+ getArchive().purge();
+ } catch (Throwable e) {
+ throw new BundleException("Could not purge bundle " + toString(), e);
+ }
+ }
+
+ void updateMetadata() {
+ File file = new File(this.bundleDir, "meta");
+ DataOutputStream dataOutputStream;
+ try {
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ dataOutputStream = new DataOutputStream(fileOutputStream);
+ dataOutputStream.writeLong(this.bundleID);
+ dataOutputStream.writeUTF(this.location);
+
+ dataOutputStream.flush();
+ fileOutputStream.getFD().sync();
+ if (dataOutputStream != null)
+ try {
+ dataOutputStream.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ } catch (Throwable e) {
+ log.log("Could not save meta data " + file.getAbsolutePath(), Logger.LogLevel.ERROR, e);
+ }
+
+ }
+
+ public String toString() {
+ return "Bundle [" + this.bundleID + "]: " + this.location;
+ }
+
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/Framework.java b/bundle/src/ctrip/android/bundle/framework/Framework.java
new file mode 100644
index 0000000..ab79fcf
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/Framework.java
@@ -0,0 +1,234 @@
+package ctrip.android.bundle.framework;
+
+import android.os.Build;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import ctrip.android.bundle.log.Logger;
+import ctrip.android.bundle.log.LoggerFactory;
+import ctrip.android.bundle.runtime.RuntimeArgs;
+
+
+/**
+ * Created by yb.wang on 14/12/31.
+ * 框架包含自身SystemBundle
+ * 1.管理各个Bundle的启动,更新,卸载
+ * 2.提供框架启动Runtime
+ */
+public final class Framework {
+ static final Logger log;
+
+ public static final String SYMBOL_SEMICOLON = ";";
+
+ private static String BASEDIR = null;
+ private static String BUNDLE_LOCATION = null;
+ static String STORAGE_LOCATION;
+ static Map bundles;
+
+ private static long nextBundleID;
+ static Properties properties;
+
+ static {
+ log = LoggerFactory.getLogcatLogger("Framework");
+ bundles = new ConcurrentHashMap<>();
+ nextBundleID = 1;
+
+ }
+
+
+ private Framework() {
+ }
+
+ static void startup(Properties pros) throws BundleException {
+ if (properties == null) {
+ properties = new Properties();
+ }
+ properties = pros;
+ startup();
+ }
+
+ static void startup() throws BundleException {
+
+ log.log("*------------------------------------*", Logger.LogLevel.DBUG);
+ log.log(" Ctrip Bundle on " + Build.MODEL + "|" + Build.CPU_ABI + "starting...", Logger.LogLevel.DBUG);
+ log.log("*------------------------------------*", Logger.LogLevel.DBUG);
+
+ long currentTimeMillis = System.currentTimeMillis();
+ initialize();
+ launch();
+ boolean isInit = getProperty("ctrip.bundle.init", false);
+ if (isInit) {
+ File file = new File(STORAGE_LOCATION);
+ if (file.exists()) {
+ log.log("Purging Storage ...", Logger.LogLevel.DBUG);
+ deleteDirectory(file);
+ }
+ file.mkdirs();
+
+ } else {
+ storeProfile();
+ }
+
+ long endTimeMillis = System.currentTimeMillis() - currentTimeMillis;
+
+ log.log("*------------------------------------*", Logger.LogLevel.DBUG);
+ log.log(" Framework " + (isInit ? "restarted" : "start") + " in " + endTimeMillis + " ms", Logger.LogLevel.DBUG);
+ log.log("*------------------------------------*", Logger.LogLevel.DBUG);
+
+
+ }
+
+ public static List getBundles() {
+ List arrayList = new ArrayList(bundles.size());
+ synchronized (bundles) {
+ arrayList.addAll(bundles.values());
+ }
+ return arrayList;
+ }
+
+ public static Bundle getBundle(String str) {
+ return bundles.get(str);
+ }
+
+ public static Bundle getBundle(long id) {
+ synchronized (bundles) {
+ for (Bundle bundle : bundles.values()) {
+ if (bundle.getBundleId() == id) {
+ return bundle;
+ }
+ }
+ return null;
+ }
+ }
+
+
+ private static void initialize() {
+ File filesDir = RuntimeArgs.androidApplication.getFilesDir();
+ BASEDIR = properties.getProperty("ctrip.android.bundle.basedir", filesDir.getAbsolutePath());
+
+ }
+
+ private static void launch() {
+
+ STORAGE_LOCATION = properties.getProperty("ctrip.android.bundle.storage", properties.getProperty("ctrip.android.bundle.framework.dir", BASEDIR + File.separatorChar + "storage")) + File.separatorChar;
+ }
+
+
+ public static boolean getProperty(String str, boolean defaultValue) {
+ if (properties == null) {
+ return defaultValue;
+ }
+ String str2 = (String) properties.get(str);
+ return str2 != null ? Boolean.valueOf(str2).booleanValue() : defaultValue;
+ }
+
+ public static int getProperty(String str, int defaultValue) {
+ if (properties == null) return defaultValue;
+ String str2 = (String) properties.get(str);
+ return str2 != null ? Integer.parseInt(str2) : defaultValue;
+ }
+
+ public static String getProperty(String str) {
+ return properties == null ? null : (String) properties.get(str);
+ }
+
+ public static String getProperty(String str, String defaultValue) {
+ return properties == null ? defaultValue : (String) properties.get(str);
+ }
+
+ private static void storeProfile() {
+ BundleImpl[] bundleImplArr = getBundles().toArray(new BundleImpl[bundles.size()]);
+ for (BundleImpl bundleImpl : bundleImplArr) {
+ bundleImpl.updateMetadata();
+ }
+ storeMetadata();
+ }
+
+ private static void storeMetadata() {
+ try {
+ DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(new File(STORAGE_LOCATION, "meta")));
+
+ dataOutputStream.writeLong(nextBundleID);
+
+ dataOutputStream.flush();
+ dataOutputStream.close();
+ } catch (Throwable e) {
+ log.log("Could not save meta data.", Logger.LogLevel.ERROR, e);
+ }
+ }
+
+ private static int restoreProfile() {
+ try {
+ log.log("Restoring profile", Logger.LogLevel.DBUG);
+ File file = new File(STORAGE_LOCATION, "meta");
+ if (file.exists()) {
+ DataInputStream dataInputStream = new DataInputStream(new FileInputStream(file));
+ int readInt = dataInputStream.readInt();
+ nextBundleID = dataInputStream.readLong();
+ dataInputStream.close();
+ File file2 = new File(STORAGE_LOCATION);
+ File[] listFiles = file2.listFiles();
+ int i = 0;
+ while (i < listFiles.length) {
+ if (listFiles[i].isDirectory() && new File(listFiles[i], "meta").exists()) {
+ try {
+ String location = new BundleImpl(listFiles[i]).location;
+ log.log("RESTORED BUNDLE " + location, Logger.LogLevel.DBUG);
+ } catch (Exception e) {
+ log.log(e.getMessage(), Logger.LogLevel.ERROR, e.getCause());
+ }
+ }
+ i++;
+ }
+ return readInt;
+ }
+// System.out.println("Profile not found, performing clean start ...");
+ log.log("Profile not found, performing clean start ...", Logger.LogLevel.DBUG);
+ return -1;
+ } catch (Exception e2) {
+ e2.printStackTrace();
+ return 0;
+ }
+ }
+
+
+ public static void deleteDirectory(File file) {
+ if (file != null) {
+ File[] listFiles = file.listFiles();
+ for (int i = 0; i < listFiles.length; i++) {
+ if (listFiles[i].isDirectory()) {
+ deleteDirectory(listFiles[i]);
+ } else {
+ listFiles[i].delete();
+ }
+ }
+ file.delete();
+ }
+ }
+
+
+
+ static BundleImpl installNewBundle(String location, InputStream inputStream) throws BundleException {
+ BundleImpl bundleImpl = (BundleImpl) getBundle(location);
+ if (bundleImpl != null) {
+ return bundleImpl;
+ }
+ long j = nextBundleID;
+ nextBundleID = 1 + j;
+ bundleImpl = new BundleImpl(new File(STORAGE_LOCATION, String.valueOf(j)), location, j, inputStream);
+ storeMetadata();
+ return bundleImpl;
+ }
+
+
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/storage/Archive.java b/bundle/src/ctrip/android/bundle/framework/storage/Archive.java
new file mode 100644
index 0000000..6442820
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/storage/Archive.java
@@ -0,0 +1,81 @@
+package ctrip.android.bundle.framework.storage;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ */
+
+
+/**
+ * Bundle 存储接口
+ */
+public interface Archive {
+
+ /**
+ * 关闭存储
+ */
+ void close();
+
+
+ /**
+ * 获取Bundle下的文件
+ *
+ * @return File类型
+ */
+ File getArchiveFile();
+
+
+ BundleArchiveRevision getCurrentRevision();
+
+
+ boolean isBundleInstalled();
+
+ /**
+ * 是否已经被Dex优化过
+ *
+ * @return boolean
+ */
+ boolean isDexOpted();
+
+ /**
+ * 优化dex文件
+ */
+ void optDexFile() throws Exception;
+
+ /**
+ * 清理文件
+ * @throws Exception
+ */
+ void purge() throws Exception;
+
+
+ /**
+ * 创建新Bundle存储
+ * @param storageFile 存储文件
+ * @param inputStream 需要新建的目标文件流
+ * @return
+ * @throws IOException
+ */
+ BundleArchiveRevision newRevision(File storageFile, InputStream inputStream) throws IOException;
+
+ /**
+ * 打开Asset目录文件
+ * @param fileName
+ * @return 文件流
+ * @throws IOException
+ */
+ InputStream openAssetInputStream(String fileName) throws IOException;
+
+ /**
+ * 打开非Asset目录文件:如 res/drawable-mdpi/icon.png
+ * @param fileName 文件相对路径:res/drawable-mdpi/icon.png
+ * @return 文件流
+ * @throws IOException
+ */
+ InputStream openNonAssetInputStream(String fileName) throws IOException;
+
+
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/storage/BundleAchive.java b/bundle/src/ctrip/android/bundle/framework/storage/BundleAchive.java
new file mode 100644
index 0000000..375a7ae
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/storage/BundleAchive.java
@@ -0,0 +1,127 @@
+package ctrip.android.bundle.framework.storage;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import ctrip.android.bundle.framework.Framework;
+import ctrip.android.bundle.util.StringUtil;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ *
+ * Bundle 目录结构:version_1,version_2
+ */
+public class BundleAchive implements Archive {
+
+ public static final String REVISION_DIREXTORY = "version";
+ private static final Long BENGIN_VERSION = 1L;
+ private File bundleDir;
+ private final BundleArchiveRevision currentRevision;
+ private final SortedMap revisionSortedMap;
+
+ public BundleAchive(File file) throws IOException {
+ this.revisionSortedMap = new TreeMap();
+ String[] lists = file.list();
+ if (lists != null) {
+ for (String str : lists) {
+ if (str.startsWith(REVISION_DIREXTORY)) {
+ long parseLong = Long.parseLong(StringUtil.subStringAfter(str, "_"));
+ if (parseLong > 0) {
+ this.revisionSortedMap.put(parseLong, null);
+ }
+ }
+ }
+
+ }
+ if (revisionSortedMap.isEmpty()) {
+ throw new IOException("No Valid revisions in bundle archive directory");
+ }
+ this.bundleDir = file;
+ long longValue = this.revisionSortedMap.lastKey();
+ BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(longValue, new File(file, REVISION_DIREXTORY + "_" + String.valueOf(longValue)));
+ this.revisionSortedMap.put(longValue, bundleArchiveRevision);
+ this.currentRevision = bundleArchiveRevision;
+ }
+
+ public BundleAchive(File file, InputStream inputStream) throws IOException {
+ this.revisionSortedMap = new TreeMap();
+ this.bundleDir = file;
+ BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(BENGIN_VERSION, new File(file, REVISION_DIREXTORY + "_" + String.valueOf(BENGIN_VERSION)), inputStream);
+ this.revisionSortedMap.put(BENGIN_VERSION, bundleArchiveRevision);
+ this.currentRevision = bundleArchiveRevision;
+ }
+
+
+ @Override
+ public BundleArchiveRevision newRevision(File storageFile, InputStream inputStream) throws IOException {
+ long version = this.revisionSortedMap.lastKey() + 1;
+ BundleArchiveRevision bundleArchiveRevision = new BundleArchiveRevision(version, new File(storageFile, REVISION_DIREXTORY + "_" + String.valueOf(version)), inputStream);
+ this.revisionSortedMap.put(version, bundleArchiveRevision);
+ return bundleArchiveRevision;
+
+ }
+
+
+ public BundleArchiveRevision getCurrentRevision() {
+ return this.currentRevision;
+ }
+
+ public File getBundleDir() {
+ return this.bundleDir;
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public File getArchiveFile() {
+ return this.currentRevision.getRevisionFile();
+ }
+
+
+ @Override
+ public boolean isBundleInstalled() {
+ return this.currentRevision.isBundleInstalled();
+ }
+
+ @Override
+ public boolean isDexOpted() {
+ return this.currentRevision.isDexOpted();
+ }
+
+ @Override
+ public void optDexFile() throws Exception {
+ this.currentRevision.optDexFile();
+ }
+
+ @Override
+ public void purge() throws Exception {
+
+ Framework.deleteDirectory(this.currentRevision.getRevisionDir());
+ long l = this.revisionSortedMap.lastKey();
+ this.revisionSortedMap.clear();
+ if (l < 1) {
+ this.revisionSortedMap.put(0L, this.currentRevision);
+ } else {
+ this.revisionSortedMap.put(l - 1, this.currentRevision);
+ }
+ }
+
+
+ @Override
+ public InputStream openAssetInputStream(String fileName) throws IOException {
+ return this.currentRevision.openAssetInputStream(fileName);
+ }
+
+ @Override
+ public InputStream openNonAssetInputStream(String fileName) throws IOException {
+ return this.currentRevision.openNonAssetInputStream(fileName);
+ }
+
+}
diff --git a/bundle/src/ctrip/android/bundle/framework/storage/BundleArchiveRevision.java b/bundle/src/ctrip/android/bundle/framework/storage/BundleArchiveRevision.java
new file mode 100644
index 0000000..e901ae8
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/framework/storage/BundleArchiveRevision.java
@@ -0,0 +1,219 @@
+package ctrip.android.bundle.framework.storage;
+
+import android.content.res.AssetManager;
+import android.os.Build;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+import ctrip.android.bundle.hack.SysHacks;
+import ctrip.android.bundle.loader.BundlePathLoader;
+import ctrip.android.bundle.log.Logger;
+import ctrip.android.bundle.log.LoggerFactory;
+import ctrip.android.bundle.runtime.RuntimeArgs;
+import ctrip.android.bundle.util.APKUtil;
+import ctrip.android.bundle.util.StringUtil;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ *
+ * Bundle 存储文件:bundle.zip,bundle.dex
+ * 采用DexFile 加载 dex文件,并opt释放优化后的dex
+ * findClass会被BundleClassLoader调用
+ */
+public class BundleArchiveRevision {
+ static final Logger log;
+ static final String BUNDLE_FILE_NAME = "bundle.zip";
+ static final String BUNDEL_DEX_FILE = "bundle.dex";
+ static final String FILE_PROTOCOL = "file:";
+ static final String REFERENCE_PROTOCOL = "reference:";
+ private final long revisionNum;
+ private File revisionDir;
+ private File bundleFile;
+ private String revisionLocation;
+
+ static {
+ log = LoggerFactory.getLogcatLogger("BundleArchiveRevision");
+ }
+
+
+ BundleArchiveRevision(long revisionNumber, File file, InputStream inputStream) throws IOException {
+ this.revisionNum = revisionNumber;
+ this.revisionDir = file;
+ if (!this.revisionDir.exists()) {
+ this.revisionDir.mkdirs();
+ }
+ this.revisionLocation = FILE_PROTOCOL;
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ APKUtil.copyInputStreamToFile(inputStream, this.bundleFile);
+ updateMetaData();
+
+ }
+
+ BundleArchiveRevision(long revisionNumber, File file, File file2) throws IOException {
+ this.revisionNum = revisionNumber;
+ this.revisionDir = file;
+ if (!this.revisionDir.exists()) {
+ this.revisionDir.mkdirs();
+ }
+ if (file2.canWrite()) {
+ if (isSameDir(file, file2)) {
+ this.revisionLocation = FILE_PROTOCOL;
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ file2.renameTo(this.bundleFile);
+ } else {
+ this.revisionLocation = FILE_PROTOCOL;
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ APKUtil.copyInputStreamToFile(new FileInputStream(file2), this.bundleFile);
+ }
+ } else if (Build.HARDWARE.toLowerCase().contains("mt6592") && file2.getName().endsWith(".apk")) {
+ this.revisionLocation = FILE_PROTOCOL;
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ Runtime.getRuntime().exec(String.format("ln -s %s %s", new Object[]{file2.getAbsolutePath(), this.bundleFile.getAbsolutePath()}));
+ } else if (SysHacks.LexFile == null || SysHacks.LexFile.getmClass() == null) {
+ this.revisionLocation = REFERENCE_PROTOCOL + file2.getAbsolutePath();
+ this.bundleFile = file2;
+ } else {
+ this.revisionLocation = FILE_PROTOCOL;
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ APKUtil.copyInputStreamToFile(new FileInputStream(file2), this.bundleFile);
+ }
+ updateMetaData();
+ }
+
+ BundleArchiveRevision(long revisionNumber, File file) throws IOException {
+ File fileMeta = new File(file, "meta");
+ if (fileMeta.exists()) {
+ DataInputStream dataInputStream = new DataInputStream(new FileInputStream(fileMeta));
+ this.revisionLocation = dataInputStream.readUTF();
+ dataInputStream.close();
+ this.revisionNum = revisionNumber;
+ this.revisionDir = file;
+ if (!this.revisionDir.exists()) {
+ this.revisionDir.mkdirs();
+ }
+ if (this.revisionLocation.startsWith(REFERENCE_PROTOCOL)) {
+ this.bundleFile = new File(StringUtil.subStringAfter(this.revisionLocation, REFERENCE_PROTOCOL));
+ return;
+ } else {
+ this.bundleFile = new File(file, BUNDLE_FILE_NAME);
+ return;
+ }
+ }
+ throw new IOException("Can not find meta file in " + file.getAbsolutePath());
+ }
+
+ void updateMetaData() throws IOException {
+
+ File file = new File(this.revisionDir, "meta");
+ DataOutputStream dataOutputStream = null;
+ try {
+ if (!file.getParentFile().exists()) {
+ file.getParentFile().mkdirs();
+ }
+ dataOutputStream = new DataOutputStream(new FileOutputStream(file));
+ dataOutputStream.writeUTF(this.revisionLocation);
+ dataOutputStream.flush();
+
+ } catch (IOException ex) {
+ throw new IOException("Can not save meta data " + file.getAbsolutePath());
+ } finally {
+ if (dataOutputStream != null) dataOutputStream.close();
+ }
+ }
+
+ private boolean isSameDir(File file, File file2) {
+ return StringUtil.equals(StringUtil.subStringBetween(file.getAbsolutePath(), File.separator, File.separator),
+ StringUtil.subStringBetween(file2.getAbsolutePath(), File.separator, File.separator));
+ }
+
+ public long getRevisionNum() {
+ return this.revisionNum;
+ }
+
+ public File getRevisionDir() {
+ return this.revisionDir;
+ }
+
+ public File getRevisionFile() {
+ return this.bundleFile;
+ }
+
+ public boolean isDexOpted() {
+ return new File(this.revisionDir, BUNDEL_DEX_FILE).exists();
+ }
+
+ public boolean isBundleInstalled(){
+ if(bundleFile.exists()){
+ return verifyZipFile(bundleFile);
+ }
+ return false;
+ }
+ private boolean verifyZipFile(File file) {
+ try {
+ ZipFile zipFile = new ZipFile(file);
+ try {
+ zipFile.close();
+ return true;
+ } catch (IOException e) {
+ log.log("Failed to close zip file: " + file.getAbsolutePath(), Logger.LogLevel.ERROR,e);
+ }
+ } catch (ZipException ex) {
+ log.log("File " + file.getAbsolutePath() + " is not a valid zip file.", Logger.LogLevel.ERROR,ex);
+ } catch (IOException ex) {
+ log.log("Got an IOException trying to open zip file: " + file.getAbsolutePath(), Logger.LogLevel.ERROR,ex);
+ }
+ return false;
+ }
+
+ public void optDexFile() throws Exception{
+ List files = new ArrayList();
+ files.add(this.bundleFile);
+ BundlePathLoader.installBundleDexs(RuntimeArgs.androidApplication.getClassLoader(), revisionDir, files,false);
+ }
+
+ public InputStream openAssetInputStream(String fileName) throws IOException {
+ try {
+ AssetManager assetManager = AssetManager.class.newInstance();
+ if (((Integer) SysHacks.AssetManager_addAssetPath.invoke(assetManager, this.bundleFile.getAbsoluteFile())).intValue() != 0) {
+ return assetManager.open(fileName);
+ }
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public InputStream openNonAssetInputStream(String fileName) throws IOException {
+ try {
+ AssetManager assetManager = AssetManager.class.newInstance();
+ int intValue = ((Integer) SysHacks.AssetManager_addAssetPath.invoke(assetManager, this.bundleFile.getAbsoluteFile())).intValue();
+ if (intValue != 0) {
+ return assetManager.openNonAssetFd(intValue, fileName).createInputStream();
+ }
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+
+}
diff --git a/bundle/src/ctrip/android/bundle/hack/AndroidHack.java b/bundle/src/ctrip/android/bundle/hack/AndroidHack.java
new file mode 100644
index 0000000..51af9e4
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/hack/AndroidHack.java
@@ -0,0 +1,139 @@
+package ctrip.android.bundle.hack;
+
+import android.app.Activity;
+import android.app.Application;
+import android.app.Instrumentation;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+
+/**
+ * Created by yb.wang on 15/1/5.
+ * Android中的ClassLoader Hack
+ */
+public class AndroidHack {
+ private static Object _mLoadedApk;
+ private static Object _sActivityThread;
+
+ static class ActivityThreadGetter implements Runnable {
+ ActivityThreadGetter() {
+ }
+
+ public void run() {
+ try {
+ _sActivityThread = SysHacks.ActivityThread_currentActivityThread.invoke(SysHacks.ActivityThread.getmClass(), new Object[0]);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ synchronized (SysHacks.ActivityThread_currentActivityThread) {
+ SysHacks.ActivityThread_currentActivityThread.notify();
+ }
+ }
+ }
+
+ static {
+ _sActivityThread = null;
+ _mLoadedApk = null;
+ }
+
+ public static Object getActivityThread() throws Exception {
+ if (_sActivityThread == null) {
+ if (Thread.currentThread().getId() == Looper.getMainLooper().getThread().getId()) {
+ _sActivityThread = SysHacks.ActivityThread_currentActivityThread.invoke(null, new Object[0]);
+ } else {
+ Handler handler = new Handler(Looper.getMainLooper());
+ synchronized (SysHacks.ActivityThread_currentActivityThread) {
+ handler.post(new ActivityThreadGetter());
+ SysHacks.ActivityThread_currentActivityThread.wait();
+ }
+ }
+ }
+ return _sActivityThread;
+ }
+
+ public static Object getLoadedApk(Object obj, String str) throws Exception {
+ if (_mLoadedApk == null) {
+ WeakReference weakReference = (WeakReference) ((Map) SysHacks.ActivityThread_mPackages.get(obj)).get(str);
+ if (weakReference != null) {
+ _mLoadedApk = weakReference.get();
+ }
+ }
+ return _mLoadedApk;
+ }
+
+ public static void injectClassLoader(String str, ClassLoader classLoader) throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+ activityThread = getLoadedApk(activityThread, str);
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.mLoadedApk");
+ }
+ SysHacks.LoadedApk_mClassLoader.set(activityThread, classLoader);
+ }
+
+ public static void injectResources(Application application, Resources resources) throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+ Object loadedApk = getLoadedApk(activityThread, application.getPackageName());
+ if (loadedApk == null) {
+ throw new Exception("Failed to get ActivityThread.mLoadedApk");
+ }
+ SysHacks.LoadedApk_mResources.set(loadedApk, resources);
+ SysHacks.ContextImpl_mResources.set(application.getBaseContext(), resources);
+ SysHacks.ContextImpl_mTheme.set(application.getBaseContext(), null);
+ }
+
+ public static void injectActivityResources(Activity activity, Resources resources) throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+ Object loadedApk = getLoadedApk(activityThread, activity.getPackageName());
+ if (loadedApk == null) {
+ throw new Exception("Failed to get ActivityThread.mLoadedApk");
+ }
+ SysHacks.LoadedApk_mResources.set(loadedApk, resources);
+ SysHacks.ContextImpl_mResources.set(activity.getBaseContext(), resources);
+ SysHacks.ContextImpl_mTheme.set(activity.getBaseContext(), null);
+ }
+
+ public static ClassLoader currentClassLoader(String str) throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+ activityThread = getLoadedApk(activityThread, str);
+ if (activityThread != null) {
+ return SysHacks.LoadedApk_mClassLoader.get(activityThread);
+ }
+ throw new Exception("Failed to get ActivityThread.mLoadedApk");
+ }
+
+ public static Instrumentation getInstrumentation() throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread != null) {
+ return SysHacks.ActivityThread_mInstrumentation.get(activityThread);
+ }
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+
+ public static void injectInstrumentationHook(Instrumentation instrumentation) throws Exception {
+ Object activityThread = getActivityThread();
+ if (activityThread == null) {
+ throw new Exception("Failed to get ActivityThread.sCurrentActivityThread");
+ }
+ SysHacks.ActivityThread_mInstrumentation.set(activityThread, instrumentation);
+ }
+
+ public static void injectContextHook(ContextWrapper contextWrapper, ContextWrapper contextWrapper2) {
+ SysHacks.ContextWrapper_mBase.set(contextWrapper, contextWrapper2);
+ }
+}
diff --git a/bundle/src/ctrip/android/bundle/hack/AssertionArrayException.java b/bundle/src/ctrip/android/bundle/hack/AssertionArrayException.java
new file mode 100644
index 0000000..60d8f54
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/hack/AssertionArrayException.java
@@ -0,0 +1,75 @@
+package ctrip.android.bundle.hack;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+import ctrip.android.bundle.framework.Framework;
+
+/**
+ * Created by yb.wang on 15/1/5.
+ */
+public class AssertionArrayException extends Exception {
+ private static final long serialVersionUID = 1;
+ private List mAssertionErr;
+
+ public AssertionArrayException(String str) {
+ super(str);
+ this.mAssertionErr = new ArrayList();
+ }
+
+ public void addException(Hack.HackDeclaration.HackAssertionException hackAssertionException) {
+ this.mAssertionErr.add(hackAssertionException);
+ }
+
+ public void addException(List list) {
+ this.mAssertionErr.addAll(list);
+ }
+
+ public List getExceptions() {
+ return this.mAssertionErr;
+ }
+
+ public static AssertionArrayException mergeException(AssertionArrayException assertionArrayException, AssertionArrayException assertionArrayException2) {
+ if (assertionArrayException == null) {
+ return assertionArrayException2;
+ }
+ if (assertionArrayException2 == null) {
+ return assertionArrayException;
+ }
+ AssertionArrayException assertionArrayException3 = new AssertionArrayException(assertionArrayException.getMessage() + Framework.SYMBOL_SEMICOLON + assertionArrayException2.getMessage());
+ assertionArrayException3.addException(assertionArrayException.getExceptions());
+ assertionArrayException3.addException(assertionArrayException2.getExceptions());
+ return assertionArrayException3;
+ }
+
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (Hack.HackDeclaration.HackAssertionException hackAssertionException : this.mAssertionErr) {
+ stringBuilder.append(hackAssertionException.toString()).append(Framework.SYMBOL_SEMICOLON);
+ try {
+ if (hackAssertionException.getCause() instanceof NoSuchFieldException) {
+ Field[] declaredFields = hackAssertionException.getHackedClass().getDeclaredFields();
+ stringBuilder.append(hackAssertionException.getHackedClass().getName()).append(".").append(hackAssertionException.getHackedFieldName()).append(Framework.SYMBOL_SEMICOLON);
+ for (Field field : declaredFields) {
+ stringBuilder.append(field.getName()).append(File.separator);
+ }
+ } else if (hackAssertionException.getCause() instanceof NoSuchMethodException) {
+ Method[] declaredMethods = hackAssertionException.getHackedClass().getDeclaredMethods();
+ stringBuilder.append(hackAssertionException.getHackedClass().getName()).append("->").append(hackAssertionException.getHackedMethodName()).append(Framework.SYMBOL_SEMICOLON);
+ for (int i = 0; i < declaredMethods.length; i++) {
+ if (hackAssertionException.getHackedMethodName().equals(declaredMethods[i].getName())) {
+ stringBuilder.append(declaredMethods[i].toGenericString()).append(File.separator);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ stringBuilder.append("@@@@");
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/bundle/src/ctrip/android/bundle/hack/Hack.java b/bundle/src/ctrip/android/bundle/hack/Hack.java
new file mode 100644
index 0000000..86f5771
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/hack/Hack.java
@@ -0,0 +1,269 @@
+package ctrip.android.bundle.hack;
+
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ * Hack--反射机制反射后包装的形式:类,方法,字段
+ */
+public class Hack {
+ private static AssertionFailureHandler sFailureHandler;
+
+ public static interface AssertionFailureHandler {
+ boolean onAssertionFailure(HackDeclaration.HackAssertionException hackAssertionException);
+ }
+
+ public static abstract class HackDeclaration {
+
+ public static class HackAssertionException extends Throwable {
+ private static final long serialVersionUID = 1;
+ private Class> mHackedClass;
+ private String mHackedFieldName;
+ private String mHackedMethodName;
+
+ public HackAssertionException(String str) {
+ super(str);
+ }
+
+ public HackAssertionException(Exception exception) {
+ super(exception);
+ }
+
+ public String toString() {
+ return getCause() != null ? getClass().getName() + ": " + getCause() : super.toString();
+ }
+
+ public Class> getHackedClass() {
+ return this.mHackedClass;
+ }
+
+ public void setHackedClass(Class> cls) {
+ this.mHackedClass = cls;
+ }
+
+ public String getHackedMethodName() {
+ return this.mHackedMethodName;
+ }
+
+ public void setHackedMethodName(String str) {
+ this.mHackedMethodName = str;
+ }
+
+ public String getHackedFieldName() {
+ return this.mHackedFieldName;
+ }
+
+ public void setHackedFieldName(String str) {
+ this.mHackedFieldName = str;
+ }
+ }
+ }
+
+ public static class HackedClass {
+ protected Class mClass;
+
+ public HackedField staticField(String str) throws HackDeclaration.HackAssertionException {
+ return new HackedField(this.mClass, str, 8);
+ }
+
+ public HackedField field(String str) throws HackDeclaration.HackAssertionException {
+ return new HackedField(this.mClass, str, 0);
+ }
+
+ public HackedMethod staticMethod(String str, Class>... clsArr) throws HackDeclaration.HackAssertionException {
+ return new HackedMethod(this.mClass, str, clsArr, 8);
+ }
+
+ public HackedMethod method(String str, Class>... clsArr) throws HackDeclaration.HackAssertionException {
+ return new HackedMethod(this.mClass, str, clsArr, 0);
+ }
+
+ public HackedConstructor constructor(Class>... clsArr) throws HackDeclaration.HackAssertionException {
+ return new HackedConstructor(this.mClass, clsArr);
+ }
+
+ public HackedClass(Class cls) {
+ this.mClass = cls;
+ }
+
+ public Class getmClass() {
+ return this.mClass;
+ }
+ }
+
+ public static class HackedConstructor {
+ protected Constructor> mConstructor;
+
+ HackedConstructor(Class> cls, Class>[] clsArr) throws HackDeclaration.HackAssertionException {
+ if (cls != null) {
+ try {
+ this.mConstructor = cls.getDeclaredConstructor(clsArr);
+ } catch (Exception e) {
+ HackDeclaration.HackAssertionException hackAssertionException = new HackDeclaration.HackAssertionException(e);
+ hackAssertionException.setHackedClass(cls);
+ Hack.fail(hackAssertionException);
+ }
+ }
+ }
+
+ public Object getInstance(Object... objArr) throws IllegalArgumentException {
+ Object obj = null;
+ this.mConstructor.setAccessible(true);
+ try {
+ obj = this.mConstructor.newInstance(objArr);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return obj;
+ }
+ }
+
+ public static class HackedField {
+ private final Field mField;
+
+ public HackedField ofGenericType(Class> cls) throws HackDeclaration.HackAssertionException {
+ if (!(this.mField == null || cls.isAssignableFrom(this.mField.getType()))) {
+ Hack.fail(new HackDeclaration.HackAssertionException(new ClassCastException(this.mField + " is not of type " + cls)));
+ }
+ return this;
+ }
+
+ public HackedField ofType(Class> cls) throws HackDeclaration.HackAssertionException {
+ if (!(this.mField == null || cls.isAssignableFrom(this.mField.getType()))) {
+ Hack.fail(new HackDeclaration.HackAssertionException(new ClassCastException(this.mField + " is not of type " + cls)));
+ }
+ return this;
+ }
+
+ public HackedField ofType(String str) throws HackDeclaration.HackAssertionException {
+ HackedField ofType = null;
+ try {
+ ofType = ofType((Class) Class.forName(str));
+ } catch (Exception e) {
+ Hack.fail(new HackDeclaration.HackAssertionException(e));
+ }
+ return ofType;
+ }
+
+ public T get(C c) {
+ try {
+ return (T) this.mField.get(c);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void set(C c, Object obj) {
+ try {
+ this.mField.set(c, obj);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void hijack(C c, Interception.InterceptionHandler> interceptionHandler) {
+ T obj = get(c);
+ if (obj == null) {
+ throw new IllegalStateException("Cannot hijack null");
+ }
+ set(c, Interception.proxy(obj, (Interception.InterceptionHandler) interceptionHandler, obj.getClass().getInterfaces()));
+ }
+
+ HackedField(Class cls, String str, int i) throws HackDeclaration.HackAssertionException {
+ Field field = null;
+ if (cls == null) {
+ this.mField = null;
+ return;
+ }
+ try {
+ field = cls.getDeclaredField(str);
+ if (i > 0 && (field.getModifiers() & i) != i) {
+ Hack.fail(new HackDeclaration.HackAssertionException(field + " does not match modifiers: " + i));
+ }
+ field.setAccessible(true);
+ } catch (Exception e) {
+ HackDeclaration.HackAssertionException hackAssertionException = new HackDeclaration.HackAssertionException(e);
+ hackAssertionException.setHackedClass(cls);
+ hackAssertionException.setHackedFieldName(str);
+ Hack.fail(hackAssertionException);
+ } finally {
+ this.mField = field;
+ }
+ }
+
+ public Field getField() {
+ return this.mField;
+ }
+ }
+
+ public static class HackedMethod {
+ protected final Method mMethod;
+
+ public Object invoke(Object obj, Object... objArr) throws IllegalArgumentException, InvocationTargetException {
+ Object obj2 = null;
+ try {
+ obj2 = this.mMethod.invoke(obj, objArr);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return obj2;
+ }
+
+ HackedMethod(Class> cls, String str, Class>[] clsArr, int i) throws HackDeclaration.HackAssertionException {
+ Method method = null;
+ if (cls == null) {
+ this.mMethod = null;
+ return;
+ }
+ try {
+ method = cls.getDeclaredMethod(str, clsArr);
+ if (i > 0 && (method.getModifiers() & i) != i) {
+ Hack.fail(new HackDeclaration.HackAssertionException(method + " does not match modifiers: " + i));
+ }
+ method.setAccessible(true);
+ } catch (Exception e) {
+ HackDeclaration.HackAssertionException hackAssertionException = new HackDeclaration.HackAssertionException(e);
+ hackAssertionException.setHackedClass(cls);
+ hackAssertionException.setHackedMethodName(str);
+ Hack.fail(hackAssertionException);
+ } finally {
+ this.mMethod = method;
+ }
+ }
+
+ public Method getMethod() {
+ return this.mMethod;
+ }
+ }
+
+ public static HackedClass into(Class cls) {
+ return new HackedClass(cls);
+ }
+
+ public static HackedClass into(String str) throws HackDeclaration.HackAssertionException {
+ try {
+ return new HackedClass(Class.forName(str));
+ } catch (Exception e) {
+ fail(new HackDeclaration.HackAssertionException(e));
+ return new HackedClass(null);
+ }
+ }
+
+ private static void fail(HackDeclaration.HackAssertionException hackAssertionException) throws HackDeclaration.HackAssertionException {
+ if (sFailureHandler == null || !sFailureHandler.onAssertionFailure(hackAssertionException)) {
+ throw hackAssertionException;
+ }
+ }
+
+ public static void setAssertionFailureHandler(AssertionFailureHandler assertionFailureHandler) {
+ sFailureHandler = assertionFailureHandler;
+ }
+
+ private Hack() {
+ }
+}
diff --git a/bundle/src/ctrip/android/bundle/hack/Interception.java b/bundle/src/ctrip/android/bundle/hack/Interception.java
new file mode 100644
index 0000000..efbe15b
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/hack/Interception.java
@@ -0,0 +1,57 @@
+package ctrip.android.bundle.hack;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Created by yb.wang on 15/1/5.
+ */
+public class Interception {
+
+ private static interface Intercepted {
+ }
+
+ public static abstract class InterceptionHandler implements InvocationHandler {
+ private T mDelegatee;
+
+ public Object invoke(Object obj, Method method, Object[] objArr) throws Throwable {
+ Object obj2 = null;
+ try {
+ obj2 = method.invoke(delegatee(), objArr);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e2) {
+ e2.printStackTrace();
+ } catch (InvocationTargetException e3) {
+ throw e3.getTargetException();
+ }
+ return obj2;
+ }
+
+ protected T delegatee() {
+ return this.mDelegatee;
+ }
+
+ void setDelegatee(T t) {
+ this.mDelegatee = t;
+ }
+ }
+
+ public static T proxy(Object obj, Class cls, InterceptionHandler interceptionHandler) throws IllegalArgumentException {
+ if (obj instanceof Intercepted) {
+ return (T) obj;
+ }
+ interceptionHandler.setDelegatee((T) obj);
+ return (T) Proxy.newProxyInstance(Interception.class.getClassLoader(), new Class[]{cls, Intercepted.class}, interceptionHandler);
+ }
+
+ public static T proxy(Object obj, InterceptionHandler interceptionHandler, Class>... clsArr) throws IllegalArgumentException {
+ interceptionHandler.setDelegatee((T) obj);
+ return (T) Proxy.newProxyInstance(Interception.class.getClassLoader(), clsArr, interceptionHandler);
+ }
+
+ private Interception() {
+ }
+}
diff --git a/bundle/src/ctrip/android/bundle/hack/SysHacks.java b/bundle/src/ctrip/android/bundle/hack/SysHacks.java
new file mode 100644
index 0000000..49f42e0
--- /dev/null
+++ b/bundle/src/ctrip/android/bundle/hack/SysHacks.java
@@ -0,0 +1,211 @@
+package ctrip.android.bundle.hack;
+
+import android.app.Application;
+import android.app.Instrumentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Build;
+import android.view.ContextThemeWrapper;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import ctrip.android.bundle.hack.Hack.HackedClass;
+import ctrip.android.bundle.hack.Hack.HackedField;
+import ctrip.android.bundle.hack.Hack.HackedMethod;
+import ctrip.android.bundle.log.Logger;
+import ctrip.android.bundle.log.LoggerFactory;
+import dalvik.system.DexClassLoader;
+
+/**
+ * Created by yb.wang on 14/12/31.
+ * Hack 系统的功能:包括类加载机制,资源加载,Context等
+ */
+public class SysHacks extends Hack.HackDeclaration implements Hack.AssertionFailureHandler {
+ public static HackedClass