diff --git a/Android/DevSample/build.gradle b/Android/DevSample/build.gradle index 2e6276ef..3cd69ca6 100644 --- a/Android/DevSample/build.gradle +++ b/Android/DevSample/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -24,10 +24,11 @@ task clean(type: Delete) { apply plugin: 'net.wequick.small' small { - buildToAssets = true + buildToAssets = false android { compileSdkVersion = 24 buildToolsVersion = "23.0.3" supportVersion = "23.4.0" } + strictSplitResources=false } diff --git a/Android/DevSample/gradle/wrapper/gradle-wrapper.properties b/Android/DevSample/gradle/wrapper/gradle-wrapper.properties index 4409037e..380f7531 100644 --- a/Android/DevSample/gradle/wrapper/gradle-wrapper.properties +++ b/Android/DevSample/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/Android/DevSample/settings.gradle b/Android/DevSample/settings.gradle index b3f4e20b..5425dfc5 100644 --- a/Android/DevSample/settings.gradle +++ b/Android/DevSample/settings.gradle @@ -4,7 +4,7 @@ def externalModules = [ ':app', ':app+stub', ':app.main', ':app.home', ':app.detail', ':app.mine', ':app.ok-if-stub', ':web.about', - ':lib.utils', ':lib.style', ':lib.afterutils', ':lib.analytics', + ':lib.utils', ':lib.style', ':lib.analytics', ':jni_plugin' ] as String[] diff --git a/Android/DevSample/small/base.gradle b/Android/DevSample/small/base.gradle index 82a76238..6af102f3 100644 --- a/Android/DevSample/small/base.gradle +++ b/Android/DevSample/small/base.gradle @@ -23,7 +23,7 @@ configurations { } dependencies { -// testCompile 'junit:junit:4.12' + testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.2.1' provided 'com.android.support:support-v4:23.2.1' } diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java b/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java index 73b6ea9b..976d9d44 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/ApkBundleLauncher.java @@ -23,18 +23,18 @@ import android.app.Instrumentation; import android.content.ActivityNotFoundException; import android.content.ComponentName; +import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.ProviderInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Handler; import android.os.IBinder; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; import android.os.Message; import android.support.v4.app.TaskStackBuilder; import android.util.Log; @@ -641,10 +641,41 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl } } + @Override + public void postLazySetUp() { + super.postLazySetUp(); + long lStartTime = System.currentTimeMillis(); + if (sLoadedApks == null) { + Log.e(TAG, "Could not find LazyLoad APK bundles!"); + return; + } + + Collection apks = sLoadedApks.values(); + + // Merge lazy load bundle's resources and replace the host one + final Application app = Small.getContext(); + String[] paths = new String[apks.size() + 1]; + int i = 0; + for (LoadedApk apk : apks) { + if (apk.nonResources) continue; // ignores the empty entry to fix #62 + paths[i++] = apk.path; // add plugin asset path + } + if (i != paths.length) { + paths = Arrays.copyOf(paths, i); + } + ReflectAccelerator.addAssetPaths(app.getAssets(), paths); + + mergeWithOutAssets(app, apks); + long lEndTime = System.currentTimeMillis(); + long difference = lEndTime - lStartTime; + System.out.println("postLazySetUp's Elapsed milliseconds: " + + difference); + } + @Override public void postSetUp() { super.postSetUp(); - + long lStartTime = System.currentTimeMillis(); if (sLoadedApks == null) { Log.e(TAG, "Could not find any APK bundles!"); return; @@ -666,9 +697,20 @@ public void postSetUp() { } ReflectAccelerator.mergeResources(app, sActivityThread, paths); + mergeWithOutAssets(app, apks); + long lEndTime = System.currentTimeMillis(); + long difference = lEndTime - lStartTime; + System.out.println("postSetUp's Elapsed milliseconds: " + + difference); + } + + /** + * just put code together + */ + private void mergeWithOutAssets(final Application app, Collection apks) { // Merge all the dex into host's class loader ClassLoader cl = app.getClassLoader(); - i = 0; + int i = 0; int N = apks.size(); String[] dexPaths = new String[N]; DexFile[] dexFiles = new DexFile[N]; @@ -733,6 +775,7 @@ public void run() { } // Free temporary variables + mLazyInitProviders=null; sLoadedApks = null; sProviders = null; sActivityThread = null; @@ -758,7 +801,7 @@ public File getExtractFile(Bundle bundle, String entryName) { } @Override - public void loadBundle(Bundle bundle) { + public void loadBundle(final Bundle bundle) { String packageName = bundle.getPackageName(); BundleParser parser = bundle.getParser(); @@ -786,7 +829,12 @@ public void loadBundle(Bundle bundle) { @Override public void run() { try { + long lStartTime = System.currentTimeMillis(); fApk.dexFile = DexFile.loadDex(fApk.path, fApk.optDexFile.getPath(), 0); + long lEndTime = System.currentTimeMillis(); + long difference = lEndTime - lStartTime; + System.out.println(bundle.getPackageName() + "'s Elapsed milliseconds: " + + difference); } catch (IOException e) { throw new RuntimeException(e); } @@ -807,7 +855,8 @@ public void run() { } // Record activities for intent redirection - if (sLoadedActivities == null) sLoadedActivities = new ConcurrentHashMap(); + if (sLoadedActivities == null) + sLoadedActivities = new ConcurrentHashMap(); for (ActivityInfo ai : pluginInfo.activities) { sLoadedActivities.put(ai.name, ai); } diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/Bundle.java b/Android/DevSample/small/src/main/java/net/wequick/small/Bundle.java index f5118f24..ff37d48f 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/Bundle.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/Bundle.java @@ -33,7 +33,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; @@ -88,8 +87,11 @@ private static final class Manifest { // Thread & Handler private static final int MSG_COMPLETE = 1; + private static final int MSG_LAZY_LOAD_COMPLETE = 2; private static LoadBundleHandler sHandler; + private static LazyLoadBundleHandler sLazyHandler; private static LoadBundleThread sThread; + private static LazyLoadBundleThread sLazyThread; private String mPackageName; private String uriString; @@ -102,6 +104,7 @@ private static final class Manifest { private HashMap rules; private int versionCode; private String versionName; + private boolean isLazyLoad=false; private BundleLauncher mApplicableLauncher = null; @@ -113,6 +116,7 @@ private static final class Manifest { private boolean launchable = true; private boolean enabled = true; private boolean patching = false; + private boolean loaded=false; private String entrance = null; // Main activity for `apk bundle', index page for `web bundle' @@ -220,6 +224,22 @@ public static boolean is64bit() { return sIs64bit; } + public static void loadLazyBundles(Small.OnLazyCompleteListener listener){ + boolean synchronous = (listener == null); + if (synchronous) { + lazyLoadBundles(); + return; + } + + // Asynchronous + if (sLazyThread == null) { + sLazyThread = new LazyLoadBundleThread(); + sLazyHandler = new LazyLoadBundleHandler(listener); + sLazyThread.start(); + } + } + + /** * Load bundles from manifest */ @@ -366,7 +386,7 @@ protected static Bundle getLaunchableBundle(Uri uri) { if (sPreloadBundles != null) { for (Bundle bundle : sPreloadBundles) { if (bundle.matchesRule(uri)) { - if (bundle.mApplicableLauncher == null) { + if (bundle.mApplicableLauncher == null&&!bundle.isLazyLoad()) { break; } @@ -538,6 +558,9 @@ private void initWithMap(JSONObject map) throws JSONException { if (map.has("type")) { this.type = map.getString("type"); } + if (map.has("lazyload")) { + this.isLazyLoad = map.getBoolean("lazyload"); + } this.rules = new HashMap(); // Default rules to visit entrance page of bundle @@ -621,6 +644,14 @@ protected void setExtractPath(File path) { this.mExtractPath = path; } + public boolean isLoaded() { + return loaded; + } + + public void setLoaded(boolean loaded) { + this.loaded = loaded; + } + protected String getType() { return type; } @@ -678,6 +709,14 @@ public String getVersionName() { return versionName; } + public boolean isLazyLoad() { + return isLazyLoad; + } + + public void setLazyLoad(boolean lazyLoad) { + isLazyLoad = lazyLoad; + } + protected boolean isLaunchable() { return launchable && enabled; } @@ -748,8 +787,33 @@ public void run() { } } + private static class LazyLoadBundleThread extends Thread { + @Override + public void run() { + lazyLoadBundles(); + sLazyHandler.obtainMessage(MSG_LAZY_LOAD_COMPLETE).sendToTarget(); + } + } + private static final int LOADING_TIMEOUT_MINUTES = 5; + private static List unLoadBundle; + + private static void lazyLoadBundles() { + if (unLoadBundle == null) { + unLoadBundle = new ArrayList<>(); + } + for (Bundle bundle : sPreloadBundles) { + if (bundle.isLazyLoad()) { + unLoadBundle.add(bundle); + bundle.setLazyLoad(false); + bundle.prepareForLaunch(); + } + } + + processBundlesLoad(unLoadBundle,true); + } + private static void loadBundles(List bundles) { sPreloadBundles = bundles; @@ -758,6 +822,13 @@ private static void loadBundles(List bundles) { bundle.prepareForLaunch(); } + processBundlesLoad(bundles,false); + } + + /** + * just put code together + */ + private static void processBundlesLoad(List bundles, boolean isLazyLoad){ // Handle I/O if (sIOActions != null) { ExecutorService executor = Executors.newFixedThreadPool(sIOActions.size()); @@ -788,7 +859,11 @@ private static void loadBundles(List bundles) { // Notify `postSetUp' to all launchers for (BundleLauncher launcher : sBundleLaunchers) { - launcher.postSetUp(); + if(isLazyLoad){ + launcher.postLazySetUp(); + }else{ + launcher.postSetUp(); + } } // Wait for the things to be done on UI thread after `postSetUp`, @@ -803,12 +878,15 @@ private static void loadBundles(List bundles) { // Free all unused temporary variables for (Bundle bundle : bundles) { - if (bundle.parser != null) { - bundle.parser.close(); - bundle.parser = null; + if(!bundle.isLazyLoad()){ + bundle.setLoaded(true); + if (bundle.parser != null) { + bundle.parser.close(); + bundle.parser = null; + } + bundle.mBuiltinFile = null; + bundle.mExtractPath = null; } - bundle.mBuiltinFile = null; - bundle.mExtractPath = null; } } @@ -868,5 +946,26 @@ public void handleMessage(Message msg) { } } } + private static class LazyLoadBundleHandler extends Handler { + private Small.OnLazyCompleteListener mLazyListener; + + public LazyLoadBundleHandler(Small.OnLazyCompleteListener listener) { + mLazyListener = listener; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_LAZY_LOAD_COMPLETE: + if (mLazyListener != null) { + mLazyListener.onComplete(); + } + mLazyListener = null; + sLazyThread = null; + sLazyThread = null; + break; + } + } + } } diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/BundleLauncher.java b/Android/DevSample/small/src/main/java/net/wequick/small/BundleLauncher.java index 9a30e5fe..0299fd96 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/BundleLauncher.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/BundleLauncher.java @@ -110,6 +110,8 @@ public void setUp(Context context) { } */ public void postSetUp() { } + public void postLazySetUp(){} + /** * Called when loading bundles by {@link Bundle#loadLaunchableBundles(Small.OnCompleteListener)}. * @@ -121,7 +123,9 @@ public void postSetUp() { } */ public boolean resolveBundle(Bundle bundle) { if (!preloadBundle(bundle)) return false; - + if(bundle.isLazyLoad()){ + return false; + } loadBundle(bundle); return true; } diff --git a/Android/DevSample/small/src/main/java/net/wequick/small/Small.java b/Android/DevSample/small/src/main/java/net/wequick/small/Small.java index bab5f47f..5f0c68e4 100644 --- a/Android/DevSample/small/src/main/java/net/wequick/small/Small.java +++ b/Android/DevSample/small/src/main/java/net/wequick/small/Small.java @@ -24,6 +24,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.widget.Toast; import net.wequick.small.util.ApplicationUtils; import net.wequick.small.util.ReflectAccelerator; @@ -90,6 +91,10 @@ public interface ActivityLifecycleCallbacks { void onActivityDestroyed(Activity activity); } + public interface OnLazyCompleteListener { + void onComplete(); + } + public static Application getContext() { if (sContext == null) { // While launching bundle independently, the `Small.setUp` may not be called, @@ -284,7 +289,7 @@ public static boolean openUri(String uriString, Context context) { return openUri(makeUri(uriString), context); } - public static boolean openUri(Uri uri, Context context) { + public static boolean openUri(Uri uri, final Context context) { // System url schemes String scheme = uri.getScheme(); if (scheme != null @@ -297,10 +302,14 @@ public static boolean openUri(Uri uri, Context context) { } // Small url schemes - Bundle bundle = Bundle.getLaunchableBundle(uri); + final Bundle bundle = Bundle.getLaunchableBundle(uri); if (bundle != null) { - bundle.launchFrom(context); - return true; + if(!bundle.isLoaded()){ + Toast.makeText(context, "插件包未加载,请稍等", Toast.LENGTH_SHORT).show(); + }else{ + bundle.launchFrom(context); + return true; + } } return false; } diff --git a/Android/Sample/app.home/src/main/java/net/wequick/example/small/app/home/MainFragment.java b/Android/Sample/app.home/src/main/java/net/wequick/example/small/app/home/MainFragment.java index b7bd1a2c..cfc4feec 100644 --- a/Android/Sample/app.home/src/main/java/net/wequick/example/small/app/home/MainFragment.java +++ b/Android/Sample/app.home/src/main/java/net/wequick/example/small/app/home/MainFragment.java @@ -15,6 +15,7 @@ import net.wequick.small.Small; import net.wequick.example.small.lib.utils.UIUtils; +import net.wequick.small.Small.OnLazyCompleteListener; import org.json.JSONArray; import org.json.JSONObject; @@ -77,6 +78,20 @@ public void onClick(View v) { checkUpgrade(); } }); + + button = (Button) rootView.findViewById(R.id.btnLazyLoad); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + net.wequick.small.Bundle.loadLazyBundles(new OnLazyCompleteListener() { + @Override + public void onComplete() { + Toast.makeText(getActivity(), "插件包延时加载成功", Toast.LENGTH_SHORT).show(); + } + }); + } + }); + return rootView; } diff --git a/Android/Sample/app.home/src/main/res/layout/fragment_main.xml b/Android/Sample/app.home/src/main/res/layout/fragment_main.xml index 69084118..36056ea8 100644 --- a/Android/Sample/app.home/src/main/res/layout/fragment_main.xml +++ b/Android/Sample/app.home/src/main/res/layout/fragment_main.xml @@ -73,4 +73,17 @@ android:layout_alignParentRight="true" android:layout_alignParentEnd="true" app:backgroundTint="@color/colorLight" /> + + diff --git a/Android/Sample/app.home/src/main/res/values/strings.xml b/Android/Sample/app.home/src/main/res/values/strings.xml index 242ff983..bed5d4bb 100644 --- a/Android/Sample/app.home/src/main/res/values/strings.xml +++ b/Android/Sample/app.home/src/main/res/values/strings.xml @@ -5,5 +5,6 @@ Open web.about Call lib.utils Check upgrade + Trigger LazyLoad Open detail/sub diff --git a/Android/Sample/app/src/main/assets/bundle.json b/Android/Sample/app/src/main/assets/bundle.json index 32b6f5bb..3b713470 100644 --- a/Android/Sample/app/src/main/assets/bundle.json +++ b/Android/Sample/app/src/main/assets/bundle.json @@ -28,6 +28,7 @@ { "uri": "detail", "pkg": "net.wequick.example.small.app.detail", + "lazyload":true, "rules": { "sub": "Sub" } diff --git a/Android/Sample/build.gradle b/Android/Sample/build.gradle index 878a45f1..5443a02a 100644 --- a/Android/Sample/build.gradle +++ b/Android/Sample/build.gradle @@ -6,7 +6,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.2.2' classpath 'net.wequick.tools.build:gradle-small:1.1.0-beta3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Android/Sample/gradle/wrapper/gradle-wrapper.properties b/Android/Sample/gradle/wrapper/gradle-wrapper.properties index 608154d6..a1af8612 100644 --- a/Android/Sample/gradle/wrapper/gradle-wrapper.properties +++ b/Android/Sample/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/Android/Sample/lib.afterutils/public.txt b/Android/Sample/lib.afterutils/public.txt index e69de29b..281639d2 100644 --- a/Android/Sample/lib.afterutils/public.txt +++ b/Android/Sample/lib.afterutils/public.txt @@ -0,0 +1 @@ +int string app_name 0x45020000 diff --git a/Android/Sample/lib.analytics/public.txt b/Android/Sample/lib.analytics/public.txt index e69de29b..b121c802 100644 --- a/Android/Sample/lib.analytics/public.txt +++ b/Android/Sample/lib.analytics/public.txt @@ -0,0 +1 @@ +int string app_name 0x76020000 diff --git a/Android/Sample/lib.style/public.txt b/Android/Sample/lib.style/public.txt index 40311f5a..5b9c2879 100644 --- a/Android/Sample/lib.style/public.txt +++ b/Android/Sample/lib.style/public.txt @@ -1,13 +1,13 @@ -int dimen activity_horizontal_margin 0x79020000 -int dimen activity_vertical_margin 0x79020001 -int dimen appbar_padding_top 0x79020002 -int dimen fab_margin 0x79020003 -int style AppTheme_NoActionBar 0x79030000 -int style AppTheme 0x79030001 -int style AppTheme_AppBarOverlay 0x79030002 -int style AppTheme_PopupOverlay 0x79030003 -int color colorAccent 0x79040000 -int color colorLight 0x79040001 -int color colorPrimary 0x79040002 -int color colorPrimaryDark 0x79040003 -int anim my_fade_in 0x79050000 +int anim my_fade_in 0x79020000 +int dimen activity_horizontal_margin 0x79030000 +int dimen activity_vertical_margin 0x79030001 +int dimen appbar_padding_top 0x79030002 +int dimen fab_margin 0x79030003 +int style AppTheme_NoActionBar 0x79040000 +int style AppTheme 0x79040001 +int style AppTheme_AppBarOverlay 0x79040002 +int style AppTheme_PopupOverlay 0x79040003 +int color colorAccent 0x79050000 +int color colorLight 0x79050001 +int color colorPrimary 0x79050002 +int color colorPrimaryDark 0x79050003 diff --git a/Android/Sample/lib.utils/public.txt b/Android/Sample/lib.utils/public.txt index e4f7820f..edb2b3d3 100644 --- a/Android/Sample/lib.utils/public.txt +++ b/Android/Sample/lib.utils/public.txt @@ -1,10 +1,10 @@ int attr label 0x73010000 int bool my_test_bool 0x73020000 -int array my_test_colors 0x73030000 -int array my_test_integers 0x73030001 -int array my_test_strings 0x73030002 -int color my_test_color1 0x73040000 -int color my_test_color2 0x73040001 -int dimen my_test_dimen 0x73050000 -int id my_test_id 0x73060000 -int integer my_test_integer 0x73070000 +int dimen my_test_dimen 0x73030000 +int integer my_test_integer 0x73040000 +int array my_test_colors 0x73050000 +int array my_test_integers 0x73050001 +int array my_test_strings 0x73050002 +int color my_test_color1 0x73060000 +int color my_test_color2 0x73060001 +int id my_test_id 0x73070000