范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

Android插件化技术,你值得一看

  相信大家在工作中会遇到关于Android插件化开发出现的问题,随着对这种技术的了解,我发现在插件开发的一些基础问题上下需要花费更多的时间。就比如在主工程Context和插件Context的区别所在,权限必然要在主工程申明,本着学习的心态,对插件的历史花了些时间进行了解,随后便写了两个Demo进行总结。这篇文章主要是通过对插件的实现原理进行直观的说明,以此来对插件内开发进行更进一步的加深和了解,所以这篇文章也是主要对核心逻辑进行更多的探讨,所以也就不会对其背景和原理进行一个深入的探讨.
  原理和背景
  首先从android插件化的技术起源开始,插件化最初是从免安装运行apk这个方向的一个初步想法从而发展出来的技术,插件也可以称之为一个免安装的apk,app在运行过程中因为其支持插件化,从而可以将aap中一些平常不适应的模块做成我们所需要的插件,以此来实现app中的动态扩展功能,更是进一步减少了其安装包的大小,当然需要实现其插件化,主要是要如何解决这三个问题: 载类与主程序如何进行相互调动 资源的加载如何与主工程进行相互访问 组件的生命周期如何管理
  我将相对来说比较有名的开源插件化框架进行了时间排序,以此来了解它们的原理,如何实现,这样我们就可以看出插件化技术的发展历程,我将其插件化框架进行了分层,根据其实现原理划分很了三代
  第一代:dynamic-load-apk相较于其他插件框架,它最先使用了ProxyActivity静态代理技术,由ProxyActivity进行控制插件PluginActivity生命周期。但是这种方式对的缺点很明显,插件里activity就必须要继承PluginActivity,所以如果要进行开发就必须要仔细处理context。相对而言DroidPlugin就是通过Hook系统服务进行启动该插件中的Activity,这样我们在开发插件的过程与普通的app开发的是一样的,区别不大,但hook会过多的进行系统服务,会显得复杂而且相对而说就不够稳定。
  第二代:VirtualAPK为了跟普通app一样开发插件并且让插件开发达到低侵入性,同时也让其拥有框架的稳定性,所以在实现原理上都会尽量选择比较少的hook,在通过manifest中实现对四大组件的插件化进行一个预埋。而且各个框架都从设计的思想进行了一定程度的扩展,其中更是将Small做成了一个具有跨平台,组件化功能的开发框架。
  第三代:VirtualApp相对于前面两代就比较厉害了,不仅能够app的运行环境进行完全模拟,还能够讲app的免安装运行和双开技术进行实现。Atlas是阿里19年开源出来的一个具有热修复技术和结合组件化的一个app的基础框架,至今仍然应用于阿里系的app中,因此也将其号称为一个容器化框架。
  下面详细介绍插件化框架的原理,分别对应着实现插件化的三个核心问题。 类加载
  从外部apk中类的加载来看,通常在Android中常用的共有两种类加载器,PathClassLoader和DexClassLoader,这两种类加载器都继承于BaseDexClassLoader。
  Android对于外部的dex文件,主要通过 DexClassLoader 类加载,因此,只需要给定插件的路径,就可以构造对应的类加载器: // DexClassLoaderpublic class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory,             String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);     } } // PathClassLoader  public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) {         super(dexPath, null, null, parent);     }        public PathClassLoader(String dexPath, String libraryPath,             ClassLoader parent) {         super(dexPath, null, libraryPath, parent);     } }
  这种区别就是在调用父类构造器时,DexClassLoader会多传了一个optimizedDirectory参数,并且这个目录的存储路径必须是内部存储路径,具体是用来缓存系统创建的Dex文件。而PathClassLoader的参数是为null,所以就只能从内部存储目录中加载Dex文件。
  那么我们就可以使用DexClassLoader拿去加载外部的apk,所使用的方法如下
  //第一个参数为apk的文件目录//第二个参数为内部存储目录//第三个为库文件的存储目录//第四个参数为父加载器new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent) 资源加载
  Android系统是通过Resource对象进行加载资源,下面代码的生成过程展示了该对象
  所以也就说明现在插件Activity中就不会有所限制: //创建AssetManager对象  AssetManager assets = new AssetManager();  //将apk路径添加到AssetManager中   if (assets.addAssetPath(resDir) == 0){                   return null;   }  //创建Resource对象  r = new Resources(assets, metrics, getConfiguration(), compInfo);
  所以,我们只要将插件apk中的路径加入到AssetManager之后,就可以实现访问其插件资源。
  在具体实现时,因为AssetManager跟public并不是一类,所以就需要通过反射让其去创建,而且部分Rom会对其创建的Resource类进行修改,那么我们就有必要考虑不同Rom的兼容性。 生命周期
  在Android进行开发中会有一些特殊的类,全是才系统创建而来,并且会让系统来管理生命周期。
  对生命周期的管理在插件化中是较为复杂的,就如同常用的四大组件,Service,Activity,ContentProvider和BroadcastReceiver。其中插件化中Activity是最为繁琐的,其中早期的dynamic-load-apk所采用的基本上是用代理的方式,主要是通过使用一个空壳的Activity让其成为代理(Proxy),系统会将Activity的回调都一一映射到这个Activity插件,这样的话就可以将插件的生命周期实现通过系统来进行管理,不得不说这种方式及其直观,但其所拥有的插件Activity需要继承这样代理的PluginActivity (Demo中的命名),不仅拥有侵入性强的特点,更可以结合我们后面的例子并加以理解。 代理实现
  首先我们将建立一个 PluginManager 类从而来实现插件的加载: public class PluginManager {     static class PluginMgrHolder {         static PluginManager sManager = new PluginManager();     }      private static Context mContext;      Map sMap = new HashMap<>();      public static PluginManager getInstance() {         return PluginMgrHolder.sManager;     }     public PluginApk getPluginApk(String packageName) {         return sMap.get(packageName);     }      public static void init(Context context) {         mContext = context.getApplicationContext();     }      public final void loadApk(String apkPath) {         PackageInfo packageInfo = queryPackageInfo(apkPath);         if (packageInfo == null || TextUtils.isEmpty(packageInfo.packageName)) {             return;         }         // check cache         PluginApk pluginApk = sMap.get(packageInfo.packageName);          if (pluginApk == null) {             pluginApk = createApk(apkPath);             if (pluginApk != null) {                 pluginApk.packageInfo = packageInfo;                 sMap.put(packageInfo.packageName, pluginApk);             } else {                 throw new NullPointerException("PluginApk is null");             }         }     } ```  ```  private PluginApk createApk(String apkPath) {         String addAssetPathMethod = "addAssetPath";         PluginApk pluginApk = null;         try {             AssetManager assetManager = AssetManager.class.newInstance();             Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);             addAssetPath.invoke(assetManager, apkPath);             Resources pluginRes = new Resources(assetManager,                     mContext.getResources().getDisplayMetrics(),                     mContext.getResources().getConfiguration());             pluginApk = new PluginApk(pluginRes);             pluginApk.classLoader = createDexClassLoader(apkPath);         } catch (IllegalAccessException                 | InstantiationException                 | NoSuchMethodException                 | InvocationTargetException e) {             e.printStackTrace();         }         return pluginApk;     } ``` ```  private PackageInfo queryPackageInfo(String apkPath) {         PackageInfo packageInfo = mContext.getPackageManager().getPackageArchiveInfo(apkPath,                 PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES);         if (packageInfo == null) {             return null;         }         return packageInfo;     } ``` private DexClassLoader createDexClassLoader(String apkPath) {         File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);         DexClassLoader loader = new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),                 null, mContext.getClassLoader());         return loader;     } ``` ``` public void startActivity(Intent intent) {         Intent pluginIntent = new Intent(mContext, ProxyActivity.class);         Bundle extra = intent.getExtras();         // complicate if statement         if (extra == null || !extra.containsKey(Constants.PLUGIN_CLASS_NAME) && !extra.containsKey(Constants.PACKAGE_NAME)) {             try {                 throw new IllegalAccessException("lack class of plugin and package name");             } catch (IllegalAccessException e) {                 e.printStackTrace();             }         }         pluginIntent.putExtras(intent);         pluginIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);         mContext.startActivity(pluginIntent);     } } ```
  PluginApk 表示一个Apk文件: public class PluginApk {     public PackageInfo packageInfo;     public DexClassLoader classLoader;     public Resources pluginRes;      public PluginApk(Resources pluginRes) {         this.pluginRes = pluginRes;     }  } ```
  插件Activity的所以都必须继承一个父类 PluginActivity : public abstract class PluginActivity extends Activity implements Pluginable, Attachable {     public final static String TAG = PluginActivity.class.getSimpleName();     protected Activity mProxyActivity;     private Resources mResources;     PluginApk mPluginApk;      @Override     public void attach(Activity proxy, PluginApk apk) {         mProxyActivity = proxy;         mPluginApk = apk;         mResources = apk.pluginRes;     }      @Override     public void setContentView(int layoutResID) {         mProxyActivity.setContentView(layoutResID);     }      @Override     public void setContentView(View view) {         mProxyActivity.setContentView(view);     }      @Override     public void setContentView(View view, ViewGroup.LayoutParams params) {         mProxyActivity.setContentView(view, params);     }       @Override     public View findViewById(int id) {         return mProxyActivity.findViewById(id);     }      @Override     public Resources getResources() {         return mResources;     }      @Override     public WindowManager getWindowManager() {         return mProxyActivity.getWindowManager();     }      @Override     public ClassLoader getClassLoader() {         return mProxyActivity.getClassLoader();     }      @Override     public Context getApplicationContext() {         return mProxyActivity.getApplicationContext();     }      @Override     public MenuInflater getMenuInflater() {         return mProxyActivity.getMenuInflater();     }       @Override     public Window getWindow() {         return mProxyActivity.getWindow();     }      @Override     public Intent getIntent() {         return mProxyActivity.getIntent();     }      @Override     public LayoutInflater getLayoutInflater() {         return mProxyActivity.getLayoutInflater();     }      @Override     public String getPackageName() {         return mPluginApk.packageInfo.packageName;     }       @Override     public void onCreate(Bundle bundle) {         // DO NOT CALL super.onCreate(bundle)         // following same         VLog.log(TAG + ": onCreate");     }      @Override     public void onStart() {      }      @Override     public void onResume() {      }      @Override     public void onStop() {      }      @Override     public void onPause() {      }      @Override     public void onDestroy() {     } } ```
  这个类相当于一个壳,系统将通过 ProxyActivity 能够触发对应的方法进行具体的实现: public class ProxyActivity extends Activity {     LifeCircleController mPluginController = new LifeCircleController(this);      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         mPluginController.onCreate(getIntent().getExtras());     }      @Override     public Resources getResources() {         // construct when loading apk         Resources resources = mPluginController.getResources();         return resources == null ? super.getResources() : resources;     }      @Override     public Resources.Theme getTheme() {         Resources.Theme theme = mPluginController.getTheme();         return theme == null ? super.getTheme() : theme;     }      @Override     public AssetManager getAssets() {         return mPluginController.getAssets();     }       @Override     protected void onStart() {         super.onStart();         mPluginController.onStart();     }      @Override     protected void onResume() {         super.onResume();         mPluginController.onResume();     }      @Override     protected void onStop() {         super.onStop();         mPluginController.onStop();     }      @Override     protected void onPause() {         super.onPause();         mPluginController.onPause();     }      @Override     protected void onDestroy() {         super.onDestroy();         mPluginController.onDestroy();     } } ```
  这个类是系统能够实际启动的类,其中最主要逻辑由 LifeCircleController对其负责: public class LifeCircleController implements Pluginable {     Activity mProxy;     PluginActivity mPlugin;     Resources mResources;     Resources.Theme mTheme;     PluginApk mPluginApk;     String mPluginClazz;      public LifeCircleController(Activity activity) {         mProxy = activity;     }      public void onCreate(Bundle bundle) {         mPluginClazz = bundle.getString(Constants.PLUGIN_CLASS_NAME);         String packageName = bundle.getString(Constants.PACKAGE_NAME);         mPluginApk = PluginManager.getInstance().getPluginApk(packageName);         try {             mPlugin = (PluginActivity) loadPluginable(mPluginApk.classLoader, mPluginClazz);             mPlugin.attach(mProxy, mPluginApk);             mResources = mPluginApk.pluginRes;             mPlugin.onCreate(bundle);         } catch (Exception e) {             VLog.log("Fail in LifeCircleController onCreate");             VLog.log(e.getMessage());             e.printStackTrace();         }      }     private Object loadPluginable(ClassLoader classLoader, String pluginActivityClass)             throws Exception {         Class<?> pluginClz = classLoader.loadClass(pluginActivityClass);         Constructor<?> constructor = pluginClz.getConstructor(new Class[] {});         constructor.setAccessible(true);         return constructor.newInstance(new Object[] {});     }      @Override     public void onStart() {         if (mPlugin != null) {             mPlugin.onStart();         }     } ``` ``` @Override     public void onResume() {         if (mPlugin != null) {             mPlugin.onResume();         }     }      @Override     public void onStop() {         mPlugin.onStop();     }      @Override     public void onPause() {         mPlugin.onPause();     }      @Override     public void onDestroy() {         mPlugin.onDestroy();     }      public Resources getResources() {         return mResources;     }      public Resources.Theme getTheme() {         return mTheme;     }      public AssetManager getAssets() {         return mResources.getAssets();     }  } ```
  这个看起来有点像Activity源码所拥有的的外观模式,而且内部的职责和分工划分是使用者中不可见的。
  我们在最后在主工程进行启动插件: Intent intent = new Intent(); intent.putExtra(Constants.PACKAGE_NAME, PLUGIN_PACKAGE_NAME); intent.putExtra(Constants.PLUGIN_CLASS_NAME, PLUGIN_CLAZZ_NAME); mPluginManager.startActivity(intent);
  通过插件Activity如下: public class MainActivity extends PluginActivity {      @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         setTitle("Plugin App");         ((ImageView) findViewById(R.id.iv_logo)).setImageResource(R.drawable.android);     } } ```效果
  可以看出Hook已经实现
  当然Hook的方式只需要了解系统启动一个基本Activity的过程,但对于系统来说会先检查Activity是否注册,所以再去生成Activity,这样的话我们只需要在检查的过程中使用一个已经注册过的Activity(桩,通常表示为StubActivity)从而让系统进行一个检查,检查通过之后再生成的时候再可以替换成插件,这样可以了。
  所以要通过自己实现一个 Instrumentation ,要在里面使用一些替换工作,然后再去Hook掉系统所持有的对象: public class HookedInstrumentation extends Instrumentation implements Handler.Callback {     public static final String TAG = "HookedInstrumentation";     protected Instrumentation mBase;     private PluginManager mPluginManager;      public HookedInstrumentation(Instrumentation base, PluginManager pluginManager) {         mBase = base;         mPluginManager = pluginManager;     }      /**      * 覆盖掉原始Instrumentation类的对应方法,用于插件内部跳转Activity时适配      *      * @Override      */     public ActivityResult execStartActivity(             Context who, IBinder contextThread, IBinder token, Activity target,             Intent intent, int requestCode, Bundle options) {          if (Constants.DEBUG) Log.e(TAG, "execStartActivity");         mPluginManager.hookToStubActivity(intent);          try {             Method execStartActivity = Instrumentation.class.getDeclaredMethod(                     "execStartActivity", Context.class, IBinder.class, IBinder.class,                     Activity.class, Intent.class, int.class, Bundle.class);             execStartActivity.setAccessible(true);             return (ActivityResult) execStartActivity.invoke(mBase, who,                     contextThread, token, target, intent, requestCode, options);         } catch (Exception e) {             e.printStackTrace();             throw new RuntimeException("do not support!!!" + e.getMessage());         }     } ``` @Override     public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {         if (Constants.DEBUG) Log.e(TAG, "newActivity");          if (mPluginManager.hookToPluginActivity(intent)) {             String targetClassName = intent.getComponent().getClassName();             PluginApp pluginApp = mPluginManager.getLoadedPluginApk();             Activity activity = mBase.newActivity(pluginApp.mClassLoader, targetClassName, intent);             activity.setIntent(intent);             ReflectUtil.setField(ContextThemeWrapper.class, activity, Constants.FIELD_RESOURCES, pluginApp.mResources);             return activity;         }          if (Constants.DEBUG) Log.e(TAG, "super.newActivity(...)");         return super.newActivity(cl, className, intent);     }      @Override     public boolean handleMessage(Message message) {         if (Constants.DEBUG) Log.e(TAG, "handleMessage");         return false;     }       @Override     public void callActivityOnCreate(Activity activity, Bundle icicle) {         if (Constants.DEBUG) Log.e(TAG, "callActivityOnCreate");         super.callActivityOnCreate(activity, icicle);     } } ```
  启动过程中负责的 execStartActivity 让其设置成启动已注册的Activity,然后在 newActivity的设置中为其实际过程要启动的插件的Activity。最后去Hook系统持有该字段: public class ReflectUtil {     public static final String METHOD_currentActivityThread = "currentActivityThread";     public static final String CLASS_ActivityThread = "android.app.ActivityThread";     public static final String FIELD_mInstrumentation = "mInstrumentation";     public static final String TAG = "ReflectUtil";       private static Instrumentation sInstrumentation;     private static Instrumentation sActivityInstrumentation;     private static Field sActivityThreadInstrumentationField;     private static Field sActivityInstrumentationField;     private static Object sActivityThread;      public static boolean init() {         //获取当前的ActivityThread对象         Class<?> activityThreadClass = null;         try {             activityThreadClass = Class.forName(CLASS_ActivityThread);             Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod(METHOD_currentActivityThread);             currentActivityThreadMethod.setAccessible(true);             Object currentActivityThread = currentActivityThreadMethod.invoke(null);               //拿到在ActivityThread类里面的原始mInstrumentation对象             Field instrumentationField = activityThreadClass.getDeclaredField(FIELD_mInstrumentation);             instrumentationField.setAccessible(true);             sActivityThreadInstrumentationField = instrumentationField;              sInstrumentation = (Instrumentation) instrumentationField.get(currentActivityThread);             sActivityThread = currentActivityThread;               sActivityInstrumentationField =  Activity.class.getDeclaredField(FIELD_mInstrumentation);             sActivityInstrumentationField.setAccessible(true);             return true;         } catch (ClassNotFoundException                 | NoSuchMethodException                 | IllegalAccessException                 | InvocationTargetException                 | NoSuchFieldException e) {             e.printStackTrace();         }          return false;     }      public static Instrumentation getInstrumentation() {         return sInstrumentation;     }      public static Object getActivityThread() {         return sActivityThread;     }      public static void setInstrumentation(Object activityThread, HookedInstrumentation hookedInstrumentation) {         try {             sActivityThreadInstrumentationField.set(activityThread, hookedInstrumentation);             if (Constants.DEBUG) Log.e(TAG, "set hooked instrumentation");         } catch (IllegalAccessException e) {             e.printStackTrace();         }     }      public static void setActivityInstrumentation(Activity activity, PluginManager manager) {         try {             sActivityInstrumentation = (Instrumentation) sActivityInstrumentationField.get(activity);             HookedInstrumentation instrumentation = new HookedInstrumentation(sActivityInstrumentation, manager);             sActivityInstrumentationField.set(activity, instrumentation);             if (Constants.DEBUG) Log.e(TAG, "set activity hooked instrumentation");         } catch (IllegalAccessException e) {             e.printStackTrace();         }     }       public static void setField(Class clazz, Object target, String field, Object object) {         try {             Field f = clazz.getDeclaredField(field);             f.setAccessible(true);             f.set(target, object);         } catch (Exception e) {             e.printStackTrace();         }      }  } ```
  PluginManager 也是负责加载插件的资源和类这一块 public class PluginManager {     private final static String TAG = "PluginManager";     private static PluginManager sInstance;     private Context mContext;     private PluginApp mPluginApp;       public static PluginManager getInstance(Context context) {         if (sInstance == null && context != null) {             sInstance = new PluginManager(context);         }         return sInstance;     }      private PluginManager(Context context) {         mContext = context;     }       public void hookInstrumentation() {         try {             Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation();              final HookedInstrumentation instrumentation = new HookedInstrumentation(baseInstrumentation, this);              Object activityThread = ReflectUtil.getActivityThread();             ReflectUtil.setInstrumentation(activityThread, instrumentation);         } catch (Exception e) {             e.printStackTrace();         }     } ``` ``` public void hookCurrentActivityInstrumentation(Activity activity) {         ReflectUtil.setActivityInstrumentation(activity, sInstance);     }       public void hookToStubActivity(Intent intent) {         if (Constants.DEBUG) Log.e(TAG, "hookToStubActivity");          if (intent == null || intent.getComponent() == null) {             return;         }         String targetPackageName = intent.getComponent().getPackageName();         String targetClassName = intent.getComponent().getClassName();          if (mContext != null                 && !mContext.getPackageName().equals(targetPackageName)                 && isPluginLoaded(targetPackageName)) {             if (Constants.DEBUG) Log.e(TAG, "hook " +  targetClassName + " to " + Constants.STUB_ACTIVITY);              intent.setClassName(Constants.STUB_PACKAGE, Constants.STUB_ACTIVITY);             intent.putExtra(Constants.KEY_IS_PLUGIN, true);             intent.putExtra(Constants.KEY_PACKAGE, targetPackageName);             intent.putExtra(Constants.KEY_ACTIVITY, targetClassName);         }     } ``` ``` public boolean hookToPluginActivity(Intent intent) {         if (Constants.DEBUG) Log.e(TAG, "hookToPluginActivity");         if (intent.getBooleanExtra(Constants.KEY_IS_PLUGIN, false)) {             String pkg = intent.getStringExtra(Constants.KEY_PACKAGE);             String activity = intent.getStringExtra(Constants.KEY_ACTIVITY);             if (Constants.DEBUG) Log.e(TAG, "hook " + intent.getComponent().getClassName() + " to " + activity);             intent.setClassName(pkg, activity);             return true;         }         return false;     } ``` ```  private boolean isPluginLoaded(String packageName) {         // TODO 检查packageNmae是否匹配         return mPluginApp != null;     } ``` ```  public PluginApp loadPluginApk(String apkPath) {         String addAssetPathMethod = "addAssetPath";         PluginApp pluginApp = null;         try {             AssetManager assetManager = AssetManager.class.newInstance();             Method addAssetPath = assetManager.getClass().getMethod(addAssetPathMethod, String.class);             addAssetPath.invoke(assetManager, apkPath);             Resources pluginRes = new Resources(assetManager,                     mContext.getResources().getDisplayMetrics(),                     mContext.getResources().getConfiguration());             pluginApp = new PluginApp(pluginRes);             pluginApp.mClassLoader = createDexClassLoader(apkPath);         } catch (IllegalAccessException                 | InstantiationException                 | NoSuchMethodException                 | InvocationTargetException e) {             e.printStackTrace();         }         return pluginApp;     } ``` private DexClassLoader createDexClassLoader(String apkPath) {         File dexOutputDir = mContext.getDir("dex", Context.MODE_PRIVATE);         return new DexClassLoader(apkPath, dexOutputDir.getAbsolutePath(),                 null, mContext.getClassLoader());      }      public boolean loadPlugin(String apkPath) {         File apk = new File(apkPath);         if (!apk.exists()) {             return false;         }         mPluginApp = loadPluginApk(apkPath);         return mPluginApp != null;     }      public PluginApp getLoadedPluginApk() {         return mPluginApp;     } } ```
  在MainActivity中进行初始化,并且注意Hook的时机: public class MainActivity extends Activity implements View.OnClickListener {     // https://zhuanlan.zhihu.com/p/33017826      public static final boolean DEBUG = true;     public static final String TAG = "MainActivity";      private String mPluginPackageName = "top.vimerzhao.image";     private String mPluginClassName = "top.vimerzhao.image.MainActivity";      //读写权限     private static String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE,             Manifest.permission.WRITE_EXTERNAL_STORAGE};     //请求状态码     private static int REQUEST_PERMISSION_CODE = 1;      private PluginManager mPluginManager;       @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);          initData();         initView();         initPlugin();     }       private void initPlugin() {         // !! must first         ReflectUtil.init();          mPluginManager = PluginManager.getInstance(getApplicationContext());         mPluginManager.hookInstrumentation();         mPluginManager.hookCurrentActivityInstrumentation(this);     }      private void initData() {         if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {             if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {                 ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);             }         }     }      private void initView() {         (findViewById(R.id.tv_launch)).setOnClickListener(this);     }      @Override     protected void attachBaseContext(Context newBase) {         super.attachBaseContext(newBase);         // !!! 不要在此Hook,看源码发现mInstrumentaion会在此方法后初始化     }      @Override     public void onClick(View view) {         if (Constants.DEBUG) Log.e(TAG, "click view id: " + view.getId());         if (view.getId() == R.id.tv_launch) {             // TODO launch plugin app             if (mPluginManager.loadPlugin(Constants.PLUGIN_PATH)) {                 Intent intent = new Intent();                 intent.setClassName(mPluginPackageName, mPluginClassName);                 startActivity(intent);             }         }      } } ```
  证明插件Activity现在将不会有任何的限制: public class MainActivity extends AppCompatActivity {      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);      }      @Override     protected void onResume() {         super.onResume();      } } ```
  效果和上图类似。
  参考 1.Android插件化:从入门到放弃(http://www.infoq.com/cn/articles/android-plug-ins-from-entry-to-give-up)
  2.Android apk动态加载机制的研究 (http://blog.csdn.net/singwhatiwanna/article/details/22597587)
  3.Android插件化原理解析系列文章 (http://weishu.me/2016/01/28/understand-plugin-framework-overview/)
  4.深度 | 滴滴插件化方案 VirtualApk 源码解析
  5.VirtualAPK资源加载原理解析 (https://www.notion.so/VirtualAPK-1fce1a910c424937acde9528d2acd537)
  如果您觉得我们的内容还不错,可以在评论区下方留言或者私信我

大数据获客软件,精准营销该如何做,对企业有多重要伴随着信息时代的到来,大数据营销变成了企业网络营销宣传策划的有效途径,必要性显而易见。当今的大数据营销在经过了刚崛起时的风潮后,碰到了驱使发展趋势止步不前的窘境,普遍现象通讯荒岛的云鲸大数据,大数据营销,在获客中有何优势互联网营销,伴随着数据生活家居的普及化,全世界的信息内容总产量正展现爆发式提高。根据这一发展趋势以上的,是互联网大数据云计算技术等新理念和新方式的普遍盛行,他们毫无疑问正带领着新一代账行业如何获客,大数据获客软件,未来趋势有别于过去,近些年经济销售市场波澜起伏直接致使许多公司手足无措,为了更好地处理眼下的吃饱难题,愈来愈多的财税咨询公司从传统的的项目进行方式逐渐转更为方便快捷智能化的扩展方式。这一行运营商大数据获客,电信大数据获客软件,应用有哪些要了解整体实力的身后是强有力的基础性支持能力,做为新基建的关键内容,云数据中心基本建设是中国电信全面推行云改数转发展战略的主要支撑点能量。近些年,中国电信持续增加对云数据中心的搭建企业的灵魂,运营商大数据,有哪些关键作用大数据并不是大,而是有效合理。实用价值成份和发掘成本费用比数量十分关键。针对很多领域而言,怎样运用运营商大数据获客方法读取数据信息内容早已成为了市场需求的关键构成部分。一运营商大数保险销售从业人员,怎样获取客源,从而提高客户质量大家都知道,保险业是最先运用客服中心开展规模性市场营销管理的领域之一。伴随着项目经营规模的发展趋势,原来的服务平台不能符合业务需求,需要更新换代。而传统式的保险电销方式中,人力电销叮咚!国际海运中集装箱这两种成本的性质你都了解吗?那么今天华富康供应链小编就带大家了解一下什么是整箱和拼箱。在集装箱运输业务中,我们把一个集装箱一个出口人一个收货人一个目的港,满足这四个一条件的货物叫做整箱货,而把一个集装箱出口人迅速扩散!巴西邮政工人大罢工!物流货运受影响,包裹派送将延误据华富康供应链了解,此前巴西邮政的近10万名员工罢工已持续近两周,从而导致大量邮件快递堆积,派送延误!其主要原因是反对政府提出的将巴西邮政公司私有化抱怨在新冠疫情中员工的健康问题遭速看!关于规范外集卡拖运危险品货物进出堆场业务的通知据华富康供应链了解,日前冠东公司发布了一篇关于规范外集卡拖运危险品货物进出堆场业务的通知。该通知的具体内容如下。各位客户为保证外集卡拖运危险品货物的作业安全,进一步加强危险品非剧毒重点干货,一文搞懂提前申报,必收藏!伴随着提前申报方式的改革进步,提前申报已经成为企业报关时经常选择的方式。企业采取提前申报方式办理通关手续,提前办理单证审核和货物运输作业,非布控查验货物抵达口岸后即可放行提离,企业重磅!缺箱爆仓!多家船公司采取了这些新举措由于今年受疫情原因,现如今从中国出口的柜量慢慢在增加,国外进口到中国的柜量逐渐减少,导致部分船公司出现了国外返程空船亏载的情况。1hr部分船公司已发出缺箱调整通知赫伯罗特中国内地空
阿里巴巴进货可靠吗?阿里巴巴作为全球领先的电商企业,旗下的进货网1688也是全球顶尖产品的交易市场。但是,每年的3。15打假阿里巴巴都被点名。对此很多人就开始担忧阿里巴巴进货可靠吗?在阿里巴巴进货有没巴黎欧莱雅就面膜事件致歉将提出能针对所有相关消费者的妥善解决方案11月18日凌晨,巴黎欧莱雅就面膜事件发布致歉声明称,经初步调查,此次出现部分消费者在预售后以较低价格拍下商品,是因为叠加使用了多种平台及店铺的优惠。对于此次因过于繁琐复杂的销售机非典型理工男深耕行业13年陈博让救援英雄成为游戏主角非典型理工男深耕行业13年陈博让救援英雄成为游戏主角华西都市报封面新闻记者朱珠热爱冒险,喜欢独处,遇事沉着冷静,这个非典型理工男陈博是一家游戏公司的创始人,而他做的游戏都和救援英雄天玑9000芯片到底是不是华为的麒麟芯片改名的?我看有些人是魔怔了,居然会有天玑9000是华为麒麟芯片改名的想法,当然有些人估计是觉得天玑9000如果到时候表现好,那就是华为出手的缘故,然后又可以沸腾一番,可是这是不可能的,天玑光羿科技第三代电致变色技术,控光与控热的密钥近年来,全球工业科技领域出现了一种新型材料电致变色材料。业内人士指出,电致变色材料是目前有研究和应用前景的智能材料之一,其在学术界已活跃了近半个世纪,虽拥有巨大商业前景。而将电致变比特币白皮书的核心思想是什么,能否做些简洁的概括?比特币的定义,就是点对点的电子现金系统,所谓p2p也就是pointtopoint的技术,点对点不通过任何金融机构,从而实现交易。说到电子货币的交易,以往的交易模式的弊病,是不能防止在执行死刑前,若研制出芯片,可以免除死刑吗?科幻片看多了吧?你先明白什么是芯片,怎么制造出来的,然后你在牢房里带着重型犯人的脚链和手链,机器设备没有,怎么造出芯片???这操作在中国是完全可以的,不一定要芯片而且不一定要真自己如何做好网站建设?在大多情况下,我们在设计网站页面的时候,很多人都关注导航栏和内容的布局,而网站页面底部通常都是被忽略的。事实上,网站页面底部也是整个网站的重要组成部分,用户也可以从页面底部获取想要贾跃亭FF公司Q3巨亏近18亿!被指财务欺诈启动调查,公司称明年7月交付首批FF91中国经济周刊经济网讯据媒体报道,11月15日,FaradayFuture(法拉第未来)向美国证券交易委员会提交的报告显示,因为调查尚未完成,所以无法完整披露三季度财报。但分享了其第37岁把公司卖给马云,套现300亿全部用来造车,何小鹏究竟有多牛文鉴史人编辑鉴史人37岁将公司卖给马云,到手300亿,创造互联网史上金额最大并购案的壮举。而不甘舒适的他,选择砸出全部身价再次创业,赌上前半生赚的功与名,横着膀子开始造汽车。他的传特斯拉没有在中国内地使用星链服务的计划通信1特斯拉没有在中国内地使用星链服务的计划近日,记者从特斯拉中国获悉,目前特斯拉在中国内地没有使用星链服务的计划,所有中国内地的车辆及充电站,均使用中国通讯运营商提供的网络服务,