提交 7d11f3bb 编写于 作者: S superq_sky

Merge branch 'android-p' into dev

# Conflicts:
#	CoreLibrary/src/main/java/com/didi/virtualapk/internal/LoadedPlugin.java
#	PluginDemo/app/build.gradle
#	PluginDemo/build.gradle
#	README.md
#	app/build.gradle
#	build.gradle
#	virtualapk-gradle-plugin/gradle.properties
......@@ -21,8 +21,8 @@ import android.content.ComponentName;
/** @hide */
oneway interface IServiceConnection {
void connected(in ComponentName name, IBinder service);
// void connected(in ComponentName name, IBinder service);
/** Added in Android O */
//void connected(in ComponentName name, IBinder service, boolean dead);
void connected(in ComponentName name, IBinder service, boolean dead);
}
package android.app;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
/**
* Created by qiaopu on 2018/5/7.
*/
public class Instrumentation {
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
throw new RuntimeException("Stub!");
}
public void callApplicationOnCreate(Application app) {
throw new RuntimeException("Stub!");
}
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
throw new RuntimeException("Stub!");
}
public void callActivityOnCreate(Activity activity, Bundle icicle) {
throw new RuntimeException("Stub!");
}
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
throw new RuntimeException("Stub!");
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
throw new RuntimeException("Stub!");
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
throw new RuntimeException("Stub!");
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {
throw new RuntimeException("Stub!");
}
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {
throw new RuntimeException("Stub!");
}
public Context getContext() {
throw new RuntimeException("Stub!");
}
public Context getTargetContext() {
throw new RuntimeException("Stub!");
}
public ComponentName getComponentName() {
throw new RuntimeException("Stub!");
}
public static final class ActivityResult {
public ActivityResult(int resultCode, Intent resultData) {
throw new RuntimeException("Stub!");
}
}
}
package android.app;
/**
* Created by qiaopu on 2018/4/25.
*/
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
package android.content;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Created by qiaopu on 2018/5/7.
*/
public abstract class ContentResolver {
public ContentResolver(Context context) {
throw new RuntimeException("Stub!");
}
public final @Nullable
Bundle call(@NonNull Uri uri, @NonNull String method,
@Nullable String arg, @Nullable Bundle extras) {
throw new RuntimeException("Stub!");
}
protected abstract IContentProvider acquireProvider(Context c, String name);
protected IContentProvider acquireExistingProvider(Context c, String name) {
throw new RuntimeException("Stub!");
}
public abstract boolean releaseProvider(IContentProvider icp);
protected abstract IContentProvider acquireUnstableProvider(Context c, String name);
public abstract boolean releaseUnstableProvider(IContentProvider icp);
public abstract void unstableProviderDied(IContentProvider icp);
public void appNotRespondingViaProvider(IContentProvider icp) {
throw new RuntimeException("Stub!");
}
}
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<CompatibilityInfo> CREATOR
= new Parcelable.Creator<CompatibilityInfo>() {
@Override
public CompatibilityInfo createFromParcel(Parcel source) {
throw new RuntimeException("Stub!");
}
@Override
public CompatibilityInfo[] newArray(int size) {
throw new RuntimeException("Stub!");
}
};
}
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 int getColor(int id) throws NotFoundException {
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 String getString(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 void updateConfiguration(Configuration config, DisplayMetrics metrics) {
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 {
}
}
package android.content.res;
/**
* Created by qiaopu on 2018/5/18.
*/
public class ResourcesImpl {
}
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!");
}
}
import com.android.build.gradle.api.ApplicationVariant
import com.android.build.gradle.api.LibraryVariant
import com.google.common.base.Joiner
apply plugin: 'com.android.library'
android {
......@@ -29,12 +33,31 @@ repositories {
jcenter()
}
final String projectAndroidStub = ':AndroidStub'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
provided project(':AndroidStub')
provided project(projectAndroidStub)
testCompile 'junit:junit:4.12'
}
// Using Stub classes first when compiling.
afterEvaluate {
project.android.libraryVariants.each { LibraryVariant variant ->
variant.javaCompile.doFirst { JavaCompile javaCompile ->
String projectAndroidStubPath = project.project(projectAndroidStub).projectDir.canonicalPath
// println "projectAndroidStubPath: ${projectAndroidStubPath}"
File stubPath = javaCompile.classpath.find {
it.canonicalPath.startsWith(projectAndroidStubPath)
}
if (stubPath == null) {
throw new RuntimeException("reset bootclasspath error.")
}
javaCompile.options.setBootClasspath(Joiner.on(File.pathSeparator).join(stubPath, javaCompile.options.bootClasspath))
}
}
}
apply from: 'upload.gradle'
......@@ -6,54 +6,56 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<activity android:exported="false" android:name="com.didi.virtualapk.delegate.StubActivity" android:launchMode="standard"/>
<!-- Stub Activities -->
<activity android:name=".A$1" android:launchMode="standard"/>
<activity android:name=".A$2" android:launchMode="standard"
<activity android:exported="false" android:name=".A$1" android:launchMode="standard"/>
<activity android:exported="false" android:name=".A$2" android:launchMode="standard"
android:theme="@android:style/Theme.Translucent" />
<!-- Stub Activities -->
<activity android:name=".B$1" android:launchMode="singleTop"/>
<activity android:name=".B$2" android:launchMode="singleTop"/>
<activity android:name=".B$3" android:launchMode="singleTop"/>
<activity android:name=".B$4" android:launchMode="singleTop"/>
<activity android:name=".B$5" android:launchMode="singleTop"/>
<activity android:name=".B$6" android:launchMode="singleTop"/>
<activity android:name=".B$7" android:launchMode="singleTop"/>
<activity android:name=".B$8" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$1" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$2" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$3" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$4" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$5" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$6" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$7" android:launchMode="singleTop"/>
<activity android:exported="false" android:name=".B$8" android:launchMode="singleTop"/>
<!-- Stub Activities -->
<activity android:name=".C$1" android:launchMode="singleTask"/>
<activity android:name=".C$2" android:launchMode="singleTask"/>
<activity android:name=".C$3" android:launchMode="singleTask"/>
<activity android:name=".C$4" android:launchMode="singleTask"/>
<activity android:name=".C$5" android:launchMode="singleTask"/>
<activity android:name=".C$6" android:launchMode="singleTask"/>
<activity android:name=".C$7" android:launchMode="singleTask"/>
<activity android:name=".C$8" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$1" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$2" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$3" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$4" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$5" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$6" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$7" android:launchMode="singleTask"/>
<activity android:exported="false" android:name=".C$8" android:launchMode="singleTask"/>
<!-- Stub Activities -->
<activity android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:name=".D$8" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:exported="false" android:name=".D$8" android:launchMode="singleInstance"/>
<!-- Local Service running in main process -->
<service android:name="com.didi.virtualapk.delegate.LocalService" />
<service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" />
<!-- Daemon Service running in child process -->
<service android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
<service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
<intent-filter>
<action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
</intent-filter>
</service>
<provider
android:exported="false"
android:name="com.didi.virtualapk.delegate.RemoteContentProvider"
android:authorities="${applicationId}.VirtualAPK.Provider"
android:process=":daemon" />
......
package android.content;
import android.annotation.TargetApi;
import android.os.Build;
/**
* Wrapper of {@link ContentResolver}
* Created by qiaopu on 2018/5/7.
*/
public abstract class ContentResolverWrapper extends ContentResolver {
ContentResolver mBase;
public ContentResolverWrapper(Context context) {
super(context);
mBase = context.getContentResolver();
}
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
return mBase.acquireProvider(context, auth);
}
@Override
protected IContentProvider acquireExistingProvider(Context context, String auth) {
return mBase.acquireExistingProvider(context, auth);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected IContentProvider acquireUnstableProvider(Context context, String auth) {
return mBase.acquireUnstableProvider(context, auth);
}
@Override
public boolean releaseProvider(IContentProvider icp) {
return mBase.releaseProvider(icp);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public boolean releaseUnstableProvider(IContentProvider icp) {
return mBase.releaseUnstableProvider(icp);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void unstableProviderDied(IContentProvider icp) {
mBase.unstableProviderDied(icp);
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
@Override
public void appNotRespondingViaProvider(IContentProvider icp) {
// dark greylist in Android P
// mBase.appNotRespondingViaProvider(icp);
}
}
......@@ -5,6 +5,7 @@ import android.util.Log;
import android.view.View;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import java.util.LinkedList;
......@@ -14,7 +15,7 @@ import java.util.LinkedList;
* Created by qiaopu on 2018/4/11.
*/
public class DataBinderMapperProxy extends DataBinderMapper implements PluginManager.Callback {
public static final String TAG = "DataBinderMapperProxy";
public static final String TAG = Constants.TAG_PREFIX + "DataBinderMapperProxy";
private final LinkedList<DataBinderMapper> mMappers;
private DataBinderMapper[] mCache;
......
......@@ -26,27 +26,32 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.databinding.DataBinderMapperProxy;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.Singleton;
import com.didi.virtualapk.delegate.IContentProviderProxy;
import com.didi.virtualapk.internal.PluginContentResolver;
import com.didi.virtualapk.delegate.ActivityManagerProxy;
import com.didi.virtualapk.delegate.IContentProviderProxy;
import com.didi.virtualapk.delegate.RemoteContentProvider;
import com.didi.virtualapk.internal.ComponentsHandler;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.internal.VAInstrumentation;
import com.didi.virtualapk.utils.PluginUtil;
import com.didi.virtualapk.utils.ReflectUtil;
import com.didi.virtualapk.internal.utils.PluginUtil;
import com.didi.virtualapk.utils.Reflector;
import com.didi.virtualapk.utils.RunUtil;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
......@@ -58,50 +63,89 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class PluginManager {
public static final String TAG = "PluginManager";
public static final String TAG = Constants.TAG_PREFIX + "PluginManager";
private static volatile PluginManager sInstance = null;
// Context of host app
private Context mContext;
private ComponentsHandler mComponentsHandler;
private Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();
private final List<Callback> mCallbacks = new ArrayList<>();
protected final Context mContext;
protected final Application mApplication;
protected ComponentsHandler mComponentsHandler;
protected final Map<String, LoadedPlugin> mPlugins = new ConcurrentHashMap<>();
protected final List<Callback> mCallbacks = new ArrayList<>();
private Instrumentation mInstrumentation; // Hooked instrumentation
private IActivityManager mActivityManager; // Hooked IActivityManager binder
private IContentProvider mIContentProvider; // Hooked IContentProvider binder
protected VAInstrumentation mInstrumentation; // Hooked instrumentation
protected IActivityManager mActivityManager; // Hooked IActivityManager binder
protected IContentProvider mIContentProvider; // Hooked IContentProvider binder
public static PluginManager getInstance(Context base) {
if (sInstance == null) {
synchronized (PluginManager.class) {
if (sInstance == null)
sInstance = new PluginManager(base);
if (sInstance == null) {
sInstance = createInstance(base);
}
}
}
return sInstance;
}
private static PluginManager createInstance(Context context) {
try {
Bundle metaData = context.getPackageManager()
.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA)
.metaData;
if (metaData == null) {
return new PluginManager(context);
}
String factoryClass = metaData.getString("VA_FACTORY");
if (factoryClass == null) {
return new PluginManager(context);
}
PluginManager pluginManager = Reflector.on(factoryClass).method("create", Context.class).call(context);
if (pluginManager != null) {
Log.d(TAG, "Created a instance of " + pluginManager.getClass());
return pluginManager;
}
} catch (Exception e) {
Log.w(TAG, "Created the instance error!", e);
}
return new PluginManager(context);
}
private PluginManager(Context context) {
Context app = context.getApplicationContext();
if (app == null) {
this.mContext = context;
protected PluginManager(Context context) {
if (context instanceof Application) {
this.mApplication = (Application) context;
this.mContext = mApplication.getBaseContext();
} else {
this.mContext = ((Application)app).getBaseContext();
final Context app = context.getApplicationContext();
if (app == null) {
this.mContext = context;
this.mApplication = ActivityThread.currentApplication();
} else {
this.mApplication = (Application) app;
this.mContext = mApplication.getBaseContext();
}
}
prepare();
mComponentsHandler = createComponentsHandler();
hookCurrentProcess();
}
private void prepare() {
Systems.sHostContext = getHostContext();
this.hookInstrumentationAndHandler();
this.hookSystemServices();
protected void hookCurrentProcess() {
hookInstrumentationAndHandler();
hookSystemServices();
hookDataBindingUtil();
}
public void init() {
mComponentsHandler = new ComponentsHandler(this);
RunUtil.getThreadPool().execute(new Runnable() {
@Override
public void run() {
......@@ -110,20 +154,41 @@ public class PluginManager {
});
}
private void doInWorkThread() {
protected void doInWorkThread() {
}
private void hookDataBindingUtil() {
try {
Class cls = Class.forName("android.databinding.DataBindingUtil");
Object old = ReflectUtil.getField(cls, null, "sMapper");
Callback callback = new DataBinderMapperProxy(old);
ReflectUtil.setField(cls, null, "sMapper", callback);
addCallback(callback);
public Application getHostApplication() {
return this.mApplication;
}
} catch (Exception e) {
e.printStackTrace();
protected ComponentsHandler createComponentsHandler() {
return new ComponentsHandler(this);
}
protected VAInstrumentation createInstrumentation(Instrumentation origin) throws Exception {
return new VAInstrumentation(this, origin);
}
protected ActivityManagerProxy createActivityManagerProxy(IActivityManager origin) throws Exception {
return new ActivityManagerProxy(this, origin);
}
protected LoadedPlugin createLoadedPlugin(File apk) throws Exception {
return new LoadedPlugin(this, this.mContext, apk);
}
protected void hookDataBindingUtil() {
Reflector.QuietReflector reflector = Reflector.QuietReflector.on("android.databinding.DataBindingUtil").field("sMapper");
Object old = reflector.get();
if (old != null) {
try {
Callback callback = Reflector.on("android.databinding.DataBinderMapperProxy").constructor().newInstance();
reflector.set(callback);
addCallback(callback);
Log.d(TAG, "hookDataBindingUtil succeed : " + callback);
} catch (Reflector.ReflectedException e) {
Log.w(TAG, e);
}
}
}
......@@ -139,55 +204,61 @@ public class PluginManager {
/**
* hookSystemServices, but need to compatible with Android O in future.
*/
private void hookSystemServices() {
protected void hookSystemServices() {
try {
Singleton<IActivityManager> defaultSingleton;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManager.class, null, "IActivityManagerSingleton");
defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
} else {
defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
}
IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
IActivityManager origin = defaultSingleton.get();
IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
createActivityManagerProxy(origin));
// Hook IActivityManager from ActivityManagerNative
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);
if (defaultSingleton.get() == activityManagerProxy) {
this.mActivityManager = activityManagerProxy;
Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
private void hookInstrumentationAndHandler() {
protected void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains("lbe")) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
ReflectUtil.setInstrumentation(activityThread, instrumentation);
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
ActivityThread activityThread = ActivityThread.currentActivityThread();
Instrumentation baseInstrumentation = activityThread.getInstrumentation();
// if (baseInstrumentation.getClass().getName().contains("lbe")) {
// // reject executing in paralell space, for example, lbe.
// System.exit(0);
// }
final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation);
Reflector.with(activityThread).field("mInstrumentation").set(instrumentation);
Handler mainHandler = Reflector.with(activityThread).method("getHandler").call();
Reflector.with(mainHandler).field("mCallback").set(instrumentation);
this.mInstrumentation = instrumentation;
Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation);
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
private void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
protected void hookIContentProviderAsNeeded() {
Uri uri = Uri.parse(RemoteContentProvider.getUri(mContext));
mContext.getContentResolver().call(uri, "wakeup", null, null);
try {
Field authority = null;
Field mProvider = null;
ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
Iterator iter = mProviderMap.entrySet().iterator();
Field provider = null;
ActivityThread activityThread = ActivityThread.currentActivityThread();
Map providerMap = Reflector.with(activityThread).field("mProviderMap").get();
Iterator iter = providerMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
......@@ -202,12 +273,12 @@ public class PluginManager {
}
auth = (String) authority.get(key);
}
if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
if (mProvider == null) {
mProvider = val.getClass().getDeclaredField("mProvider");
mProvider.setAccessible(true);
if (auth.equals(RemoteContentProvider.getAuthority(mContext))) {
if (provider == null) {
provider = val.getClass().getDeclaredField("mProvider");
provider.setAccessible(true);
}
IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
IContentProvider rawProvider = (IContentProvider) provider.get(val);
IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
mIContentProvider = proxy;
Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
......@@ -215,7 +286,7 @@ public class PluginManager {
}
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
......@@ -230,30 +301,33 @@ public class PluginManager {
}
if (!apk.exists()) {
throw new FileNotFoundException(apk.getAbsolutePath());
// throw the FileNotFoundException by opening a stream.
InputStream in = new FileInputStream(apk);
in.close();
}
LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
if (null != plugin) {
this.mPlugins.put(plugin.getPackageName(), plugin);
synchronized (mCallbacks) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onAddedLoadedPlugin(plugin);
}
LoadedPlugin plugin = createLoadedPlugin(apk);
if (null == plugin) {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
this.mPlugins.put(plugin.getPackageName(), plugin);
synchronized (mCallbacks) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onAddedLoadedPlugin(plugin);
}
// try to invoke plugin's application
plugin.invokeApplication();
} else {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
}
public LoadedPlugin getLoadedPlugin(Intent intent) {
ComponentName component = PluginUtil.getComponent(intent);
return getLoadedPlugin(component.getPackageName());
return getLoadedPlugin(PluginUtil.getComponent(intent));
}
public LoadedPlugin getLoadedPlugin(ComponentName component) {
if (component == null) {
return null;
}
return this.getLoadedPlugin(component.getPackageName());
}
......@@ -271,7 +345,7 @@ public class PluginManager {
return this.mContext;
}
public Instrumentation getInstrumentation() {
public VAInstrumentation getInstrumentation() {
return this.mInstrumentation;
}
......
/*
* Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.didi.virtualapk;
import android.content.Context;
/**
* Created by renyugang on 16/12/22.
*/
/**
* This is God class, you should not know where it's from and any details.
*/
public class Systems {
static Context sHostContext;
/**
* get a Context object anywhere you want.
* @return a Context object
*/
public static Context getContext() {
return sHostContext;
}
}
......@@ -33,28 +33,24 @@ import android.os.ServiceManager;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.utils.PluginUtil;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.utils.PluginUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author johnsonlee
*/
public class ActivityManagerProxy implements InvocationHandler {
private static final String TAG = "IActivityManagerProxy";
private static final String TAG = Constants.TAG_PREFIX + "IActivityManagerProxy";
public static final int INTENT_SENDER_BROADCAST = 1;
public static final int INTENT_SENDER_ACTIVITY = 2;
public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
public static final int INTENT_SENDER_SERVICE = 4;
public static IActivityManager newInstance(PluginManager pluginManager, IActivityManager activityManager) {
return (IActivityManager) Proxy.newProxyInstance(activityManager.getClass().getClassLoader(), new Class[] { IActivityManager.class }, new ActivityManagerProxy(pluginManager, activityManager));
}
private PluginManager mPluginManager;
private IActivityManager mActivityManager;
......@@ -87,25 +83,25 @@ public class ActivityManagerProxy implements InvocationHandler {
try {
return bindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
Log.w(TAG, e);
}
} else if ("unbindService".equals(method.getName())) {
try {
return unbindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
Log.w(TAG, e);
}
} else if ("getIntentSender".equals(method.getName())) {
try {
getIntentSender(method, args);
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
} else if ("overridePendingTransition".equals(method.getName())){
try {
overridePendingTransition(method, args);
} catch (Exception e){
e.printStackTrace();
Log.w(TAG, e);
}
}
......@@ -135,7 +131,7 @@ public class ActivityManagerProxy implements InvocationHandler {
}
private Object startService(Object proxy, Method method, Object[] args) throws Throwable {
protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args[0];
Intent target = (Intent) args[1];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
......@@ -147,7 +143,7 @@ public class ActivityManagerProxy implements InvocationHandler {
return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
private Object stopService(Object proxy, Method method, Object[] args) throws Throwable {
protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable {
Intent target = (Intent) args[1];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
......@@ -159,7 +155,7 @@ public class ActivityManagerProxy implements InvocationHandler {
return 1;
}
private Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable {
protected Object stopServiceToken(Object proxy, Method method, Object[] args) throws Throwable {
ComponentName component = (ComponentName) args[0];
Intent target = new Intent().setComponent(component);
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
......@@ -172,7 +168,7 @@ public class ActivityManagerProxy implements InvocationHandler {
return true;
}
private Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
Intent target = (Intent) args[2];
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
......@@ -187,7 +183,7 @@ public class ActivityManagerProxy implements InvocationHandler {
return 1;
}
private Object unbindService(Object proxy, Method method, Object[] args) throws Throwable {
protected Object unbindService(Object proxy, Method method, Object[] args) throws Throwable {
IBinder iServiceConnection = (IBinder)args[0];
Intent target = mPluginManager.getComponentsHandler().forgetIServiceConnection(iServiceConnection);
if (target == null) {
......@@ -200,12 +196,12 @@ public class ActivityManagerProxy implements InvocationHandler {
return true;
}
private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}
private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// fill in service with ComponentName
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
......@@ -225,7 +221,7 @@ public class ActivityManagerProxy implements InvocationHandler {
return intent;
}
private void getIntentSender(Method method, Object[] args) {
protected void getIntentSender(Method method, Object[] args) {
String hostPackageName = mPluginManager.getHostContext().getPackageName();
args[1] = hostPackageName;
......@@ -246,7 +242,7 @@ public class ActivityManagerProxy implements InvocationHandler {
}
}
private void overridePendingTransition(Method method, Object[] args) {
protected void overridePendingTransition(Method method, Object[] args) {
String hostPackageName = mPluginManager.getHostContext().getPackageName();
args[1] = hostPackageName;
}
......
......@@ -24,6 +24,7 @@ import android.os.Bundle;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.internal.PluginContentResolver;
......@@ -33,14 +34,12 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import static com.didi.virtualapk.delegate.RemoteContentProvider.KEY_WRAPPER_URI;
/**
* Created by renyugang on 16/12/8.
*/
public class IContentProviderProxy implements InvocationHandler {
private static final String TAG = "IContentProviderProxy";
private static final String TAG = Constants.TAG_PREFIX + "IContentProviderProxy";
private IContentProvider mBase;
private Context mContext;
......@@ -84,7 +83,7 @@ public class IContentProviderProxy implements InvocationHandler {
if (method.getName().equals("call")) {
bundleInCallMethod = getBundleParameter(args);
if (bundleInCallMethod != null) {
String uriString = bundleInCallMethod.getString(KEY_WRAPPER_URI);
String uriString = bundleInCallMethod.getString(RemoteContentProvider.KEY_WRAPPER_URI);
if (uriString != null) {
uri = Uri.parse(uriString);
}
......@@ -100,14 +99,9 @@ public class IContentProviderProxy implements InvocationHandler {
if (info != null) {
String pkg = info.packageName;
LoadedPlugin plugin = pluginManager.getLoadedPlugin(pkg);
String pluginUri = Uri.encode(uri.toString());
StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(mContext));
builder.append("/?plugin=" + plugin.getLocation());
builder.append("&pkg=" + pkg);
builder.append("&uri=" + pluginUri);
Uri wrapperUri = Uri.parse(builder.toString());
Uri wrapperUri = PluginContentResolver.wrapperUri(plugin, uri);
if (method.getName().equals("call")) {
bundleInCallMethod.putString(KEY_WRAPPER_URI, wrapperUri.toString());
bundleInCallMethod.putString(RemoteContentProvider.KEY_WRAPPER_URI, wrapperUri.toString());
} else {
args[index] = wrapperUri;
}
......
......@@ -31,9 +31,10 @@ import android.os.IBinder;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.utils.PluginUtil;
import com.didi.virtualapk.utils.ReflectUtil;
import com.didi.virtualapk.internal.utils.PluginUtil;
import com.didi.virtualapk.utils.Reflector;
import java.lang.reflect.Method;
......@@ -41,7 +42,7 @@ import java.lang.reflect.Method;
* @author johnsonlee
*/
public class LocalService extends Service {
private static final String TAG = "LocalService";
private static final String TAG = Constants.TAG_PREFIX + "LocalService";
/**
* The target service, usually it's a plugin service intent
......@@ -87,7 +88,7 @@ public class LocalService extends Service {
target.setExtrasClassLoader(plugin.getClassLoader());
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
......@@ -114,7 +115,7 @@ public class LocalService extends Service {
break;
}
case EXTRA_COMMAND_BIND_SERVICE: {
ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service = null;
......@@ -133,7 +134,7 @@ public class LocalService extends Service {
service.onCreate();
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
t.printStackTrace();
Log.w(TAG, t);
}
}
try {
......@@ -141,14 +142,12 @@ public class LocalService extends Service {
IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
if (Build.VERSION.SDK_INT >= 26) {
ReflectUtil.invokeNoException(IServiceConnection.class, iServiceConnection, "connected",
new Class[]{ComponentName.class, IBinder.class, boolean.class},
new Object[]{component, binder, false});
iServiceConnection.connected(component, binder, false);
} else {
iServiceConnection.connected(component, binder);
Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
break;
}
......
......@@ -30,6 +30,7 @@ import android.support.annotation.NonNull;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.utils.RunUtil;
......@@ -44,7 +45,7 @@ import java.util.Map;
*/
public class RemoteContentProvider extends ContentProvider {
private static final String TAG = "RemoteContentProvider";
private static final String TAG = Constants.TAG_PREFIX + "RemoteContentProvider";
public static final String KEY_PKG = "pkg";
public static final String KEY_PLUGIN = "plugin";
......@@ -76,7 +77,7 @@ public class RemoteContentProvider extends ContentProvider {
try {
pluginManager.loadPlugin(new File(uri.getQueryParameter(KEY_PLUGIN)));
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
......@@ -91,7 +92,7 @@ public class RemoteContentProvider extends ContentProvider {
contentProvider.attachInfo(loadedPlugin.getPluginContext(), providerInfo);
sCachedProviders.put(auth, contentProvider);
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
}, true);
......@@ -209,4 +210,12 @@ public class RemoteContentProvider extends ContentProvider {
return null;
}
public static String getAuthority(Context context) {
return context.getPackageName() + ".VirtualAPK.Provider";
}
public static String getUri(Context context) {
return "content://" + getAuthority(context);
}
}
......@@ -19,8 +19,10 @@ package com.didi.virtualapk.delegate;
import android.content.ComponentName;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import java.io.File;
......@@ -29,6 +31,8 @@ import java.io.File;
* @author johnsonlee
*/
public class RemoteService extends LocalService {
private static final String TAG = Constants.TAG_PREFIX + "RemoteService";
@Override
public IBinder onBind(Intent intent) {
......@@ -50,7 +54,7 @@ public class RemoteService extends LocalService {
try {
PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
}
......
package com.didi.virtualapk.delegate;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.annotation.Nullable;
/**
* Created by qiaopu on 2018/6/13.
*/
public class StubActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Go to the main activity
Intent mainIntent = getPackageManager().getLaunchIntentForPackage(getPackageName());
if (mainIntent == null) {
mainIntent = new Intent(Intent.ACTION_MAIN);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setPackage(getPackageName());
ResolveInfo resolveInfo = getPackageManager().resolveActivity(mainIntent, 0);
if (resolveInfo != null) {
mainIntent.setClassName(this, resolveInfo.activityInfo.name);
}
}
startActivity(mainIntent);
finish();
}
}
package com.didi.virtualapk.internal;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.Application;
import android.os.Bundle;
import com.didi.virtualapk.utils.Reflector;
import java.util.ArrayList;
/**
* Created by qiaopu on 2017/8/9.
*/
class ActivityLifecycleCallbacksProxy implements Application.ActivityLifecycleCallbacks {
final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
Reflector.QuietReflector.with(ActivityThread.currentApplication()).field("mActivityLifecycleCallbacks").get();
Object[] collectActivityLifecycleCallbacks() {
if (mActivityLifecycleCallbacks == null) {
return null;
}
Object[] callbacks = null;
synchronized (mActivityLifecycleCallbacks) {
if (mActivityLifecycleCallbacks.size() > 0) {
callbacks = mActivityLifecycleCallbacks.toArray();
}
}
return callbacks;
}
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(activity,
savedInstanceState);
}
}
}
@Override
public void onActivityStarted(Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStarted(activity);
}
}
}
@Override
public void onActivityResumed(Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityResumed(activity);
}
}
}
@Override
public void onActivityPaused(Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPaused(activity);
}
}
}
@Override
public void onActivityStopped(Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStopped(activity);
}
}
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivitySaveInstanceState(activity,
outState);
}
}
}
@Override
public void onActivityDestroyed(Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(activity);
}
}
}
}
......@@ -37,7 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class ComponentsHandler {
public static final String TAG = "PluginManager";
public static final String TAG = Constants.TAG_PREFIX + "PluginManager";
private Context mContext;
private PluginManager mPluginManager;
......
......@@ -30,5 +30,8 @@ public class Constants {
public static final boolean COMBINE_RESOURCES = true;
public static final boolean COMBINE_CLASSLOADER = true;
public static final boolean DEBUG = true;
public static final String TAG = "VA";
public static final String TAG_PREFIX = TAG + ".";
}
......@@ -16,108 +16,76 @@
package com.didi.virtualapk.internal;
import android.content.ContentResolver;
import android.annotation.TargetApi;
import android.content.ContentResolverWrapper;
import android.content.Context;
import android.content.IContentProvider;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Keep;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.delegate.RemoteContentProvider;
import java.lang.reflect.Method;
/**
* Created by renyugang on 16/12/7.
*/
public class PluginContentResolver extends ContentResolver {
private ContentResolver mBase;
public class PluginContentResolver extends ContentResolverWrapper {
private PluginManager mPluginManager;
private static Method sAcquireProvider;
private static Method sAcquireExistingProvider;
private static Method sAcquireUnstableProvider;
static {
try {
sAcquireProvider = ContentResolver.class.getDeclaredMethod("acquireProvider",
new Class[]{Context.class, String.class});
sAcquireProvider.setAccessible(true);
sAcquireExistingProvider = ContentResolver.class.getDeclaredMethod("acquireExistingProvider",
new Class[]{Context.class, String.class});
sAcquireExistingProvider.setAccessible(true);
sAcquireUnstableProvider = ContentResolver.class.getDeclaredMethod("acquireUnstableProvider",
new Class[]{Context.class, String.class});
sAcquireUnstableProvider.setAccessible(true);
} catch (Exception e) {
//ignored
}
}
public PluginContentResolver(Context context) {
super(context);
mBase = context.getContentResolver();
mPluginManager = PluginManager.getInstance(context);
}
@Override
protected IContentProvider acquireProvider(Context context, String auth) {
try {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth);
} catch (Exception e) {
e.printStackTrace();
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return null;
return super.acquireProvider(context, auth);
}
@Override
protected IContentProvider acquireExistingProvider(Context context, String auth) {
try {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return (IContentProvider) sAcquireExistingProvider.invoke(mBase, context, auth);
} catch (Exception e) {
e.printStackTrace();
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return null;
return super.acquireExistingProvider(context, auth);
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected IContentProvider acquireUnstableProvider(Context context, String auth) {
try {
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return (IContentProvider) sAcquireUnstableProvider.invoke(mBase, context, auth);
} catch (Exception e) {
e.printStackTrace();
if (mPluginManager.resolveContentProvider(auth, 0) != null) {
return mPluginManager.getIContentProvider();
}
return null;
return super.acquireUnstableProvider(context, auth);
}
@Override
public boolean releaseProvider(IContentProvider provider) {
return true;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public boolean releaseUnstableProvider(IContentProvider icp) {
return true;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void unstableProviderDied(IContentProvider icp) {
}
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
@Override
public void appNotRespondingViaProvider(IContentProvider icp) {
}
/** @hide */
protected int resolveUserIdFromAuthority(String auth) {
return 0;
}
......@@ -126,7 +94,7 @@ public class PluginContentResolver extends ContentResolver {
public static Uri wrapperUri(LoadedPlugin loadedPlugin, Uri pluginUri) {
String pkg = loadedPlugin.getPackageName();
String pluginUriString = Uri.encode(pluginUri.toString());
StringBuilder builder = new StringBuilder(PluginContentResolver.getUri(loadedPlugin.getHostContext()));
StringBuilder builder = new StringBuilder(RemoteContentProvider.getUri(loadedPlugin.getHostContext()));
builder.append("/?plugin=" + loadedPlugin.getLocation());
builder.append("&pkg=" + pkg);
builder.append("&uri=" + pluginUriString);
......@@ -136,12 +104,12 @@ public class PluginContentResolver extends ContentResolver {
@Deprecated
public static String getAuthority(Context context) {
return context.getPackageName() + ".VirtualAPK.Provider";
return RemoteContentProvider.getAuthority(context);
}
@Deprecated
public static String getUri(Context context) {
return "content://" + getAuthority(context);
return RemoteContentProvider.getUri(context);
}
@Keep
......
......@@ -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() {
......
......@@ -16,87 +16,205 @@
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.ReflectUtil;
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 = Constants.TAG_PREFIX + "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 Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
try {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
assetManager = AssetManager.class.newInstance();
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", hostContext.getApplicationInfo().sourceDir);
} else {
assetManager = hostResources.getAssets();
}
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", apk);
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
ReflectUtil.invoke(AssetManager.class, assetManager, "addAssetPath", plugin.getLocation());
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);
final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;
if (cookie1 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir);
}
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);
} else {
assetManager = hostResources.getAssets();
reflector.bind(assetManager);
}
final int cookie2 = reflector.call(apk);
if (cookie2 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + apk);
}
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
final int cookie3 = reflector.call(plugin.getLocation());
if (cookie3 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
}
} catch (Exception e) {
e.printStackTrace();
}
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 {
ReflectUtil.setField(base.getClass(), base, "mResources", resources);
Object loadedApk = ReflectUtil.getPackageInfo(base);
ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources);
Object activityThread = ReflectUtil.getActivityThread(base);
Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");
if (Build.VERSION.SDK_INT < 24) {
Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
Reflector reflector = Reflector.with(base);
reflector.field("mResources").set(resources);
Object loadedApk = reflector.field("mPackageInfo").get();
Reflector.with(loadedApk).field("mResources").set(resources);
Object activityThread = ActivityThread.currentActivityThread();
Object resManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
resManager = android.app.ResourcesManager.getInstance();
} else {
// still hook Android N Resources, even though it's unnecessary, then nobody will be strange.
Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls");
Object key = map.keySet().iterator().next();
Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl");
map.put(key, new WeakReference<>(resourcesImpl));
resManager = Reflector.with(activityThread).field("mResourcesManager").get();
}
Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field("mActiveResources").get();
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
/**
* Use System Apis to update all existing resources.
* <br/>
* 1. Update ApplicationInfo.splitSourceDirs and LoadedApk.mSplitResDirs
* <br/>
* 2. Replace all keys of ResourcesManager.mResourceImpls to new ResourcesKey
* <br/>
* 3. Use ResourcesManager.appendLibAssetForMainAssetPath(appInfo.publicSourceDir, "${packageName}.vastub") to update all existing resources.
* <br/>
*
* 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<ResourcesKey, WeakReference<ResourcesImpl>> originalMap = Reflector.with(resourcesManager).field("mResourceImpls").get();
synchronized (resourcesManager) {
HashMap<ResourcesKey, WeakReference<ResourcesImpl>> 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) {
......@@ -117,34 +235,30 @@ class ResourcesManager {
private static final class MiUiResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.MiuiResources");
Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
Reflector reflector = Reflector.on("android.content.res.MiuiResources");
Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)
.newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
return newResources;
}
}
private static final class VivoResourcesCompat {
private static Resources createResources(Context hostContext, Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.VivoResources");
Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
ReflectUtil.invokeNoException(resourcesClazz, newResources, "init",
new Class[]{String.class}, hostContext.getPackageName());
Object themeValues = ReflectUtil.getFieldNoException(resourcesClazz, hostResources, "mThemeValues");
ReflectUtil.setFieldNoException(resourcesClazz, newResources, "mThemeValues", themeValues);
Reflector reflector = Reflector.on("android.content.res.VivoResources");
Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)
.newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
reflector.method("init", String.class).callByCaller(newResources, hostContext.getPackageName());
reflector.field("mThemeValues");
reflector.set(newResources, reflector.get(hostResources));
return newResources;
}
}
private static final class NubiaResourcesCompat {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Class resourcesClazz = Class.forName("android.content.res.NubiaResources");
Resources newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
Reflector reflector = Reflector.on("android.content.res.NubiaResources");
Resources newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)
.newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
return newResources;
}
}
......@@ -153,10 +267,9 @@ class ResourcesManager {
private static Resources createResources(Resources hostResources, AssetManager assetManager) throws Exception {
Resources newResources;
try {
Class resourcesClazz = hostResources.getClass();
newResources = (Resources) ReflectUtil.invokeConstructor(resourcesClazz,
new Class[]{AssetManager.class, DisplayMetrics.class, Configuration.class},
new Object[]{assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()});
Reflector reflector = Reflector.with(hostResources);
newResources = reflector.constructor(AssetManager.class, DisplayMetrics.class, Configuration.class)
.newInstance(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
} catch (Exception e) {
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
......@@ -165,4 +278,70 @@ class ResourcesManager {
}
}
private static final class ResourcesManagerCompatForN {
@TargetApi(Build.VERSION_CODES.KITKAT)
public static void resolveResourcesImplMap(Map<ResourcesKey, WeakReference<ResourcesImpl>> originalMap, Map<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap, String baseResDir, String newAssetPath) throws Exception {
for (Map.Entry<ResourcesKey, WeakReference<ResourcesImpl>> 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<ResourcesKey, WeakReference<ResourcesImpl>> originalMap, Map<ResourcesKey, WeakReference<ResourcesImpl>> resolvedMap, Context context, LoadedApk loadedApk) throws Exception {
HashMap<ResourcesImpl, Context> newResImplMap = new HashMap<>();
Map<ResourcesImpl, ResourcesKey> 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<Activity> 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<ResourcesKey, WeakReference<ResourcesImpl>> 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<ResourcesImpl, Context> 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));
}
}
}
}
......@@ -58,7 +58,7 @@ class StubActivityInfo {
boolean windowIsTranslucent = array.getBoolean(0, false);
array.recycle();
if (Constants.DEBUG) {
Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
Log.d(Constants.TAG_PREFIX + "StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
}
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
switch (launchMode) {
......
......@@ -16,198 +16,163 @@
package com.didi.virtualapk.internal;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.app.Fragment;
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.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PersistableBundle;
import android.util.Log;
import android.view.ContextThemeWrapper;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.utils.PluginUtil;
import com.didi.virtualapk.utils.ReflectUtil;
import com.didi.virtualapk.delegate.StubActivity;
import com.didi.virtualapk.internal.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.
*/
public class VAInstrumentation extends Instrumentation implements Handler.Callback {
public static final String TAG = "VAInstrumentation";
public static final String TAG = Constants.TAG_PREFIX + "VAInstrumentation";
public static final int LAUNCH_ACTIVITY = 100;
private Instrumentation mBase;
protected Instrumentation mBase;
protected final ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>();
PluginManager mPluginManager;
protected PluginManager mPluginManager;
public VAInstrumentation(PluginManager pluginManager, Instrumentation base) {
this.mPluginManager = pluginManager;
this.mBase = base;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
}
private ActivityResult realExecStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode);
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Fragment target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}
private ActivityResult realExecStartActivity(
Context who, IBinder contextThread, IBinder token, Fragment target,
Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Fragment.class, Intent.class,
int.class, Bundle.class};
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Fragment target, Intent intent, int requestCode, Bundle options) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}
private ActivityResult realExecStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, String.class, Intent.class,
int.class, Bundle.class};
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
@Override
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target, Intent intent, int requestCode, Bundle options) {
injectIntent(intent);
return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode, options);
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
protected void injectIntent(Intent intent) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(), intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return null;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
Log.i(TAG, String.format("newActivity[%s]", className));
} catch (ClassNotFoundException e) {
ComponentName component = PluginUtil.getComponent(intent);
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
if (component == null) {
return newActivity(mBase.newActivity(cl, className, intent));
}
String targetClassName = component.getClassName();
Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));
if (plugin != null) {
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
if (plugin == null) {
// Not found then goto stub activity.
boolean debuggable = false;
try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
} catch (Exception ignored) {
// ignored.
Context context = this.mPluginManager.getHostContext();
debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Throwable ex) {
}
return activity;
if (debuggable) {
throw new ActivityNotFoundException("error intent: " + intent.toURI());
}
Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class);
return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent));
}
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
// for 4.1+
Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
return newActivity(activity);
}
return mBase.newActivity(cl, className, intent);
return newActivity(mBase.newActivity(cl, className, intent));
}
@Override
public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return mBase.newApplication(cl, className, context);
}
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
injectActivity(activity);
mBase.callActivityOnCreate(activity, icicle);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
injectActivity(activity);
mBase.callActivityOnCreate(activity, icicle, persistentState);
}
protected void injectActivity(Activity activity) {
final Intent intent = activity.getIntent();
if (PluginUtil.isIntentFromPlugin(intent)) {
Context base = activity.getBaseContext();
try {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());
ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());
Reflector.with(base).field("mResources").set(plugin.getResources());
Reflector reflector = Reflector.with(activity);
reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext()));
reflector.field("mApplication").set(plugin.getApplication());
// set screenOrientation
ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
......@@ -215,12 +180,9 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba
activity.setRequestedOrientation(activityInfo.screenOrientation);
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
mBase.callActivityOnCreate(activity, icicle);
}
@Override
......@@ -229,9 +191,10 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba
// ActivityClientRecord r
Object r = msg.obj;
try {
Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent");
intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader());
ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo");
Reflector reflector = Reflector.with(r);
Intent intent = reflector.field("intent").get();
intent.setExtrasClassLoader(mPluginManager.getHostContext().getClassLoader());
ActivityInfo activityInfo = reflector.field("activityInfo").get();
if (PluginUtil.isIntentFromPlugin(intent)) {
int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
......@@ -241,7 +204,7 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba
}
}
} catch (Exception e) {
e.printStackTrace();
Log.w(TAG, e);
}
}
......@@ -263,4 +226,21 @@ public class VAInstrumentation extends Instrumentation implements Handler.Callba
return mBase.getComponentName();
}
protected 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<WeakReference<Activity>> getActivities() {
synchronized (mActivities) {
return new ArrayList<>(mActivities);
}
}
}
......@@ -14,13 +14,14 @@
* limitations under the License.
*/
package com.didi.virtualapk.utils;
package com.didi.virtualapk.internal.utils;
import android.app.ActivityThread;
import android.content.Context;
import android.os.Build;
import com.didi.virtualapk.Systems;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.utils.Reflector;
import java.io.File;
import java.lang.reflect.Array;
......@@ -28,67 +29,56 @@ import java.lang.reflect.Field;
import java.util.List;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
public class DexUtil {
private static boolean sHasInsertedNativeLibrary = false;
public static void insertDex(DexClassLoader dexClassLoader) throws Exception {
Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader) throws Exception {
Object baseDexElements = getDexElements(getPathList(baseClassLoader));
Object newDexElements = getDexElements(getPathList(dexClassLoader));
Object allDexElements = combineArray(baseDexElements, newDexElements);
Object pathList = getPathList(getPathClassLoader());
ReflectUtil.setField(pathList.getClass(), pathList, "dexElements", allDexElements);
Object pathList = getPathList(baseClassLoader);
Reflector.with(pathList).field("dexElements").set(allDexElements);
insertNativeLibrary(dexClassLoader);
}
private static PathClassLoader getPathClassLoader() {
PathClassLoader pathClassLoader = (PathClassLoader) DexUtil.class.getClassLoader();
return pathClassLoader;
insertNativeLibrary(dexClassLoader, baseClassLoader);
}
private static Object getDexElements(Object pathList) throws Exception {
return ReflectUtil.getField(pathList.getClass(), pathList, "dexElements");
return Reflector.with(pathList).field("dexElements").get();
}
private static Object getPathList(Object baseDexClassLoader) throws Exception {
return ReflectUtil.getField(Class.forName("dalvik.system.BaseDexClassLoader"), baseDexClassLoader, "pathList");
private static Object getPathList(ClassLoader baseDexClassLoader) throws Exception {
return Reflector.with(baseDexClassLoader).field("pathList").get();
}
private static Object combineArray(Object firstArray, Object secondArray) {
Class<?> localClass = firstArray.getClass().getComponentType();
int firstArrayLength = Array.getLength(firstArray);
int allLength = firstArrayLength + Array.getLength(secondArray);
Object result = Array.newInstance(localClass, allLength);
for (int k = 0; k < allLength; ++k) {
if (k < firstArrayLength) {
Array.set(result, k, Array.get(firstArray, k));
} else {
Array.set(result, k, Array.get(secondArray, k - firstArrayLength));
}
}
int secondArrayLength = Array.getLength(secondArray);
Object result = Array.newInstance(localClass, firstArrayLength + secondArrayLength);
System.arraycopy(firstArray, 0, result, 0, firstArrayLength);
System.arraycopy(secondArray, 0, result, firstArrayLength, secondArrayLength);
return result;
}
private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader) throws Exception {
private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader) throws Exception {
if (sHasInsertedNativeLibrary) {
return;
}
sHasInsertedNativeLibrary = true;
Object basePathList = getPathList(getPathClassLoader());
Context context = ActivityThread.currentApplication();
Object basePathList = getPathList(baseClassLoader);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
List<File> nativeLibraryDirectories = (List<File>) ReflectUtil.getField(basePathList.getClass(),
basePathList, "nativeLibraryDirectories");
nativeLibraryDirectories.add(Systems.getContext().getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE));
Reflector reflector = Reflector.with(basePathList);
List<File> nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get();
nativeLibraryDirectories.add(context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE));
Object baseNativeLibraryPathElements = ReflectUtil.getField(basePathList.getClass(), basePathList, "nativeLibraryPathElements");
Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get();
final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements);
Object newPathList = getPathList(dexClassLoader);
Object newNativeLibraryPathElements = ReflectUtil.getField(newPathList.getClass(), newPathList, "nativeLibraryPathElements");
Object newNativeLibraryPathElements = reflector.get(newPathList);
Class<?> elementClass = newNativeLibraryPathElements.getClass().getComponentType();
Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1);
System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength);
......@@ -110,15 +100,15 @@ public class DexUtil {
}
}
ReflectUtil.setField(basePathList.getClass(), basePathList, "nativeLibraryPathElements", allNativeLibraryPathElements);
reflector.set(allNativeLibraryPathElements);
} else {
File[] nativeLibraryDirectories = (File[]) ReflectUtil.getFieldNoException(basePathList.getClass(),
basePathList, "nativeLibraryDirectories");
Reflector reflector = Reflector.with(basePathList).field("nativeLibraryDirectories");
File[] nativeLibraryDirectories = reflector.get();
final int N = nativeLibraryDirectories.length;
File[] newNativeLibraryDirectories = new File[N + 1];
System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N);
newNativeLibraryDirectories[N] = Systems.getContext().getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
ReflectUtil.setField(basePathList.getClass(), basePathList, "nativeLibraryDirectories", newNativeLibraryDirectories);
newNativeLibraryDirectories[N] = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
reflector.set(newNativeLibraryDirectories);
}
}
......
......@@ -14,12 +14,14 @@
* limitations under the License.
*/
package com.didi.virtualapk.utils;
package com.didi.virtualapk.internal.utils;
import android.content.Context;
import android.content.pm.PackageParser;
import android.os.Build;
import com.didi.virtualapk.utils.Reflector;
import java.io.File;
/**
......@@ -27,52 +29,54 @@ import java.io.File;
*/
public final class PackageParserCompat {
public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
if (Build.VERSION.SDK_INT >= 24) {
if (Build.VERSION.PREVIEW_SDK_INT == 0) {
public static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) {
try {
if (Build.VERSION.SDK_INT >= 28
|| (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview
return PackageParserPPreview.parsePackage(context, apk, flags);
} else if (Build.VERSION.SDK_INT >= 24) {
return PackageParserV24.parsePackage(context, apk, flags);
} else if (Build.VERSION.SDK_INT >= 21) {
return PackageParserLollipop.parsePackage(context, apk, flags);
} else {
return PackageParserPPreview.parsePackage(context, apk, flags);
return PackageParserLegacy.parsePackage(context, apk, flags);
}
} else if (Build.VERSION.SDK_INT >= 21) {
return PackageParserLollipop.parsePackage(context, apk, flags);
} else {
return PackageParserLegacy.parsePackage(context, apk, flags);
} catch (Throwable e) {
throw new RuntimeException("error", e);
}
}
private static final class PackageParserPPreview {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws PackageParser.PackageParserException {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {
PackageParser parser = new PackageParser();
PackageParser.Package pkg = parser.parsePackage(apk, flags);
ReflectUtil.invokeNoException(PackageParser.class, null, "collectCertificates",
new Class[]{PackageParser.Package.class, boolean.class}, pkg, false);
Reflector.with(parser)
.method("collectCertificates", PackageParser.Package.class, boolean.class)
.call(pkg, false);
return pkg;
}
}
private static final class PackageParserV24 {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws PackageParser.PackageParserException {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {
PackageParser parser = new PackageParser();
PackageParser.Package pkg = parser.parsePackage(apk, flags);
ReflectUtil.invokeNoException(PackageParser.class, null, "collectCertificates",
new Class[]{PackageParser.Package.class, int.class}, pkg, flags);
Reflector.with(parser)
.method("collectCertificates", PackageParser.Package.class, int.class)
.call(pkg, flags);
return pkg;
}
}
private static final class PackageParserLollipop {
static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws PackageParser.PackageParserException {
static final PackageParser.Package parsePackage(final Context context, final File apk, final int flags) throws Throwable {
PackageParser parser = new PackageParser();
PackageParser.Package pkg = parser.parsePackage(apk, flags);
try {
parser.collectCertificates(pkg, flags);
} catch (Throwable e) {
// ignored
}
parser.collectCertificates(pkg, flags);
return pkg;
}
......@@ -80,11 +84,12 @@ public final class PackageParserCompat {
private static final class PackageParserLegacy {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) {
static final PackageParser.Package parsePackage(Context context, File apk, int flags) throws Throwable {
PackageParser parser = new PackageParser(apk.getAbsolutePath());
PackageParser.Package pkg = parser.parsePackage(apk, apk.getAbsolutePath(), context.getResources().getDisplayMetrics(), flags);
ReflectUtil.invokeNoException(PackageParser.class, parser, "collectCertificates",
new Class[]{PackageParser.Package.class, int.class}, pkg, flags);
Reflector.with(parser)
.method("collectCertificates", PackageParser.Package.class, int.class)
.call(pkg, flags);
return pkg;
}
......
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.didi.virtualapk.utils;
package com.didi.virtualapk.internal.utils;
import android.app.Activity;
import android.content.ComponentName;
......@@ -30,11 +30,11 @@ import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.view.ContextThemeWrapper;
import com.didi.virtualapk.PluginManager;
import com.didi.virtualapk.internal.Constants;
import com.didi.virtualapk.internal.LoadedPlugin;
import com.didi.virtualapk.utils.Reflector;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
......@@ -51,17 +51,25 @@ import java.util.zip.ZipFile;
* Created by renyugang on 16/8/15.
*/
public class PluginUtil {
public static String getTargetActivity(Intent intent) {
return intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY);
}
public static final String TAG = Constants.TAG_PREFIX + "NativeLib";
public static ComponentName getComponent(Intent intent) {
return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE),
if (intent == null) {
return null;
}
if (isIntentFromPlugin(intent)) {
return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE),
intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY));
}
return intent.getComponent();
}
public static boolean isIntentFromPlugin(Intent intent) {
if (intent == null) {
return false;
}
return intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false);
}
......@@ -90,7 +98,7 @@ public class PluginUtil {
return appInfo.theme;
}
return PluginUtil.selectDefaultTheme(0, Build.VERSION.SDK_INT);
return selectDefaultTheme(0, Build.VERSION.SDK_INT);
}
public static int selectDefaultTheme(final int curTheme, final int targetSdkVersion) {
......@@ -101,7 +109,7 @@ public class PluginUtil {
android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
private static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) {
public static int selectSystemTheme(final int curTheme, final int targetSdkVersion, final int orig, final int holo, final int dark, final int deviceDefault) {
if (curTheme != 0) {
return curTheme;
}
......@@ -133,19 +141,20 @@ public class PluginUtil {
final LoadedPlugin plugin = PluginManager.getInstance(activity).getLoadedPlugin(packageName);
final Resources resources = plugin.getResources();
if (resources != null) {
ReflectUtil.setField(base.getClass(), base, "mResources", resources);
Reflector.with(base).field("mResources").set(resources);
// copy theme
Resources.Theme theme = resources.newTheme();
theme.setTo(activity.getTheme());
int themeResource = (int)ReflectUtil.getField(ContextThemeWrapper.class, activity, "mThemeResource");
Reflector reflector = Reflector.with(activity);
int themeResource = reflector.field("mThemeResource").get();
theme.applyStyle(themeResource, true);
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mTheme", theme);
reflector.field("mTheme").set(theme);
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", resources);
reflector.field("mResources").set(resources);
}
} catch (Exception e) {
e.printStackTrace();
Log.w(Constants.TAG, e);
}
}
......@@ -161,23 +170,19 @@ public class PluginUtil {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
bundle.putBinder(key, value);
} else {
try {
ReflectUtil.invoke(Bundle.class, bundle, "putIBinder", new Class[]{String.class, IBinder.class}, key, value);
} catch (Exception e) {
}
Reflector.QuietReflector.with(bundle).method("putIBinder", String.class, IBinder.class).call(key, value);
}
}
public static IBinder getBinder(Bundle bundle, String key) {
if (bundle == null) {
return null;
}
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return bundle.getBinder(key);
} else {
try {
return (IBinder) ReflectUtil.invoke(Bundle.class, bundle, "getIBinder", key);
} catch (Exception e) {
}
return null;
return (IBinder) Reflector.QuietReflector.with(bundle)
.method("getIBinder", String.class).call(key);
}
}
......@@ -203,12 +208,12 @@ public class PluginUtil {
} finally {
zipfile.close();
Log.d("NativeLib", "Done! +" + (System.currentTimeMillis() - startTime) + "ms");
Log.d(TAG, "Done! +" + (System.currentTimeMillis() - startTime) + "ms");
}
}
private static boolean findAndCopyNativeLib(ZipFile zipfile, Context context, String cpuArch, PackageInfo packageInfo, File nativeLibDir) throws Exception {
Log.d("NativeLib", "Try to copy plugin's cup arch: " + cpuArch);
Log.d(TAG, "Try to copy plugin's cup arch: " + cpuArch);
boolean findLib = false;
boolean findSo = false;
byte buffer[] = null;
......@@ -236,29 +241,29 @@ public class PluginUtil {
if (buffer == null) {
findSo = true;
Log.d("NativeLib", "Found plugin's cup arch dir: " + cpuArch);
Log.d(TAG, "Found plugin's cup arch dir: " + cpuArch);
buffer = new byte[8192];
}
String libName = entryName.substring(entryName.lastIndexOf('/') + 1);
Log.d("NativeLib", "verify so " + libName);
Log.d(TAG, "verify so " + libName);
File libFile = new File(nativeLibDir, libName);
String key = packageInfo.packageName + "_" + libName;
if (libFile.exists()) {
int VersionCode = Settings.getSoVersion(context, key);
if (VersionCode == packageInfo.versionCode) {
Log.d("NativeLib", "skip existing so : " + entry.getName());
Log.d(TAG, "skip existing so : " + entry.getName());
continue;
}
}
FileOutputStream fos = new FileOutputStream(libFile);
Log.d("NativeLib", "copy so " + entry.getName() + " of " + cpuArch);
Log.d(TAG, "copy so " + entry.getName() + " of " + cpuArch);
copySo(buffer, zipfile.getInputStream(entry), fos);
Settings.setSoVersion(context, key, packageInfo.versionCode);
}
if (!findLib) {
Log.d("NativeLib", "Fast skip all!");
Log.d(TAG, "Fast skip all!");
return true;
}
......
......@@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.didi.virtualapk.utils;
package com.didi.virtualapk.internal.utils;
import android.content.Context;
import android.content.SharedPreferences;
......
/*
* Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.didi.virtualapk.utils;
import android.app.Instrumentation;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.UiThread;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@SuppressWarnings("rawtypes")
public class ReflectUtil {
public static Object sActivityThread;
public static Object sLoadedApk;
public static Instrumentation sInstrumentation;
public static Object getField(Class clazz, Object target, String name) throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(target);
}
public static Object getFieldNoException(Class clazz, Object target, String name) {
try {
return ReflectUtil.getField(clazz, target, name);
} catch (Exception e) {
//ignored.
}
return null;
}
public static void setField(Class clazz, Object target, String name, Object value) throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
public static void setFieldNoException(Class clazz, Object target, String name, Object value) {
try {
ReflectUtil.setField(clazz, target, name, value);
} catch (Exception e) {
//ignored.
}
}
@SuppressWarnings("unchecked")
public static Object invoke(Class clazz, Object target, String name, Object... args)
throws Exception {
Class[] parameterTypes = null;
if (args != null) {
parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
parameterTypes[i] = args[i].getClass();
}
}
Method method = clazz.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method.invoke(target, args);
}
@SuppressWarnings("unchecked")
public static Object invoke(Class clazz, Object target, String name, Class[] parameterTypes, Object... args)
throws Exception {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method.invoke(target, args);
}
@SuppressWarnings("unchecked")
public static Object invokeNoException(Class clazz, Object target, String name, Class[] parameterTypes, Object... args) {
try {
return invoke(clazz, target, name, parameterTypes, args);
} catch (Exception e) {
}
return null;
}
@SuppressWarnings("unchecked")
public static Object invokeConstructor(Class clazz, Class[] parameterTypes, Object... args)
throws Exception {
Constructor constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor.newInstance(args);
}
@UiThread
public static Object getActivityThread(Context base) {
if (sActivityThread == null) {
try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Object activityThread = null;
try {
activityThread = ReflectUtil.getField(activityThreadClazz, null, "sCurrentActivityThread");
} catch (Exception e) {
// ignored
}
if (activityThread == null) {
activityThread = ((ThreadLocal<?>) ReflectUtil.getField(activityThreadClazz, null, "sThreadLocal")).get();
}
sActivityThread = activityThread;
} catch (Exception e) {
e.printStackTrace();
}
}
return sActivityThread;
}
public static Instrumentation getInstrumentation(Context base) {
if (getActivityThread(base) != null) {
try {
sInstrumentation = (Instrumentation) ReflectUtil.invoke(
sActivityThread.getClass(), sActivityThread, "getInstrumentation");
} catch (Exception e) {
e.printStackTrace();
}
}
return sInstrumentation;
}
public static void setInstrumentation(Object activityThread, Instrumentation instrumentation) {
try {
ReflectUtil.setField(activityThread.getClass(), activityThread, "mInstrumentation", instrumentation);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object getPackageInfo(Context base) {
if (sLoadedApk == null) {
try {
sLoadedApk = ReflectUtil.getField(base.getClass(), base, "mPackageInfo");
} catch (Exception e) {
e.printStackTrace();
}
}
return sLoadedApk;
}
public static void setHandlerCallback(Context base, Handler.Callback callback) {
try {
Object activityThread = getActivityThread(base);
Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null);
ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback);
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.didi.virtualapk.utils;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import com.didi.virtualapk.internal.Constants;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Created by qiaopu on 2018/4/26.
*/
public class Reflector {
public static final String LOG_TAG = Constants.TAG_PREFIX + "Reflector";
protected Class<?> mType;
protected Object mCaller;
protected Constructor mConstructor;
protected Field mField;
protected Method mMethod;
public static class ReflectedException extends Exception {
public ReflectedException(String message) {
super(message);
}
public ReflectedException(String message, Throwable cause) {
super(message, cause);
}
}
public static Reflector on(@NonNull String name) throws ReflectedException {
return on(name, true, Reflector.class.getClassLoader());
}
public static Reflector on(@NonNull String name, boolean initialize) throws ReflectedException {
return on(name, initialize, Reflector.class.getClassLoader());
}
public static Reflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) throws ReflectedException {
try {
return on(Class.forName(name, initialize, loader));
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
public static Reflector on(@NonNull Class<?> type) {
Reflector reflector = new Reflector();
reflector.mType = type;
return reflector;
}
public static Reflector with(@NonNull Object caller) throws ReflectedException {
return on(caller.getClass()).bind(caller);
}
protected Reflector() {
}
public Reflector constructor(@Nullable Class<?>... parameterTypes) throws ReflectedException {
try {
mConstructor = mType.getDeclaredConstructor(parameterTypes);
mConstructor.setAccessible(true);
mField = null;
mMethod = null;
return this;
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
@SuppressWarnings("unchecked")
public <R> R newInstance(@Nullable Object ... initargs) throws ReflectedException {
if (mConstructor == null) {
throw new ReflectedException("Constructor was null!");
}
try {
return (R) mConstructor.newInstance(initargs);
} catch (InvocationTargetException e) {
throw new ReflectedException("Oops!", e.getTargetException());
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
protected Object checked(@Nullable Object caller) throws ReflectedException {
if (caller == null || mType.isInstance(caller)) {
return caller;
}
throw new ReflectedException("Caller [" + caller + "] is not a instance of type [" + mType + "]!");
}
protected void check(@Nullable Object caller, @Nullable Member member, @NonNull String name) throws ReflectedException {
if (member == null) {
throw new ReflectedException(name + " was null!");
}
if (caller == null && !Modifier.isStatic(member.getModifiers())) {
throw new ReflectedException("Need a caller!");
}
checked(caller);
}
public Reflector bind(@Nullable Object caller) throws ReflectedException {
mCaller = checked(caller);
return this;
}
public Reflector unbind() {
mCaller = null;
return this;
}
public Reflector field(@NonNull String name) throws ReflectedException {
try {
mField = findField(name);
mField.setAccessible(true);
mConstructor = null;
mMethod = null;
return this;
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
protected Field findField(@NonNull String name) throws NoSuchFieldException {
try {
return mType.getField(name);
} catch (NoSuchFieldException e) {
for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {
try {
return cls.getDeclaredField(name);
} catch (NoSuchFieldException ex) {
// Ignored
}
}
throw e;
}
}
@SuppressWarnings("unchecked")
public <R> R get() throws ReflectedException {
return get(mCaller);
}
@SuppressWarnings("unchecked")
public <R> R get(@Nullable Object caller) throws ReflectedException {
check(caller, mField, "Field");
try {
return (R) mField.get(caller);
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
public Reflector set(@Nullable Object value) throws ReflectedException {
return set(mCaller, value);
}
public Reflector set(@Nullable Object caller, @Nullable Object value) throws ReflectedException {
check(caller, mField, "Field");
try {
mField.set(caller, value);
return this;
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
public Reflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) throws ReflectedException {
try {
mMethod = findMethod(name, parameterTypes);
mMethod.setAccessible(true);
mConstructor = null;
mField = null;
return this;
} catch (NoSuchMethodException e) {
throw new ReflectedException("Oops!", e);
}
}
protected Method findMethod(@NonNull String name, @Nullable Class<?>... parameterTypes) throws NoSuchMethodException {
try {
return mType.getMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
for (Class<?> cls = mType; cls != null; cls = cls.getSuperclass()) {
try {
return cls.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException ex) {
// Ignored
}
}
throw e;
}
}
public <R> R call(@Nullable Object... args) throws ReflectedException {
return callByCaller(mCaller, args);
}
@SuppressWarnings("unchecked")
public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) throws ReflectedException {
check(caller, mMethod, "Method");
try {
return (R) mMethod.invoke(caller, args);
} catch (InvocationTargetException e) {
throw new ReflectedException("Oops!", e.getTargetException());
} catch (Throwable e) {
throw new ReflectedException("Oops!", e);
}
}
public static class QuietReflector extends Reflector {
protected Throwable mIgnored;
public static QuietReflector on(@NonNull String name) {
return on(name, true, QuietReflector.class.getClassLoader());
}
public static QuietReflector on(@NonNull String name, boolean initialize) {
return on(name, initialize, QuietReflector.class.getClassLoader());
}
public static QuietReflector on(@NonNull String name, boolean initialize, @Nullable ClassLoader loader) {
Class<?> cls = null;
try {
cls = Class.forName(name, initialize, loader);
return on(cls, null);
} catch (Throwable e) {
// Log.w(LOG_TAG, "Oops!", e);
return on(cls, e);
}
}
public static QuietReflector on(@Nullable Class<?> type) {
return on(type, (type == null) ? new ReflectedException("Type was null!") : null);
}
private static QuietReflector on(@Nullable Class<?> type, @Nullable Throwable ignored) {
QuietReflector reflector = new QuietReflector();
reflector.mType = type;
reflector.mIgnored = ignored;
return reflector;
}
public static QuietReflector with(@Nullable Object caller) {
if (caller == null) {
return on((Class<?>) null);
}
return on(caller.getClass()).bind(caller);
}
protected QuietReflector() {
}
public Throwable getIgnored() {
return mIgnored;
}
protected boolean skip() {
return skipAlways() || mIgnored != null;
}
protected boolean skipAlways() {
return mType == null;
}
@Override
public QuietReflector constructor(@Nullable Class<?>... parameterTypes) {
if (skipAlways()) {
return this;
}
try {
mIgnored = null;
super.constructor(parameterTypes);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public <R> R newInstance(@Nullable Object... initargs) {
if (skip()) {
return null;
}
try {
mIgnored = null;
return super.newInstance(initargs);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return null;
}
@Override
public QuietReflector bind(@Nullable Object obj) {
if (skipAlways()) {
return this;
}
try {
mIgnored = null;
super.bind(obj);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public QuietReflector unbind() {
super.unbind();
return this;
}
@Override
public QuietReflector field(@NonNull String name) {
if (skipAlways()) {
return this;
}
try {
mIgnored = null;
super.field(name);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public <R> R get() {
if (skip()) {
return null;
}
try {
mIgnored = null;
return super.get();
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return null;
}
@Override
public <R> R get(@Nullable Object caller) {
if (skip()) {
return null;
}
try {
mIgnored = null;
return super.get(caller);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return null;
}
@Override
public QuietReflector set(@Nullable Object value) {
if (skip()) {
return this;
}
try {
mIgnored = null;
super.set(value);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public QuietReflector set(@Nullable Object caller, @Nullable Object value) {
if (skip()) {
return this;
}
try {
mIgnored = null;
super.set(caller, value);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public QuietReflector method(@NonNull String name, @Nullable Class<?>... parameterTypes) {
if (skipAlways()) {
return this;
}
try {
mIgnored = null;
super.method(name, parameterTypes);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return this;
}
@Override
public <R> R call(@Nullable Object... args) {
if (skip()) {
return null;
}
try {
mIgnored = null;
return super.call(args);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return null;
}
@Override
public <R> R callByCaller(@Nullable Object caller, @Nullable Object... args) {
if (skip()) {
return null;
}
try {
mIgnored = null;
return super.callByCaller(caller, args);
} catch (Throwable e) {
mIgnored = e;
// Log.w(LOG_TAG, "Oops!", e);
}
return null;
}
}
}
......@@ -23,12 +23,14 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.util.Log;
import android.util.Pair;
import com.didi.virtualapk.internal.Constants;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* Created by renyugang on 16/11/10.
......@@ -53,7 +55,7 @@ public class RunUtil {
* @param waitUtilDone if set true, the caller thread will wait until the specific runnable finished.
*/
public static void runOnUiThread(Runnable runnable, boolean waitUtilDone) {
if (Looper.myLooper() == Looper.getMainLooper()) {
if (Thread.currentThread() == Looper.getMainLooper().getThread()) {
runnable.run();
return;
}
......@@ -68,7 +70,7 @@ public class RunUtil {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
Log.w(Constants.TAG, e);
}
}
}
......@@ -77,17 +79,22 @@ public class RunUtil {
return AsyncTask.THREAD_POOL_EXECUTOR;
}
public static String getProcessNameByPid(Context context, int pid) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcessList = manager.getRunningAppProcesses();
if (appProcessList != null) {
for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessList) {
if (pid == appProcessInfo.pid) {
return appProcessInfo.processName;
private static String getProcessNameByPid(Context context, int pid) {
try {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcessList = manager.getRunningAppProcesses();
if (appProcessList != null) {
for (ActivityManager.RunningAppProcessInfo appProcessInfo : appProcessList) {
if (pid == appProcessInfo.pid) {
return appProcessInfo.processName;
}
}
}
} catch (Throwable e) {
Log.w(Constants.TAG, e);
}
return null;
}
......@@ -117,11 +124,15 @@ public class RunUtil {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_RUN_ON_UITHREAD) {
Pair<Runnable, CountDownLatch> pair = (Pair<Runnable, CountDownLatch>)msg.obj;
Runnable runnable = pair.first;
runnable.run();
if (pair.second != null) {
pair.second.countDown();
Pair<Runnable, CountDownLatch> pair = (Pair<Runnable, CountDownLatch>) msg.obj;
try {
Runnable runnable = pair.first;
runnable.run();
} finally {
if (pair.second != null) {
pair.second.countDown();
}
}
}
}
......
......@@ -18,6 +18,9 @@ package com.didi.virtualapk.utils;
import android.content.Context;
import android.util.Base64;
import android.util.Log;
import com.didi.virtualapk.internal.Constants;
import java.io.File;
import java.io.FileInputStream;
......@@ -46,14 +49,18 @@ import java.util.zip.ZipFile;
public class ZipVerifyUtil {
public static boolean verifyZip(Context context, String zipPath) {
return verifyZip(context, zipPath, "test.cer");
}
public static boolean verifyZip(Context context, String zipPath, String cerName) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
InputStream in = context.getAssets().open("test.cer");
InputStream in = context.getAssets().open(cerName);
Certificate certificate = certificateFactory.generateCertificate(in);
in.close();
return verifyZip(zipPath, certificate);
} catch (IOException | CertificateException e) {
e.printStackTrace();
Log.w(Constants.TAG, e);
return false;
}
}
......@@ -65,7 +72,7 @@ public class ZipVerifyUtil {
remoteCertificate.verify(certificate.getPublicKey());
return true;
} catch (Exception e) {
e.printStackTrace();
Log.w(Constants.TAG, e);
return false;
}
}
......
......@@ -11,7 +11,7 @@ def gitUrl = 'https://github.com/didi/VirtualAPK' // Git仓库的url
group = GROUP_ID
archivesBaseName = 'core'
version = "0.9.6-dev"
version = "0.9.6"
install {
......
# <img src="imgs/va-logo.png" width="200px" align="center" alt="VirtualAPK"/>
[![license](http://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat)](https://github.com/didi/VirtualAPK/blob/master/LICENSE)
[![Release Version](https://img.shields.io/badge/release-0.9.1-red.svg)](https://github.com/didi/VirtualAPK/releases)
[![Release Version](https://img.shields.io/badge/release-0.9.6-red.svg)](https://github.com/didi/VirtualAPK/releases)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/didi/VirtualAPK/pulls)
VirtualAPK is a powerful yet lightweight plugin framework for Android. It can dynamically load and run an APK file (we call it `LoadedPlugin`) seamlessly as an installed application. Developers can use any Class, Resources, Activity, Service, Receiver and Provider in `LoadedPlugin` as if they are registered in app's manifest file.
......
......@@ -28,7 +28,10 @@
## com.didi.virtualapk:core:0.9.5
1. 修复多个bug,强烈建议升级至此版本,以前版本不再维护。
2. 与 com.didi.virtualapk:gradle:0.9.8.2 搭配使用,支持官方 Data Binding。
2. 与 com.didi.virtualapk:gradle:0.9.8.2及以上版本 搭配使用,支持官方 Data Binding。
## com.didi.virtualapk:core:0.9.6
1. 修复部分空指针问题。
## VirtualAPK 的构建部分已经开源了,![点击这里查看](https://github.com/didi/VirtualAPK/tree/master/virtualapk-gradle-plugin)
......@@ -39,3 +42,8 @@
## com.didi.virtualapk:gradle:0.9.8.3
1. 兼容不定义 productFlavors 的配置。
## com.didi.virtualapk:gradle:0.9.8.4
1. 修复当插件依赖library module时构建失败的bug。
2. 修复依赖本地aar时构建失败的bug。
3. 修复当插件自定义attr属性时id错误的bug。
......@@ -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");
}
}
......
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
......@@ -10,30 +11,32 @@
tools:context="com.didi.virtualapk.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!"
android:id="@+id/textView" />
<Button
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/open_plugin"
android:id="@+id/button"
android:onClick="onButtonClick"
android:layout_below="@+id/textView"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="42dp" />
android:layout_marginTop="10dp" />
<Button
android:text="@string/about"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/button"
android:onClick="onButtonClick"
android:layout_alignParentLeft="true"
android:layout_marginTop="58dp"
android:id="@+id/about"
android:layout_alignRight="@+id/button" />
</RelativeLayout>
android:layout_marginTop="10dp"
android:id="@+id/about" />
<Button
android:text="@string/webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onButtonClick"
android:layout_marginTop="10dp"
android:id="@+id/webview" />
</LinearLayout>
......@@ -2,5 +2,6 @@
<string name="app_name">VirtualAPK-EN</string>
<string name="open_plugin">open plugin</string>
<string name="about">about</string>
<string name="webview">Append WebView</string>
<string name="about_detail">VirtualAPK is a plugin framework powered by DiDi company for Android,see the source code : https://github.com/didi/VirtualAPK</string>
</resources>
\ No newline at end of file
......@@ -2,5 +2,6 @@
<string name="app_name">VirtualAPK</string>
<string name="open_plugin">加载插件</string>
<string name="about">关于</string>
<string name="webview">Append WebView</string>
<string name="about_detail">VirtualAPK 是一款由滴滴出行研发的 Android 插件化框架,项目地址:https://github.com/didi/VirtualAPK</string>
</resources>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册