/** * Copyright (c) 2014 Baidu, Inc. All Rights Reserved. * <p> * 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 * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * 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.baidu.android.gporter.gpt; import android.app.Activity; import android.app.Application; import android.app.Instrumentation; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.os.SystemClock; import android.view.LayoutInflater; import android.view.Window; import com.baidu.android.gporter.ProxyEnvironment; import com.baidu.android.gporter.ProxyPhoneLayoutInflater; import com.baidu.android.gporter.proxy.ProxyUtil; import com.baidu.android.gporter.proxy.WindowCallbackWorker; import com.baidu.android.gporter.proxy.activity.ActivityProxy; import com.baidu.android.gporter.stat.ExceptionConstants; import com.baidu.android.gporter.stat.PluginTimeLine; import com.baidu.android.gporter.stat.ReportManger; import com.baidu.android.gporter.util.Constants; import com.baidu.android.gporter.util.JavaCalls; import com.baidu.android.gporter.util.Util; import java.io.File; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; /** * GPTInstrumentation * * @author liuhaitao * @since 2015年9月22日 */ public class GPTInstrumentation extends Instrumentation { /** * DEBUG 开关 */ public static final boolean DEBUG = true & Constants.DEBUG; /** * TAG */ public static final String TAG = "GPTInstrumentation"; /** * 判断是否是插件。 * 因为这通过PM接口获取pkg信息,我们优先返回插件的,不是插件再通过系统接口返回。 * * @param packageName 包名 * @return true or false */ public static boolean isPlugin(String packageName) { /* * 这里简单处理,如果插件没有被加载,也返回false。只有插件初始化了才认为是插件。同时也有效率方面的考虑。 * 因为运行到这里。插件已经被初始化。可以根据有没有实例进行判断。 */ return ProxyEnvironment.hasInstance(packageName); } @Override public void callApplicationOnCreate(Application app) { onCallApplicationOnCreate(app); long start = SystemClock.elapsedRealtime(); super.callApplicationOnCreate(app); // 统计插件自己Application onCreate的时长 long time = SystemClock.elapsedRealtime() - start; PluginTimeLine timeLine = ProxyEnvironment.pluginTimeLineMap.get(app.getPackageName()); if (timeLine != null) { timeLine.createApplicationTime = time; } ReportManger.getInstance().onCreateApplication(app.getApplicationContext(), app.getPackageName(), time); } /** * onCallApplicationOnCreate * * @param app Application */ private void onCallApplicationOnCreate(Application app) { String packageName = app.getPackageName(); boolean isPlugin = isPlugin(packageName); if (!isPlugin) { return; } // Begin:【FixBug】解决在中兴手机上找不到资源的问题,中兴部分手机的ROM上自己继承ContextImpl实现了一个AppContextImpl, // 里面做一些BaseContext的复用,导致插件获取Resources时可能会取到宿主的。 try { Class<?> clsCtxImpl = Class.forName("android.app.ContextImpl"); Object base = app.getBaseContext(); if (base.getClass() != clsCtxImpl) { Constructor<?> cst = clsCtxImpl.getConstructor(clsCtxImpl); Object impl = cst.newInstance(base); JavaCalls.setField(app, "mBase", impl); } } catch (Exception e) { if (DEBUG) { e.printStackTrace(); } if (ProxyEnvironment.hasInstance(app.getPackageName())) { ReportManger.getInstance().onException( ProxyEnvironment.getInstance(app.getPackageName()).getApplicationProxy(), packageName, Util.getCallStack(e), ExceptionConstants.TJ_78730010); } } replacePluginPackageName2Host(app); replaceSystemServices(app); replaceExternalDirs(app); ProxyUtil.replaceSystemServices(app); } @Override public void callActivityOnCreate(Activity activity, Bundle icicle) { onCallActivityOnCreate(activity); super.callActivityOnCreate(activity, icicle); } @Override public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { onCallActivityOnCreate(activity); super.callActivityOnCreate(activity, icicle, persistentState); } /** * onCallActivityOnCreate * * @param activity Activity */ private void onCallActivityOnCreate(Activity activity) { String packageName = activity.getPackageName(); boolean isPlugin = isPlugin(packageName); if (!isPlugin) { return; } if (ProxyEnvironment.pluginHotStartTimeMap.get(packageName) != null) { long stamp = ProxyEnvironment.pluginHotStartTimeMap.get(packageName); long millis = SystemClock.elapsedRealtime() - stamp; if (stamp > -1 && millis > 0) { ReportManger.getInstance().onPluginHotLoad(activity.getApplicationContext(), packageName, millis); ProxyEnvironment.pluginHotStartTimeMap.remove(packageName); } } replacePluginPackageName2Host(activity); replaceSystemServices(activity); replaceWindow(activity); replaceExternalDirs(activity); // 初始化 activity layoutinflator and localactivity manager Activity parent = activity.getParent(); if (parent != null && parent instanceof ActivityProxy) { ((ActivityProxy) parent).onBeforeCreate(activity); } if (Build.VERSION.SDK_INT < 23 /*Android m 6.0*/) { // bindservice trick begin // 如果在actiivty的 oncreate 中 binder service,token 中的 activity 对象为 null String className = "android.app.LocalActivityManager$LocalActivityRecord"; Class clazz = null; try { clazz = Class.forName(className); } catch (ClassNotFoundException e) { if (DEBUG) { e.printStackTrace(); } } IBinder token = JavaCalls.callMethod(activity.getBaseContext(), "getActivityToken"); if (clazz != null && token != null && token.getClass().equals(clazz)) { Activity a = (Activity) JavaCalls.getField(token, "activity"); if (a == null) { JavaCalls.setField(token, "activity", activity); } } // bindservice trick end } else { // 6.0 以上 Activity.mBase.mActvityToken 一直为 null,不使用也可以工作。 } } /** * 替换自己的LayoutInflator,用于解决setContextView, * 如果插件和主进程在同一个进程,并且两个classloader中有相同的 view class。比如 support v4。 * LayoutInflator 中 sConstructorMap 静态变量,导致的类型转换错误。 * * @param context Context * @param superLayoutInflator superGetSystemService 调用 super.getSystemService 返回的 service * @return LayoutInflater */ private static LayoutInflater getProxyLayoutInflator(Context context, LayoutInflater superLayoutInflator) { LayoutInflater layoutInflator = new ProxyPhoneLayoutInflater(superLayoutInflator, context); // ProxyPhoneLayoutInflater在第一次setFactory时会生成代理Factory // set一个null就是为了生成我们自己的代理Factory,保证在宿主和插件有"控件类"冲突时,能够获取正确的类 —— by chenyangkun layoutInflator.setFactory(null); return layoutInflator; } /** * 替换目标对象的window。 * * @param activity Activity */ private static void replaceWindow(Activity activity) { Activity parent = activity.getParent(); if (parent != null && parent instanceof ActivityProxy) { JavaCalls.setField(activity, "mWindow", parent.getWindow()); } replaceWindowCallback(activity); } /** * 替换WindowCallback * * @param activity Activity */ public static void replaceWindowCallback(Activity activity) { activity.getWindow().setCallback(activity); Window.Callback callback = activity.getWindow().getCallback(); WindowCallbackWorker callbackWorker = new WindowCallbackWorker(); callbackWorker.mTarget = callback; callbackWorker.mActivity = activity; activity.getWindow().setCallback(callbackWorker); } /** * 在必要的地方把插件的包名替换成宿主的 * * @param context application or activity */ public static void replacePluginPackageName2Host(ContextWrapper context) { // OpPackageName调用的地方一般都是aidl接口传递包名给system server,所以要用宿主的包名。 Context baseContext = context.getBaseContext(); String hostPackageName = ProxyEnvironment.getInstance(context.getPackageName()).getHostPackageName(); JavaCalls.setField(baseContext, "mOpPackageName", hostPackageName); // 可能要访问其他应用或者系统应用的ContentProvider,Resolver的包名一定是要宿主的。 ContentResolver resolver = context.getContentResolver(); if (resolver != null) { JavaCalls.setField(resolver, "mPackageName", hostPackageName); } } /** * 替换SystemServices * * @param context Application */ private static void replaceSystemServices(Application context) { String packageName = context.getPackageName(); boolean isPlugin = isPlugin(packageName); if (isPlugin) { // replayce layout inflater Context base = context.getBaseContext(); LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater gptLayoutInflater = getProxyLayoutInflator(context, layoutInflater); if (Build.VERSION.SDK_INT >= 23 /*Android m 6.0*/) { HashMap serviceMap = (HashMap) JavaCalls.getStaticField("android.app.SystemServiceRegistry", "SYSTEM_SERVICE_FETCHERS"); Object serviceFetcher = serviceMap.get(Context.LAYOUT_INFLATER_SERVICE); int cacheIndex = (Integer) JavaCalls.getField(serviceFetcher, "mCacheIndex"); Object[] serviceCache = (Object[]) JavaCalls.getField(base, "mServiceCache"); serviceCache[cacheIndex] = gptLayoutInflater; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { HashMap serviceMap = (HashMap) JavaCalls.getField(base, "SYSTEM_SERVICE_MAP"); Object serviceFetcher = serviceMap.get(Context.LAYOUT_INFLATER_SERVICE); int cacheIndex = (Integer) JavaCalls.getField(serviceFetcher, "mContextCacheIndex"); ArrayList serviceCache = (ArrayList) JavaCalls.getField(base, "mServiceCache"); serviceCache.set(cacheIndex, gptLayoutInflater); } else { // 2.3 以及以下 JavaCalls.setField(base, "mLayoutInflater", gptLayoutInflater); } } } /** * 替换SystemServices * * @param context Activity */ private static void replaceSystemServices(Activity context) { // replayce layout inflater LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); LayoutInflater gptLayoutInflater = getProxyLayoutInflator(context, layoutInflater); // ContextThemeWrapper.mInflater JavaCalls.setField(context, "mInflater", gptLayoutInflater); ProxyUtil.replaceSystemServices(context); } /** * 替换ExternalDirs * * @param context ContextWrapper */ private static void replaceExternalDirs(ContextWrapper context) { Context baseContext = context.getBaseContext(); String packageName = context.getPackageName(); Context hostApplicationContext = ProxyEnvironment.getInstance(packageName).getApplicationProxy(); // external cache dirs File[] externalCacheDirs = getTargetExternalCacheDir(hostApplicationContext, packageName); JavaCalls.setField(baseContext, "mExternalCacheDirs", externalCacheDirs); File[] externalFilesDir = getTargetExternalFilesDir(hostApplicationContext, packageName, null); JavaCalls.setField(baseContext, "mExternalFilesDirs", externalFilesDir); } /** * getTargetExternalCacheDir * android 5.0 由于 AppOps 限制,不能创建插件目录。同时我们也需要把插件的external目录接管过来。 * <p> * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * 注意:该函数用于 GPTActivity 调用,所以不要随便改变函数名字参数等。如需改动,老函数需要保留。 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * * @param hostContext Context * @param pluginPkgName pluginPkgName * @return File[] */ public static File[] getTargetExternalCacheDir(Context hostContext, String pluginPkgName) { // 解决OPPO特殊手机4.4兼容问题 File hostExternalFilesDir; try { hostExternalFilesDir = hostContext.getExternalFilesDir(null); } catch (Exception e) { return null; } File targetExternalCacheDir = Util.buildPath(hostExternalFilesDir, pluginPkgName, "cache"); if (!targetExternalCacheDir.exists()) { targetExternalCacheDir.mkdirs(); } if (!targetExternalCacheDir.exists()) { return null; } else { return new File[]{targetExternalCacheDir}; } } /** * getTargetExternalFilesDir * android 5.0 由于 AppOps 限制,不能创建插件目录。同时我们也需要把插件的external目录接管过来。 * <p> * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * 注意:该函数用于 GPTActivity 调用,所以不要随便改变函数名字参数等。如需改动,老函数需要保留。 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * * @param hostContext Context * @param pluginPkgName pluginPkgName * @param type type * @return File[] */ public static File[] getTargetExternalFilesDir(Context hostContext, String pluginPkgName, String type) { File hostExternalFilesDir = hostContext.getExternalFilesDir(null); File targetExternalFilesDir = Util.buildPath(hostExternalFilesDir, pluginPkgName, "files"); if (!targetExternalFilesDir.exists()) { targetExternalFilesDir.mkdirs(); } if (!targetExternalFilesDir.exists()) { return null; } if (type == null) { return new File[]{targetExternalFilesDir}; } File dir = new File(targetExternalFilesDir, type); if (!dir.exists()) { if (!dir.mkdirs()) { return null; } } return new File[]{dir}; } }