diff --git a/AndroidStub/src/main/java/android/app/ResourcesManager.java b/AndroidStub/src/main/java/android/app/ResourcesManager.java index 518118c666954015ea1ccb0254d09f236ad12e40..20ee6907ad37e6756cfac6da0e0f298caf064b6d 100644 --- a/AndroidStub/src/main/java/android/app/ResourcesManager.java +++ b/AndroidStub/src/main/java/android/app/ResourcesManager.java @@ -8,4 +8,9 @@ public class ResourcesManager { public static ResourcesManager getInstance() { throw new RuntimeException("Stub!"); } + + public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { + throw new RuntimeException("Stub!"); + } + } \ No newline at end of file diff --git a/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java b/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..92fc45d53f87a28cbd8e1e8e793b6c41b952ebb9 --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/CompatibilityInfo.java @@ -0,0 +1,41 @@ +package android.content.res; + +import android.content.pm.ApplicationInfo; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Created by qiaopu on 2018/5/3. + */ +public class CompatibilityInfo implements Parcelable { + + public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, + boolean forceCompat) { + throw new RuntimeException("Stub!"); + } + + @Override + public int describeContents() { + throw new RuntimeException("Stub!"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new RuntimeException("Stub!"); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public CompatibilityInfo createFromParcel(Parcel source) { + throw new RuntimeException("Stub!"); + } + + @Override + public CompatibilityInfo[] newArray(int size) { + throw new RuntimeException("Stub!"); + } + }; + +} + diff --git a/AndroidStub/src/main/java/android/content/res/Resources.java b/AndroidStub/src/main/java/android/content/res/Resources.java new file mode 100644 index 0000000000000000000000000000000000000000..58692ef156249b805af8da9c1b54a671ef701d5c --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/Resources.java @@ -0,0 +1,67 @@ +package android.content.res; + +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; + +/** + * Created by qiaopu on 2018/5/18. + */ +public class Resources { + + public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { + throw new RuntimeException("Stub!"); + } + + public final AssetManager getAssets() { + throw new RuntimeException("Stub!"); + } + + public Configuration getConfiguration() { + throw new RuntimeException("Stub!"); + } + + public DisplayMetrics getDisplayMetrics() { + throw new RuntimeException("Stub!"); + } + + public Drawable getDrawable(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public CharSequence getText(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public XmlResourceParser getXml(int id) throws NotFoundException { + throw new RuntimeException("Stub!"); + } + + public ResourcesImpl getImpl() { + throw new RuntimeException("Stub!"); + } + + public final Theme newTheme() { + throw new RuntimeException("Stub!"); + } + + public final class Theme { + + public void applyStyle(int resId, boolean force) { + throw new RuntimeException("Stub!"); + } + + public TypedArray obtainStyledAttributes(int[] attrs) { + throw new RuntimeException("Stub!"); + } + + public void setTo(Theme other) { + throw new RuntimeException("Stub!"); + } + + } + + public static class NotFoundException extends RuntimeException { + + } + +} diff --git a/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java b/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..c25947f84b99724d27405234e3e4fd34e6d2cfc2 --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/ResourcesImpl.java @@ -0,0 +1,8 @@ +package android.content.res; + +/** + * Created by qiaopu on 2018/5/18. + */ +public class ResourcesImpl { + +} diff --git a/AndroidStub/src/main/java/android/content/res/ResourcesKey.java b/AndroidStub/src/main/java/android/content/res/ResourcesKey.java new file mode 100644 index 0000000000000000000000000000000000000000..b3cecd817feaf6ef2448a0e8d297c783ccd5e54c --- /dev/null +++ b/AndroidStub/src/main/java/android/content/res/ResourcesKey.java @@ -0,0 +1,41 @@ +package android.content.res; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Created by qiaopu on 2018/5/3. + */ +public final class ResourcesKey { + @Nullable + public final String mResDir; + + @Nullable + public final String[] mSplitResDirs; + + @Nullable + public final String[] mOverlayDirs; + + @Nullable + public final String[] mLibDirs; + + public final int mDisplayId; + + @NonNull + public final Configuration mOverrideConfiguration; + + @NonNull + public final CompatibilityInfo mCompatInfo; + + public ResourcesKey(@Nullable String resDir, + @Nullable String[] splitResDirs, + @Nullable String[] overlayDirs, + @Nullable String[] libDirs, + int displayId, + @Nullable Configuration overrideConfig, + @Nullable CompatibilityInfo compatInfo) { + throw new RuntimeException("Stub!"); + } + +} + diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java b/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java index 63dcf75267c1ee1f0600e8d0c4135a2d07dc0f9b..ff62f6a37b43dd0d0f0e30f4d93fef58dc94b015 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/PluginManager.java @@ -69,7 +69,7 @@ public class PluginManager { private Map mPlugins = new ConcurrentHashMap<>(); private final List mCallbacks = new ArrayList<>(); - private Instrumentation mInstrumentation; // Hooked instrumentation + private VAInstrumentation mInstrumentation; // Hooked instrumentation private IActivityManager mActivityManager; // Hooked IActivityManager binder private IContentProvider mIContentProvider; // Hooked IContentProvider binder @@ -277,7 +277,7 @@ public class PluginManager { return this.mContext; } - public Instrumentation getInstrumentation() { + public VAInstrumentation getInstrumentation() { return this.mInstrumentation; } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java index a248a73df442bc9a6b6362f310e747bdcb6a4148..92c5b317c75bbf98f19dba09c253bdb810a8d30e 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java @@ -47,11 +47,12 @@ import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; +import android.support.annotation.UiThread; import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.utils.DexUtil; @@ -108,12 +109,9 @@ public final class LoadedPlugin { } } - @WorkerThread - private static Resources createResources(Context context, File apk) { + private static Resources createResources(Context context, String packageName, File apk) throws Exception { if (Constants.COMBINE_RESOURCES) { - Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath()); - ResourcesManager.hookResources(context, resources); - return resources; + return ResourcesManager.createResources(context, packageName, apk); } else { Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk); @@ -145,7 +143,11 @@ public final class LoadedPlugin { private Application mApplication; + @UiThread LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { + if (Thread.currentThread() != Looper.getMainLooper().getThread()) { + throw new RuntimeException("plugin mast be created by UI thread."); + } this.mPluginManager = pluginManager; this.mHostContext = context; this.mLocation = apk.getAbsolutePath(); @@ -177,7 +179,7 @@ public final class LoadedPlugin { this.mPackageManager = new PluginPackageManager(); this.mPluginContext = new PluginContext(this); this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE); - this.mResources = createResources(context, apk); + this.mResources = createResources(context, getPackageName(), apk); this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader()); tryToCopyNativeLib(apk); @@ -280,14 +282,13 @@ public final class LoadedPlugin { } public void invokeApplication() { - if (mApplication != null) { - return; - } - // make sure application's callback is run on ui thread. RunUtil.runOnUiThread(new Runnable() { @Override public void run() { + if (mApplication != null) { + return; + } mApplication = makeApplication(false, mPluginManager.getInstrumentation()); } }, true); diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java index 337fb4c956238696de7128ea468e919f3367c130..462bd427e3ff8e29f8188c5da389c918c12d9d14 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/PluginContext.java @@ -36,6 +36,11 @@ class PluginContext extends ContextWrapper { super(plugin.getPluginManager().getHostContext()); this.mPlugin = plugin; } + + public PluginContext(LoadedPlugin plugin, Context base) { + super(base); + this.mPlugin = plugin; + } @Override public Context getApplicationContext() { diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java index 682246b5959535481cd096b09652d2d390880a08..3432ffaa654c89a9d6e5816776ea0edb709ecb8f 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/ResourcesManager.java @@ -16,70 +16,93 @@ package com.didi.virtualapk.internal; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.ActivityThread; +import android.app.LoadedApk; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.res.AssetManager; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.ResourcesImpl; +import android.content.res.ResourcesKey; import android.os.Build; +import android.util.ArrayMap; import android.util.DisplayMetrics; +import android.util.Log; import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.utils.Reflector; +import java.io.File; import java.lang.ref.WeakReference; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * Created by renyugang on 16/8/9. */ class ResourcesManager { + + public static final String TAG = "LoadedPlugin"; - public static synchronized Resources createResources(Context hostContext, String apk) { + private static Configuration mDefaultConfiguration; + + public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return createResourcesForN(hostContext, packageName, apk); + } + + Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath()); + ResourcesManager.hookResources(hostContext, resources); + return resources; + } + + private static synchronized Resources createResourcesSimple(Context hostContext, String apk) throws Exception { Resources hostResources = hostContext.getResources(); Resources newResources = null; AssetManager assetManager; - try { - Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - assetManager = AssetManager.class.newInstance(); - reflector.bind(assetManager); - reflector.call(hostContext.getApplicationInfo().sourceDir); - } else { - assetManager = hostResources.getAssets(); - reflector.bind(assetManager); - } - reflector.call(apk); - List pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); - for (LoadedPlugin plugin : pluginList) { - reflector.call(plugin.getLocation()); - } - if (isMiUi(hostResources)) { - newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); - } else if (isVivo(hostResources)) { - newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); - } else if (isNubia(hostResources)) { - newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); - } else if (isNotRawResources(hostResources)) { - newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); - } else { - // is raw android resources - newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); - } - // lastly, sync all LoadedPlugin to newResources - for (LoadedPlugin plugin : pluginList) { - plugin.updateResources(newResources); - } - } catch (Exception e) { - e.printStackTrace(); + Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + assetManager = AssetManager.class.newInstance(); + reflector.bind(assetManager); + reflector.call(hostContext.getApplicationInfo().sourceDir); + } else { + assetManager = hostResources.getAssets(); + reflector.bind(assetManager); } - + reflector.call(apk); + List pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins(); + for (LoadedPlugin plugin : pluginList) { + reflector.call(plugin.getLocation()); + } + if (isMiUi(hostResources)) { + newResources = MiUiResourcesCompat.createResources(hostResources, assetManager); + } else if (isVivo(hostResources)) { + newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager); + } else if (isNubia(hostResources)) { + newResources = NubiaResourcesCompat.createResources(hostResources, assetManager); + } else if (isNotRawResources(hostResources)) { + newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager); + } else { + // is raw android resources + newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); + } + // lastly, sync all LoadedPlugin to newResources + for (LoadedPlugin plugin : pluginList) { + plugin.updateResources(newResources); + } + return newResources; - } public static void hookResources(Context base, Resources resources) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return; + } try { Reflector reflector = Reflector.with(base); reflector.field("mResources").set(resources); @@ -108,6 +131,90 @@ class ResourcesManager { e.printStackTrace(); } } + + /** + * Use System Apis to update all existing resources. + *
+ * 1. Update ApplicationInfo.splitSourceDirs and LoadedApk.mSplitResDirs + *
+ * 2. Replace all keys of ResourcesManager.mResourceImpls to new ResourcesKey + *
+ * 3. Use ResourcesManager.appendLibAssetForMainAssetPath(appInfo.publicSourceDir, "${packageName}.vastub") to update all existing resources. + *
+ * + * see android.webkit.WebViewDelegate.addWebViewAssetPath(Context) + */ + @TargetApi(Build.VERSION_CODES.N) + private static Resources createResourcesForN(Context context, String packageName, File apk) throws Exception { + long startTime = System.currentTimeMillis(); + String newAssetPath = apk.getAbsolutePath(); + ApplicationInfo info = context.getApplicationInfo(); + String baseResDir = info.publicSourceDir; + + info.splitSourceDirs = append(info.splitSourceDirs, newAssetPath); + LoadedApk loadedApk = Reflector.with(context).field("mPackageInfo").get(); + + Reflector rLoadedApk = Reflector.with(loadedApk).field("mSplitResDirs"); + String[] splitResDirs = rLoadedApk.get(); + rLoadedApk.set(append(splitResDirs, newAssetPath)); + + final android.app.ResourcesManager resourcesManager = android.app.ResourcesManager.getInstance(); + ArrayMap> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get(); + + synchronized (resourcesManager) { + HashMap> resolvedMap = new HashMap<>(); + + if (Build.VERSION.SDK_INT >= 28 + || (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // P Preview + ResourcesManagerCompatForP.resolveResourcesImplMap(originalMap, resolvedMap, context, loadedApk); + + } else { + ResourcesManagerCompatForN.resolveResourcesImplMap(originalMap, resolvedMap, baseResDir, newAssetPath); + } + + originalMap.clear(); + originalMap.putAll(resolvedMap); + } + + android.app.ResourcesManager.getInstance().appendLibAssetForMainAssetPath(baseResDir, packageName + ".vastub"); + + Resources newResources = context.getResources(); + + // lastly, sync all LoadedPlugin to newResources + for (LoadedPlugin plugin : PluginManager.getInstance(context).getAllLoadedPlugins()) { + plugin.updateResources(newResources); + } + + Log.d(TAG, "createResourcesForN cost time: +" + (System.currentTimeMillis() - startTime) + "ms"); + return newResources; + } + + private static String[] append(String[] paths, String newPath) { + if (contains(paths, newPath)) { + return paths; + } + + final int newPathsCount = 1 + (paths != null ? paths.length : 0); + final String[] newPaths = new String[newPathsCount]; + if (paths != null) { + System.arraycopy(paths, 0, newPaths, 0, paths.length); + } + newPaths[newPathsCount - 1] = newPath; + return newPaths; + } + + @TargetApi(Build.VERSION_CODES.KITKAT) + private static boolean contains(String[] array, String value) { + if (array == null) { + return false; + } + for (int i = 0; i < array.length; i++) { + if (Objects.equals(array[i], value)) { + return true; + } + } + return false; + } private static boolean isMiUi(Resources resources) { return resources.getClass().getName().equals("android.content.res.MiuiResources"); @@ -170,4 +277,69 @@ class ResourcesManager { } } + private static final class ResourcesManagerCompatForN { + + public static void resolveResourcesImplMap(Map> originalMap, Map> resolvedMap, String baseResDir, String newAssetPath) throws Exception { + for (Map.Entry> entry : originalMap.entrySet()) { + ResourcesKey key = entry.getKey(); + if (Objects.equals(key.mResDir, baseResDir)) { + resolvedMap.put(new ResourcesKey(key.mResDir, + append(key.mSplitResDirs, newAssetPath), + key.mOverlayDirs, + key.mLibDirs, + key.mDisplayId, + key.mOverrideConfiguration, + key.mCompatInfo), entry.getValue()); + } else { + resolvedMap.put(key, entry.getValue()); + } + } + } + } + + private static final class ResourcesManagerCompatForP { + + @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + public static void resolveResourcesImplMap(Map> originalMap, Map> resolvedMap, Context context, LoadedApk loadedApk) throws Exception { + HashMap newResImplMap = new HashMap<>(); + Map resKeyMap = new HashMap<>(); + Resources newRes; + + // Recreate the resImpl of the context + + // See LoadedApk.getResources() + if (mDefaultConfiguration == null) { + mDefaultConfiguration = new Configuration(); + } + newRes = context.createConfigurationContext(mDefaultConfiguration).getResources(); + newResImplMap.put(newRes.getImpl(), context); + + // Recreate the ResImpl of the activity + for (WeakReference ref : PluginManager.getInstance(context).getInstrumentation().getActivities()) { + Activity activity = ref.get(); + if (activity != null) { + newRes = activity.createConfigurationContext(activity.getResources().getConfiguration()).getResources(); + newResImplMap.put(newRes.getImpl(), activity); + } + } + + // Mapping all resKey and resImpl + for (Map.Entry> entry : originalMap.entrySet()) { + ResourcesImpl resImpl = entry.getValue().get(); + if (resImpl != null) { + resKeyMap.put(resImpl, entry.getKey()); + } + resolvedMap.put(entry.getKey(), entry.getValue()); + } + + // Replace the resImpl to the new resKey and remove the origin resKey + for (Map.Entry entry : newResImplMap.entrySet()) { + ResourcesKey newKey = resKeyMap.get(entry.getKey()); + ResourcesImpl originResImpl = entry.getValue().getResources().getImpl(); + + resolvedMap.put(newKey, new WeakReference<>(originResImpl)); + resolvedMap.remove(resKeyMap.get(originResImpl)); + } + } + } } diff --git a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java index 1f7450ae30ecc8587b0acaa667d41715d58b35ce..76377d8ca4450b93130de4d24f4585ef2f71953f 100644 --- a/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java +++ b/CoreLibrary/src/main/java/com/didi/virtualapk/internal/VAInstrumentation.java @@ -37,6 +37,10 @@ import com.didi.virtualapk.PluginManager; import com.didi.virtualapk.utils.PluginUtil; import com.didi.virtualapk.utils.Reflector; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + /** * Created by renyugang on 16/8/10. @@ -46,6 +50,8 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba public static final int LAUNCH_ACTIVITY = 100; private Instrumentation mBase; + + private final ArrayList> mActivities = new ArrayList<>(); PluginManager mPluginManager; @@ -98,7 +104,7 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba ComponentName component = PluginUtil.getComponent(intent); if (component == null) { - return mBase.newActivity(cl, className, intent); + return newActivity(mBase.newActivity(cl, className, intent)); } String targetClassName = component.getClassName(); @@ -107,7 +113,7 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); if (plugin == null) { - return mBase.newActivity(cl, className, intent); + return newActivity(mBase.newActivity(cl, className, intent)); } Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); @@ -120,10 +126,10 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba // ignored. } - return activity; + return newActivity(activity); } - return mBase.newActivity(cl, className, intent); + return newActivity(mBase.newActivity(cl, className, intent)); } @Override @@ -152,7 +158,7 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); Reflector.with(base).field("mResources").set(plugin.getResources()); Reflector reflector = Reflector.with(activity); - reflector.field("mBase").set(plugin.getPluginContext()); + reflector.field("mBase").set(new PluginContext(plugin, activity.getBaseContext())); reflector.field("mApplication").set(plugin.getApplication()); // set screenOrientation @@ -207,4 +213,21 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba return mBase.getComponentName(); } + private Activity newActivity(Activity activity) { + synchronized (mActivities) { + for (int i = mActivities.size() - 1; i >= 0; i--) { + if (mActivities.get(i).get() == null) { + mActivities.remove(i); + } + } + mActivities.add(new WeakReference<>(activity)); + } + return activity; + } + + List> getActivities() { + synchronized (mActivities) { + return new ArrayList<>(mActivities); + } + } } diff --git a/app/src/main/java/com/didi/virtualapk/MainActivity.java b/app/src/main/java/com/didi/virtualapk/MainActivity.java index 65a31bd9b40fdb1f6a51a36a3d2aa09905a95746..d355264b4daaba2772ee19e905e3c647e518b697 100644 --- a/app/src/main/java/com/didi/virtualapk/MainActivity.java +++ b/app/src/main/java/com/didi/virtualapk/MainActivity.java @@ -14,15 +14,18 @@ import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; -import com.didi.virtualapk.internal.PluginContentResolver; import com.didi.virtualapk.internal.LoadedPlugin; +import com.didi.virtualapk.internal.PluginContentResolver; import java.io.File; - public class MainActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE_STORAGE = 20171222; @@ -105,14 +108,22 @@ public class MainActivity extends AppCompatActivity { bookUri = PluginContentResolver.wrapperUri(plugin, bookUri); Cursor bookCursor = getContentResolver().query(bookUri, new String[]{"_id", "name"}, null, null, null); - while (bookCursor.moveToNext()) { - int bookId = bookCursor.getInt(0); - String bookName = bookCursor.getString(1); - Log.d("ryg", "query book:" + bookId + ", " + bookName); + if (bookCursor != null) { + while (bookCursor.moveToNext()) { + int bookId = bookCursor.getInt(0); + String bookName = bookCursor.getString(1); + Log.d("ryg", "query book:" + bookId + ", " + bookName); + } + bookCursor.close(); } - bookCursor.close(); } else if (v.getId() == R.id.about) { showAbout(); + } else if (v.getId() == R.id.webview) { + LinearLayout linearLayout = (LinearLayout) v.getParent(); + WebView webView = new WebView(this); + linearLayout.addView(webView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1)); + webView.setWebViewClient(new WebViewClient()); + webView.loadUrl("http://github.com/didi/VirtualAPK"); } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 79b54ddd85587bdd1b70b0c863c7038400dd106f..b092c85effd88a81b62342e750bdedd37a321d5f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,8 +1,9 @@ -