位置:首页 » 技术 » Android 插件化原理解析——插件加载机制

Android 插件化原理解析——插件加载机制

日期:2016-04-05 阅读:0num
Advertisement

上文Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务;通过Hook AMS 和拦截ActivityThread中 H 类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制。

但是我们启动的『没有在AndroidManifet.xml中显式声明』的Activity和宿主程序存在于同一个Apk中;但通常情况下,插件均以独立的文件存在甚至通过网络获取,这时候插件中的Activity能否成功启动呢?

要启动Activity组件肯定先要创建对应的Activity类的对象,从上文Activity生命周期管理 知道,创建Activity类对象的过程如下:

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();activity = mInstrumentation.newActivity(        cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);

也就是说,系统通过 ClassLoader 加载了需要的Activity类并通过反射创建出了Activity对象。如果Activity组件存在于独立于宿主程序的文件之中,系统的ClassLoader怎么知道去哪里加载呢?因此,如果不做额外的处理,插件中的Activity对象甚至都没有办法创建出来,谈何启动?

因此,要使存在于独立文件或者网络中的插件被成功启动,首先就需要解决这个 插件类加载 的问题。

下文将围绕此问题展开,完成『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务。

阅读本文之前,可以先clone一份 understand-plugin-framework ,参考此项目的 classloader-hook 模块。另外,插件框架原理解析系列文章见索引。

ClassLoader机制

或许有的童鞋还不太了解Java的ClassLoader机制,我这里简要介绍一下。

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校检、转换解析和初始化的,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。与那些在编译时进行链连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,Java里天生可以同代拓展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。例如,如果编写一个面相接口的应用程序,可以等到运行时在制定实际的实现类;用户可以通过Java与定义的和自定义的类加载器,让一个本地的应用程序可以在运行时从网络或其他地方加载一个二进制流作为代码的一部分,这种组装应用程序的方式目前已经广泛应用于Java程序之中。从最基础的Applet,JSP到复杂的OSGi技术,都使用了Java语言运行期类加载的特性。

Java的类加载是一个相对复杂的过程;它包括加载、验证、准备、解析和初始化五个阶段;对于开发者来说,可控性最强的是 加载阶段 ;加载阶段主要完成三件事:

  1. 根据一个类的全限定名来获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为JVM方法区中的运行时数据结构
  3. 在内存中声称一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口。

『通过一个类的全限定名获取描述次类的二进制字节流』这个过程被抽象出来,就是Java的类加载器模块,也即JDK中ClassLoader API。

Android Framework提供了DexClassLoader这个类,简化了『通过一个类的全限定名获取描述次类的二进制字节流』这个过程;我们只需要告诉DexClassLoader一个dex文件或者apk文件的路径就能完成类的加载。因此本文的内容用一句话就可以概括:

将插件的dex或者apk文件告诉『合适的』DexClassLoader,借助它完成插件类的加载

关于CLassLoader机制更多的内容,请参阅『深入理解Java虚拟机』这本书。

思路分析

Android系统使用了ClassLoader机制来进行Activity等组件的加载;apk被安装之后,APK文件的代码以及资源会被系统存放在固定的目录(比如/data/app/ /base-1.apk )系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类;但是系统并不知道存在于插件中的Activity组件的信息(插件可以是任意位置,甚至是网络,系统无法提前预知),因此正常情况下系统无法加载我们插件中的类;因此也没有办法创建Activity的对象,更不用谈启动组件了。

解决这个问题有两个思路,要么全盘接管这个类加载的过程;要么告知系统我们使用的插件存在于哪里,让系统帮忙加载;这两种方式或多或少都需要 干预 这个类加载的过程。老规矩,知己知彼,百战不殆。我们首先分析一下,系统是如果完成这个类加载过程的。

我们再次搬出Activity的创建过程的代码:

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();activity = mInstrumentation.newActivity(        cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);

这里可以很明显地看到,系统通过待启动的Activity的类名 className ,然后使用ClassLoader对象 cl 把这个类加载进虚拟机,最后使用反射创建了这个Activity类的实例对象。要想干预这个ClassLoader(告知它我们的路径或者替换他),我们首先得看看这玩意到底是个什么来头。(从哪里创建的)

cl 这个ClasssLoader对象通过 r.packageInfo 对象的getClassLoader()方法得到,r.packageInfo是一个LoadedApk类的对象;那么,LoadedApk到底是个什么东西??

我们查阅LoadedApk类的文档,只有一句话,不过说的很明白:

Local state maintained about a currently loaded .apk.

LoadedApk对象是APK文件在内存中的表示。Apk文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的Activity,Service等组件的信息我们都可以通过此对象获取。

OK, 我们知道这个LoadedApk是何方神圣了;接下来我们要搞清楚的是:这个 r.packageInfo 到底是从哪里获取的?

我们顺着 performLaunchActivity上溯,辗转handleLaunchActivity回到了 H 类的 LAUNCH_ACTIVITY消息,找到了 r.packageInfo 的来源:

final ActivityClientRecord r = (ActivityClientRecord) msg.obj;r.packageInfo = getPackageInfoNoCheck(        r.activityInfo.applicationInfo, r.compatInfo);handleLaunchActivity(r, null);

getPackageInfoNoCheck方法很简单,直接调用了getPackageInfo方法:

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,        CompatibilityInfo compatInfo) {    return getPackageInfo(ai, compatInfo, null, false, true, false);}

在这个getPackageInfo方法里面我们发现了端倪:

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,        boolean registerPackage) {        // 获取userid信息    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));    synchronized (mResourcesManager) {    // 尝试获取缓存信息        WeakReference<LoadedApk> ref;        if (differentUser) {            // Caching not supported across users            ref = null;        } else if (includeCode) {            ref = mPackages.get(aInfo.packageName);        } else {            ref = mResourcePackages.get(aInfo.packageName);        }

LoadedApk packageInfo = ref != null ? ref.get() : null;        if (packageInfo == null || (packageInfo.mResources != null                && !packageInfo.mResources.getAssets().isUpToDate())) {                // 缓存没有命中,直接new            packageInfo =                new LoadedApk(this, aInfo, compatInfo, baseLoader,                        securityViolation, includeCode &&                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

// 省略。。更新缓存        return packageInfo;    }}

这个方法很重要,我们必须弄清楚每一步;

首先,它判断了调用方和或许App信息的一方是不是同一个userId;如果是同一个user,那么可以共享缓存数据(要么缓存的代码数据,要么缓存的资源数据)

接下来尝试获取缓存数据;如果没有命中缓存数据,才通过LoadedApk的构造函数创建了LoadedApk对象;创建成功之后,如果是同一个uid还放入了缓存。

提到缓存数据,看过 Hook机制之Binder Hook 的童鞋可能就知道了,我们之前成功使用ServiceManager的本地代理使用缓存的机制Hook了各种Binder;因此这里完全可以如法炮制——我们拿到着一份缓存数据,修改里面的ClassLoader;自己控制类加载的过程,这样加载插件中的Activity类的问题就解决了。这就引出了我们加载插件类的第一种方案:

激进方案:Hook掉ClassLoader,自己操刀

从上述分析中我们得知,在获取LoadedApk的过程中使用了一份缓存数据;这个缓存数据是一个 Map ,从包名到LoadedAPk的一个映射。正常情况下,我们的插件肯定不会存在于这个对象里面;但是 如果我们手动把我们插件的信息添加到里面呢? 系统在查找缓存的过程中,会直接命中缓存!进而使用我们添加进去的LoadedApk的ClassLoader来加载这个特定的Activity类!这样我们就能接管我们自己插件类的加载过程了!

但是,细心的读者可能会发现;缓存命中还有一个条件——UID相同,因此我们需要 共享UID

这个缓存对象 mPackages 存在于ActivityThread类中;老方法,我们首先获取这个对象:

// 先获取到当前的ActivityThread对象Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");currentActivityThreadMethod.setAccessible(true);Object currentActivityThread = currentActivityThreadMethod.invoke(null);

// 获取到 mPackages 这个静态成员变量, 这里缓存了dex包的信息Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");mPackagesField.setAccessible(true);Map mPackages = (Map) mPackagesField.get(currentActivityThread);

拿到这个Map之后接下来怎么办呢? 我们需要填充这个map,把插件的信息塞进这个map里面 ,以便系统在查找的时候能命中缓存。但是这个填充这个Map我们出了需要包名之外,还需要一个LoadedApk对象;如何创建一个LoadedAPk对象呢?

我们当然可以直接反射调用它的构造函数直接创建出需要的对象,但是万一哪里有疏漏,构造参数填错了怎么办?又或者Android的不同版本使用了不同的参数,导致我们创建出来的对象与系统创建出的对象不一致,无法work怎么办?

因此我们需要使用与系统完全相同的方式创建Loadedapk对象;从上文分析得知,系统创建LoadedApk对象是通过 getPackageInfo 来完成的,因此我们可以调用这个函数来创建LoadedApk对象;但是这个函数是 private 的,我们无法使用。

有的童鞋可能会有疑问了, private 不是也能反射到吗?我们确实能够调用这个函数,但是 private 表明这个函数是内部实现,或许那一天Google高兴,把这个函数改个名字我们就直接GG了;但是public函数不同,public被导出的函数你无法保证是否有别人调用它,因此大部分情况下不会修改;我们最好调用public函数来保证尽可能少的遇到兼容性问题。(当然,如果实在木有路可以考虑调用私有方法,自己处理兼容性问题,这个我们以后也会遇到)

间接调用 getPackageInfo 这个私有函数的public函数有同名的getPackageInfo系列和getPackageInfoNoCheck;简单查看源代码发现,getPackageInfo除了获取包的信息,还检查了包的一些组件;为了绕过这些验证,我们选择使用 getPackageInfoNoCheck 获取LoadedApk信息。

构建插件LoadedApk对象

我们这一步的目的很明确,通过getPackageInfoNoCheck函数创建出我们需要的LoadedApk对象,以供接下来使用。

这个函数的签名如下:

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,            CompatibilityInfo compatInfo) {

因此,为了调用这个函数,我们需要构造两个参数。其一是ApplicationInfo,其二是CompatibilityInfo;第二个参数顾名思义,代表这个App的兼容性信息,比如targetSDK版本等等,这里我们只需要提取出app的信息,因此直接使用默认的兼容性即可;在CompatibilityInfo类里面有一个公有字段DEFAULT_COMPATIBILITY_INFO代表默认兼容性信息;因此,我们的首要目标是获取这个ApplicationInfo信息。

构建插件ApplicationInfo信息

我们首先看看ApplicationInfo代表什么,这个类的文档说的很清楚:

Information you can retrieve about a particular application. This corresponds to information collected from the AndroidManifest.xml’s <application> tag.

也就是说,这个类就是AndroidManifest.xml里面的 这个标签下面的信息;这个AndroidManifest.xml无疑是一个标准的xml文件,因此我们完全可以自己使用parse来解析这个信息。

那么,系统是如何获取这个信息的呢?其实Framework就有一个这样的parser,也即PackageParse;理论上,我们也可以借用系统的parser来解析AndroidMAnifest.xml从而得到ApplicationInfo的信息。但遗憾的是, 这个类的兼容性很差 ;Google几乎在每一个Android版本都对这个类动刀,如果坚持使用系统的解析方式,必须写一系列兼容行代码!!DroidPlugin就选择了这种方式,相关类如下:

Android 插件化原理解析——插件加载机制

看到这里我就问你怕不怕!!!这也是我们之前提到的 私有或者隐藏的API可以使用,但必须处理好兼容性问题 ;如果Android 7.0发布,这里估计得添加一个新的类PackageParseApi24。

我这里使用API 23作为演示, 版本不同的可能无法运行 请自行查阅 DroidPlugin 不同版本如何处理。

OK回到正题,我们决定使用PackageParser类来提取ApplicationInfo信息。下图是API 23上,PackageParser的部分类结构图:

Android 插件化原理解析——插件加载机制

看起来有我们需要的方法 generateApplication;确实如此,依靠这个方法我们可以成功地拿到ApplicationInfo。由于PackageParser是@hide的,因此我们需要通过反射进行调用。我们根据这个generateApplicationInfo方法的签名:

public static ApplicationInfo generateApplicationInfo(Package p, int flags,   PackageUserState state)

可以写出调用generateApplicationInfo的反射代码:

Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");// 首先拿到我们得终极目标: generateApplicationInfo方法// API 23 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!// public static ApplicationInfo generateApplicationInfo(Package p, int flags,//    PackageUserState state) {// 其他Android版本不保证也是如此.Class<?> packageParser$PackageClass = Class.forName("android.content.pm.PackageParser$Package");Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo",        packageParser$PackageClass,        int.class,                packageUserStateClass);

要成功调用这个方法,还需要三个参数;因此接下来我们需要一步一步构建调用此函数的参数信息。

构建PackageParser.Package

generateApplicationInfo方法需要的第一个参数是PackageParser.Package;从名字上看这个类代表某个apk包的信息,我们看看文档怎么解释:

Representation of a full package parsed from APK files on disk. A package consists of a single base APK, and zero or more split APKs.

果然,这个类代表从PackageParser中解析得到的某个apk包的信息,是磁盘上apk文件在内存中的数据结构表示;因此,要获取这个类,肯定需要解析整个apk文件。PackageParser中解析apk的核心方法是parsePackage,这个方法返回的就是一个Package类型的实例,因此我们调用这个方法即可;使用反射代码如下:

// 首先, 我们得创建出一个Package对象出来供这个方法调用// 而这个需要得对象可以通过 android.content.pm.PackageParser#parsePackage 这个方法返回得 Package对象得字段获取得到// 创建出一个PackageParser对象供使用Object packageParser = packageParserClass.newInstance();// 调用 PackageParser.parsePackage 解析apk的信息Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);

// 实际上是一个 android.content.pm.PackageParser.Package 对象Object packageObj = parsePackageMethod.invoke(packageParser, apkFile, 0);

这样,我们就得到了generateApplicationInfo的第一个参数;第二个参数是解析包使用的flag,我们直接选择解析全部信息,也就是0;

构建PackageUserState

第三个参数是PackageUserState,代表不同用户中包的信息。由于Android是一个多任务多用户系统,因此不同的用户同一个包可能有不同的状态;这里我们只需要获取包的信息,因此直接使用默认的即可;

至此,generateApplicaionInfo的参数我们已经全部构造完成,直接调用此方法即可得到我们需要的applicationInfo对象;在返回之前我们需要做一点小小的修改:使用系统系统的这个方法解析得到的ApplicationInfo对象中并没有apk文件本身的信息,所以我们把解析的apk文件的路径设置一下(ClassLoader依赖dex文件以及apk的路径):

// 第三个参数 mDefaultPackageUserState 我们直接使用默认构造函数构造一个出来即可Object defaultPackageUserState = packageUserStateClass.newInstance();

// 万事具备!!!!!!!!!!!!!!ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser,        packageObj, 0, defaultPackageUserState);String apkPath = apkFile.getPath();applicationInfo.sourceDir = apkPath;applicationInfo.publicSourceDir = apkPath;

替换ClassLoader

获取LoadedApk信息

方才为了获取ApplicationInfo我们费了好大一番精力,或许读者已经忘了我们的初衷是什么了。

我们最终的目的是调用getPackageInfoNoCheck得到LoadedApk的信息,并替换其中的mClassLoader然后把把添加到ActivityThread的mPackages缓存中;从而达到我们使用自己的ClassLoader加载插件中的类的目的。

现在我们已经拿到了getPackageInfoNoCheck这个方法中至关重要的第一个参数applicationInfo;上文提到第二个参数CompatibilityInfo代表设备兼容性信息,直接使用默认的值即可;因此,两个参数都已经构造出来,我们可以调用getPackageInfoNoCheck获取LoadedAPK:

// android.content.res.CompatibilityInfoClass<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);

Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");defaultCompatibilityInfoField.setAccessible(true);

Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);ApplicationInfo applicationInfo = generateApplicationInfo(apkFile);

Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);

我们成功地构造出了LoadedAPK, 接下来我们需要替换其中的classLoader,然后把它添加进ActivityThread的mPAckages中:

String odexPath = Utils.getPluginOptDexDir(applicationInfo.packageName).getPath();String libDir = Utils.getPluginLibDir(applicationInfo.packageName).getPath();ClassLoader classLoader = new CustomClassLoader(apkFile.getPath(), odexPath, libDir, ClassLoader.getSystemClassLoader());Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");mClassLoaderField.setAccessible(true);mClassLoaderField.set(loadedApk, classLoader);

// 由于是弱引用, 因此我们必须在某个地方存一份, 不然容易被GC; 那么就前功尽弃了.sLoadedApk.put(applicationInfo.packageName, loadedApk);

WeakReference weakReference = new WeakReference(loadedApk);mPackages.put(applicationInfo.packageName, weakReference);

我们的这个CustomClassLoader非常简单,直接继承了DexClassLoader,什么都没有做;当然这里可以直接使用DexClassLoader,这里重新创建一个类是为了更有区分度;以后也可以通过修改这个类实现对于类加载的控制:

public class CustomClassLoader extends DexClassLoader {

public CustomClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {        super(dexPath, optimizedDirectory, libraryPath, parent);    }}

到这里,我们已经成功地把把插件的信息放入ActivityThread中,这样我们插件中的类能够成功地被加载;因此插件中的Activity实例能被成功第创建;由于整个流程较为复杂,我们简单梳理一下:

  1. 在ActivityThread接收到IApplication的 scheduleLaunchActivity远程调用之后,将消息转发给 H
  2. H 类在handleMessage的时候,调用了getPackageInfoNoCheck方法来获取待启动的组件信息。在这个方法中会优先查找 mPackages 中的缓存信息,而我们已经手动把插件信息添加进去;因此能够成功命中缓存,获取到独立存在的插件信息。
  3. H 类然后调用handleLacunActivity最终转发到performLacunchActivity方法;这个方法使用从getPackageInfoNoCheck中拿到LoadedApk中的mClassLoader来加载Activity类,进而使用反射创建Activity实例;接着创建Application,Context等完成Activity组件的启动。

看起来好像已经天衣无缝完事大吉了;但是运行一下会出现一个运行时异常,如下:

04-05 02:49:53.742  11759-11759/com.weishu.upf.hook_classloader E/AndroidRuntime﹕ FATAL EXCEPTION: main    Process: com.weishu.upf.hook_classloader, PID: 11759    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.weishu.upf.ams_pms_hook.app/com.weishu.upf.ams_pms_hook.app.MainActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.weishu.upf.ams_pms_hook.app; is package not installed?

错误提示说是无法实例化 Application ,而Application的创建也是在performLacunchActivity中进行的,这里有些蹊跷,我们仔细查看一下。

绕过系统检查

通过ActivityThread的performLaunchActivity方法可以得知,Application通过LoadedApk的makeApplication方法创建,我们查看这个方法,在源码中发现了上文异常抛出的位置:

try {    java.lang.ClassLoader cl = getClassLoader();    if (!mPackageName.equals("android")) {        initializeJavaContextClassLoader();    }    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);    app = mActivityThread.mInstrumentation.newApplication(            cl, appClass, appContext);    appContext.setOuterContext(app);} catch (Exception e) {    if (!mActivityThread.mInstrumentation.onException(app, e)) {        throw new RuntimeException(            "Unable to instantiate application " + appClass            + ": " + e.toString(), e);    }}

木有办法,我们只有一行一行地查看到底是哪里抛出这个异常的了;所幸代码不多。(所以说,缩小异常范围是一件多么重要的事情!!!)

第一句 getClassLoader() 没什么可疑的,虽然方法很长,但是它木有抛出任何异常(当然,它调用的代码可能抛出异常,万一找不到只能进一步深搜了;所以我觉得这里应该使用受检异常)。

然后我们看第二句,如果包名不是 android 开头,那么调用了一个叫做initializeJavaContextClassLoader的方法;我们查阅这个方法:

private void initializeJavaContextClassLoader() {    IPackageManager pm = ActivityThread.getPackageManager();    android.content.pm.PackageInfo pi;    try {        pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());    } catch (RemoteException e) {        throw new IllegalStateException("Unable to get package info for "                + mPackageName + "; is system dying?", e);    }    if (pi == null) {        throw new IllegalStateException("Unable to get package info for "                + mPackageName + "; is package not installed?");    }

boolean sharedUserIdSet = (pi.sharedUserId != null);    boolean processNameNotDefault =        (pi.applicationInfo != null &&         !mPackageName.equals(pi.applicationInfo.processName));    boolean sharable = (sharedUserIdSet || processNameNotDefault);    ClassLoader contextClassLoader =        (sharable)        ? new WarningContextClassLoader()        : mClassLoader;    Thread.currentThread().setContextClassLoader(contextClassLoader);}

这里,我们找出了这个异常的来源:原来这里调用了 getPackageInfo 方法获取包的信息;而我们的插件 并没有安装在系统上 ,因此系统肯定认为插件没有安装,这个方法肯定返回null。所以,我们还要欺骗一下PMS,让系统觉得 插件已经安装在系统上了 ;至于如何欺骗 PMS,Hook机制之AMS&PMS 有详细解释,这里直接给出代码,不赘述了:

private static void hookPackageManager() throws Exception {

// 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装    // 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.

Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");    currentActivityThreadMethod.setAccessible(true);    Object currentActivityThread = currentActivityThreadMethod.invoke(null);

// 获取ActivityThread里面原始的 sPackageManager    Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");    sPackageManagerField.setAccessible(true);    Object sPackageManager = sPackageManagerField.get(currentActivityThread);

// 准备好代理对象, 用来替换原始的对象    Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");    Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),            new Class<?>[] { iPackageManagerInterface },            new IPackageManagerHookHandler(sPackageManager));

// 1. 替换掉ActivityThread里面的 sPackageManager 字段    sPackageManagerField.set(currentActivityThread, proxy);}

OK到这里,我们已经能够成功地加载 简单的 独立的存在于外部文件系统中的apk了。至此 关于 DroidPlugin 对于Activity生命周期的管理已经完全讲解完毕了;这是一种极其复杂的Activity管理方案,我们仅仅写一个用来理解的demo就Hook了相当多的东西,在Framework层来回牵扯;这其中的来龙去脉要完全把握清楚还请读者亲自翻阅源码。另外,我在此 对DroidPlugin 作者献上我的膝盖~这其中的玄妙让人叹为观止!

上文给出的方案中,我们全盘接管了插件中类的加载过程,这是一种相对暴力的解决方案;能不能更温柔一点呢?通俗来说,我们可以选择改革,而不是革命——告诉系统ClassLoader一些必要信息,让它帮忙完成插件类的加载。

保守方案:委托系统,让系统帮忙加载

我们再次搬出ActivityThread中加载Activity类的代码:

java.lang.ClassLoader cl = r.packageInfo.getClassLoader();activity = mInstrumentation.newActivity(        cl, component.getClassName(), r.intent);StrictMode.incrementExpectedActivityCount(activity.getClass());r.intent.setExtrasClassLoader(cl);

我们知道 这个r.packageInfo中的 r 是通过getPackageInfoNoCheck获取到的;在『激进方案』中我们把插件apk手动添加进缓存,采用自己加载办法解决;如果我们不干预这个过程,导致无法命中mPackages中的缓存,会发生什么?

查阅 getPackageInfo方法如下:

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,        ClassLoader baseLoader, boolean securityViolation, boolean includeCode,        boolean registerPackage) {    final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));    synchronized (mResourcesManager) {        WeakReference<LoadedApk> ref;        if (differentUser) {            // Caching not supported across users            ref = null;        } else if (includeCode) {            ref = mPackages.get(aInfo.packageName);        } else {            ref = mResourcePackages.get(aInfo.packageName);        }

LoadedApk packageInfo = ref != null ? ref.get() : null;        if (packageInfo == null || (packageInfo.mResources != null                && !packageInfo.mResources.getAssets().isUpToDate())) {            packageInfo =                new LoadedApk(this, aInfo, compatInfo, baseLoader,                        securityViolation, includeCode &&                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

// 略    }}

可以看到,没有命中缓存的情况下,系统直接new了一个LoadedApk;注意这个构造函数的第二个参数 aInfo ,这是一个ApplicationInfo类型的对象。在『激进方案』中我们为了获取独立插件的ApplicationInfo花了不少心思;那么如果不做任何处理这里传入的这个 aInfo 参数是什么?

追本溯源不难发现,这个aInfo是从我们的替身StubActivity中获取的!而StubActivity存在于宿主程序中,所以,这个 aInfo 对象代表的实际上就是宿主程序的Application信息!

我们知道,接下来会使用new出来的这个LoadedAPk的getClassLoader()方法获取到ClassLoader来对插件的类进行加载;而获取到的这个CLassLoader是宿主程序使用的CLassLoader,因此现在还无法加载插件的类;那么, 我们能不能让宿主的ClasLoader获得加载插件类的能力呢? ;如果我们告诉宿主使用的ClassLoader插件使用的类在哪里,就能帮助他完成加载!

宿主的ClassLoader在哪里,是唯一的吗?

上面说到,我们可以通过告诉宿主程序的ClassLoader插件使用的类,让宿主的ClasLoader完成对于插件类的加载;那么问题来了,我们如何获取到宿主的ClassLoader?宿主程序使用的ClasLoader默认情况下是全局唯一的吗?

答案是肯定的。

因为在FrameWork中宿主程序也是使用LoadedApk表示的,如Activity启动是加载Activity类一样,宿主中的类也都是通过LoadedApk的getClassLoader()方法得到的ClassLoader加载的。

表示宿主的LoadedApk在Application类中有一个成员变量 mLoadedApk ,而这个变量是从ContextImpl中获取的;ContextImpl重写了getClassLoader方法,因此**我们在Context环境中直接getClassLoader()获取到的就是宿主程序唯一的ClassLoader。

getClassLoader()的ClassLoader到底是什么?

现在我们确保了『使用宿主ClassLoader帮助加载插件类』可行性;那么我们应该如何完成这个过程呢?

知己知彼,百战不殆。

不论是宿主程序还是插件程序都是通过LoadedApk的getClassLoader()方法返回的ClassLoader进行类加载的,返回的这个ClassLoader到底是个什么东西??这个方法源码如下:

public ClassLoader getClassLoader() {    synchronized (this) {        if (mClassLoader != null) {            return mClassLoader;        }

if (mIncludeCode && !mPackageName.equals("android")) {            // 略...            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,                    mBaseClassLoader);

StrictMode.setThreadPolicy(oldPolicy);        } else {            if (mBaseClassLoader == null) {                mClassLoader = ClassLoader.getSystemClassLoader();            } else {                mClassLoader = mBaseClassLoader;            }        }        return mClassLoader;    }}

可以看到,非 android 开头的包和 android 开头的包分别使用了两种不同的ClassLoader,我们只关心第一种;因此继续跟踪ApplicationLoaders类:

public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent){

ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

synchronized (mLoaders) {        if (parent == null) {            parent = baseParent;        }

if (parent == baseParent) {            ClassLoader loader = mLoaders.get(zip);            if (loader != null) {                return loader;            }

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);            PathClassLoader pathClassloader =                new PathClassLoader(zip, libPath, parent);            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

mLoaders.put(zip, pathClassloader);            return pathClassloader;        }

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);        PathClassLoader pathClassloader = new PathClassLoader(zip, parent);        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        return pathClassloader;    }}

可以看到,应用程序使用的ClassLoader都是PathClassLoader类的实例。那么,这个PathClassLoader是什么呢?从Android SDK给出的源码只能看出这么多:

public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) {        super((String)null, (File)null, (String)null, (ClassLoader)null);        throw new RuntimeException("Stub!");    }

public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) {        super((String)null, (File)null, (String)null, (ClassLoader)null);        throw new RuntimeException("Stub!");    }}

SDK没有导出这个类的源码,我们去 androidxref 上面看;发现其实这个类就这么多内容;我们继续查看它的父类 BaseDexClassLoader ;ClassLoader嘛,我们查看findClass或者defineClass方法,BaseDexClassLoader的findClass方法如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {    List<Throwable> suppressedExceptions = new ArrayList<Throwable>();    Class c = pathList.findClass(name, suppressedExceptions);    if (c == null) {        ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);        for (Throwable t : suppressedExceptions) {            cnfe.addSuppressed(t);        }        throw cnfe;    }    return c;}

可以看到,查找Class的任务通过 pathList 完成;这个 pathList 是一个 DexPathList 类的对象,它的 findClass 方法如下:

public Class findClass(String name, List<Throwable> suppressed) {   for (Element element : dexElements) {       DexFile dex = element.dexFile;

if (dex != null) {           Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);           if (clazz != null) {               return clazz;           }       }   }   if (dexElementsSuppressedExceptions != null) {       suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));   }   return null;}

这个DexPathList内部有一个叫做dexElements的数组,然后findClass的时候会遍历这个数组来查找Class; 如果我们把插件的信息塞进这个数组里面,那么不就能够完成类的加载过程吗?!!

给默认ClassLoader打补丁

通过上述分析,我们知道,可以把插件的相关信息放入BaseDexClassLoader的表示dex文件的数组里面,这样宿主程序的ClassLoader在进行类加载,在遍历这个数组的时候,会自动遍历到我们添加进去的插件信息,从而完成插件类的加载!

接下来,我们实现这个过程;我们会用到一些较为复杂的反射技术哦~不过代码非常短:

public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)        throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {    // 获取 BaseDexClassLoader : pathList    Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");    pathListField.setAccessible(true);    Object pathListObj = pathListField.get(cl);

// 获取 PathList: Element[] dexElements    Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");    dexElementArray.setAccessible(true);    Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);

// Element 类型    Class<?> elementClass = dexElements.getClass().getComponentType();

// 创建一个数组, 用来替换原始的数组    Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);

// 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数    Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);    Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));

Object[] toAddElementArray = new Object[] { o };    // 把原始的elements复制进去    System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);    // 插件的那个element复制进去    System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);

// 替换    dexElementArray.set(pathListObj, newElements);

}

短短的二十几行代码,我们就完成了『委托宿主ClassLoader加载插件类』的任务;因此第二种方案也宣告完成!我们简要总结一下这种方式的原理:

  1. 默认情况下performLacunchActivity会使用替身StubActivity的ApplicationInfo也就是宿主程序的CLassLoader加载所有的类;我们的思路时告诉宿主ClassLoader我们在哪,让其帮助完成类加载的过程。
  2. 宿主程序的ClassLoader最终继承自BaseDexClassLoader,BaseDexClassLoader通过DexPathList进行类的查找过程;而这个查找通过遍历一个dexElements的数组完成; 我们通过把插件dex添加进这个数组 就让宿主ClasLoader获取了加载插件类的能力。

小结

本文中我们采用两种方案成功完成了『启动没有在AndroidManifest.xml中显示声明,并且存在于外部插件中的Activity』的任务。

『激进方案』中我们自定义了插件的ClassLoader,并且绕开了Framework的检测;利用ActivityThread对于LoadedApk的缓存机制,我们把携带这个自定义的ClassLoader的插件信息添加进 mPackages 中,进而完成了类的加载过程。

『保守方案』中我们深入探究了系统使用ClassLoader findClass的过程,发现应用程序使用的非系统类都是通过同一个PathClassLoader加载的;而这个类的最终父类BaseDexClassLoader通过DexPathList完成类的查找过程;我们hack了这个查找过程,从而完成了插件类的加载。

这两种方案孰优孰劣呢?

很显然,『激进方案』比较麻烦,从代码量和分析过程就可以看出来,这种机制异常复杂;而且在解析apk的时候我们使用的PackageParser的兼容性非常差,我们不得不手动处理每一个版本的apk解析api;另外,它Hook的地方也有点多:不仅需要Hook AMS和 H ,还需要Hook ActivityThread的 mPAckages 和PackageManager!

『保守方案』则简单得多(虽然原理也不简单),不进代码很少,而且Hook的地方也不多;有一点开源节流的意思,从最最上层Hook住了整个类的加载过程。

但是,我们不能简单地说『保守方案』比『激进方案』好。从根本上说,这两种方案的差异在哪呢?

『激进方案』是 多ClassLoader构架 ,每一个插件都有一个自己的ClassLoader,因此类的隔离性非常好——如果不同的插件使用了同一个库的不同版本,它们相安无事!『保守方案』是 单ClassLoader方案 ,插件和宿主程序的类全部都通过宿主的ClasLoader加载,虽然代码简单,但是鲁棒性很差;一旦插件之间甚至插件与宿主之间使用的类库有冲突,那么直接GG。

多ClassLoader还有一个优点:可以真正完成代码的热加载!如果插件需要升级,直接重新创建一个自定的ClassLoader加载新的插件,然后替换掉原来的版本即可(Java中,不同ClassLoader加载的同一个类被认为时不同的类);单ClassLoader的话实现非常麻烦,有可能需要重启进程。

在J2EE领域中广泛使用ClasLoader的地方均采用多ClassLoader架构,比如Tomcat服务器,Java模块化的事实标准OSGi技术;所以,我们有足够的理由认为 选择多ClassLoader架构在大多数情况下是明智之举

目前开源的插件方案中,DroidPlugin采用的『激进方案』,Small采用的『保守方案』那么,有没有两种优点兼顾的方案呢??

答案自然是有的。

DroidPlugin和Small的共同点是 两者都是非入侵的插件框架 ;什么是『非入侵』呢?打个比方,你启动一个插件Activity,直接使用 startActivity 即可,就跟开发普通的apk一样,开发插件和普通的程序对于开发者来说没有什么区别。

如果我们一定程度上放弃这种『侵入性』,那么我们就能实现一个两者优点兼而有之的插件框架;目前开源的插件框架我没有一一研究查阅源码;这里先容我卖个关子,如果市面上木有,我打算造一个出来,哈哈。

OK,本文的内容就到这里了;关于『插件机制对于Activity的处理方式』也就此完结。在本文的『保守方案』其实只处理了代码的加载过程,它并不能加载有资源的apk!所以目前我这个实现基本没什么暖用;d当然这里只是就『代码加载』进行举例;至于资源,那牵扯到另外一个问题—— 插件系统的资源管理机制 这个在后续文章的合适机会我会单独讲解。

接下来的文章,会讲述Android四大组件的另外三个 ServiceBroadCastReceiver , ContentProvider 的处理方式。喜欢就点个赞吧~持续更新,请关注github项目 understand-plugin-framework 和我的博客! 这两文章我前前后后准备了快两个星期,如果你看到了这里,请务必支持一下 :)

相关文章
  • Android 插件化原理解析——插件加载机制 Android 插件化原理解析——插件加载机制

    上文Activity生命周期管理 中我们地完成了『启动没有在AndroidManifest.xml中显式声明的Activity』的任务:通过Hook AMS 和拦截ActivityThread中 H 类对于组件调度我们成功地绕过了AndroidMAnifest.xml的限制. 但是我们启动的『没有在AndroidManifet.xml中显式声明』的Activity和宿主程序存在于同一个Apk中:但通常情况下,插件均以独立的文件存在甚至通过网络获取,这时候插件中的Activity能否成功启动呢?

  • Android 插件化原理解析——Activity生命周期管理 Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在Android系统上运行起来? 在Java平台要做到动态运行模块.热插拔可以使用 ClassLoader 技术进行动态类加载,比如广泛使用的 OSGi 技术.在Android上当然也可以使用动态加载技术,但是仅仅把类加载进来就足够了吗? Activity , Service 等组件是有生命周期的,它们

  • Android 插件化原理解析——Hook机制之AMS&amp;PMS Android 插件化原理解析——Hook机制之AMS&amp;PMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是 代理方式 和 Binder Hook :插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件化之前,有必要说明一下它对ActivityManagerServiche以及PackageManagerService的Hook方式(以下简称AMS,PMS). ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道:

  • Android插件化(3)加载插件apk中的Resource资源

    Android插件化(三)加载插件apk中的Resource资源 Android加载插件apk中的Resource资源 简介 如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中

  • Android插件化之资源动态加载 Android插件化之资源动态加载

    一.概述 Android插件化的一个重要问题就是插件资源访问问题,先列出会面对的问题 1.如何加载插件资源 2.如何处理插件资源与宿主资源的处突:插件化资源问题要做到的效果是,如果我们要获取的资源在插件中找得到,则加载优先加载插件的,如果找不到,则到宿主资源中找.这样能做到动态更新的效果. 3.如何确保插件和宿主使用到的是被修改过的资源. 二.原理分析 在做一件事之前必须先弄清楚原理,所以,这里先要弄清楚Android的资源体系原理. 1.资源链 Context:一个apk里面其context的

  • Android插件化(三)加载插件apk中的Resource资源

    如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了.总结如下: 1.新建一个AssetManager对象 2.通过反射调用addAssetPath方法 3.

  • 胪陈Android插件化原理 胪陈Android插件化原理

    详述Android插件化原理 本文基于singwhatiwanna的开源DL插件框架讲述,修改并重构了一些内容,任总的blog原理讲得比较浅,这里我基于自己的理解再详细讲一点东西,也算是一个记录吧~ 预备知识: 一. Java ClassLoader 作用: 加载Class文件到JVM,以供程序使用的.我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的.既然ClassLoader是用来加载类到JVM中的,那么ClassLoader又是如何被加载呢

  • eclipse(有ADT插件)启动,出现Android SDK Content Loader 0%,加载不了有关问题的解决

    eclipse(有ADT插件)启动,出现Android SDK Content Loader 0%,加载不了问题的解决 系统:Win8.1-32位 软件:ADT-Bundle 23.0 我碰到的问题是每次开启都无法加载,不管是重启系统还是其他情况. 找到两种解决方法: 1.删掉workspace下的.metadata\.plugins\org.eclipse.core.resources\.projects文件夹,重启eclipse. 2.删掉C:\Users\用户名\.android文件夹下的

  • android插件化-装配apkplug插件-04

    android插件化-安装apkplug插件-04 本节我们将练习怎样安装(更新)一个插件.本文章基于v1.6.7版本进行说明,最新的方式以官网为准 可下载最新的apkplugdemo源码http://git.oschina.net/plug/apkplugDemos 一 apkplug插件 apkplug插件是一个apk文件,它与普通的android APP基本相同(开发方式),本节只讲插件安装接口而插件开发将在后面的章节详细讲解 二 apkplug 插件安装服务 apkplug内嵌一个OSG

  • jquery插件NProgress.js制作网页加载进度条

    这篇文章主要介绍了jquery插件NProgress.js制作网页加载进度条的相关资料,需要的朋友可以参考下 NProgress.js是极细的纳米级进度条,用现实的细线条动画让用户看到网页正在发生的事情! 你也许已经在 Youtube 上看过了那道红色激光脉冲,它会在你切换页面时出现.其实许多移动浏览器的进度条都是这个样式,但是在网页上实现可不多见.不过,有了 NProgress 这个 jQuery 插件,你也可以轻松实现! NProgress.js应用于复杂网页的细长进度条.由 Google,

  • Raphaeljs 插件实现随便SVG节点加载 Raphaeljs 插件实现随便SVG节点加载

    Raphaeljs 插件实现任意SVG节点加载 SVG 和Raphael http://raphaeljs.com/ 官网地址 一般在做svg 开发的时候你可选的三方类库实在太少了,Raphael 是一个用户量比较大,而且一直在升级维护,只能凑活用,Raphael 有一个很强大的功能就是几乎通吃所有浏览器,这种主要兼容旧式浏览器的设计基本抛弃了很多浏览器对svg高级功能的支持,例如SMIL 协议,但是相对来说能支持这么多浏览器版本ie6+ firefox chrome 而且性能还不错,api 非

  • 开下发协助自己工作的插件(1) - 使用Java动态加载Oracle jdbc JAR包

    开发出协助自己工作的插件(1) -- 使用Java动态加载Oracle jdbc JAR包 工作快半年了,最让人反感的事情就是帮实施人员做支撑. 由于不同的省的项目的个性化导致很多数据的查询方式都不一样,所以想根据不同的情况,把不同省的业务逻辑SQL全部从项目中拿出来,组要的目的是方便查询经常需要查的一些业务数据. 同时做为一个新人,可以对整体的业务逻辑进行了解. 公司的项目中,移动主要用DB2,电信主要是Oracle. 目的:让所有业务逻辑的SQL都可以方便的清晰汇总,不必再去项目里面查找.

  • jQuery插件实现网页底部自动加载-相仿新浪微博

    jQuery插件实现网页底部自动加载-类似新浪微博 要实现滚动条滚到底部自动加载后续内容到内容到底部的功能,用jQuery非常方便的,只要知道什么时候滚动到了底部就好办了. $(document).scrollTop() //获取垂直滚动条到顶部的距离 $(document).height()//整个网页的高度 $(window).height()//浏览器窗口的高度 文档的高度减去窗口的高度就是滚动条可滚动的范围了.那么 $(window).scrollTop() + $(window).he

  • 美团Android DEX自动拆包及动态加载简介 美团Android DEX自动拆包及动态加载简介

    美团Android DEX自动拆包及动态加载简介 xijianshuai lixiaoyang 2015-06-15 10:00 概述 作为一个android开发者,在开发应用时,随着业务规模发展到一定程度,不断地加入新功能.添加新的类库,代码在急剧的膨胀,相应的apk包的大小也急剧增加, 那么终有一天,你会不幸遇到这个错误: 生成的apk在android 2.3或之前的机器上无法安装,提示INSTALL_FAILED_DEXOPT 方法数量过多,编译时出错,提示: Conversion to

  • Android apk动态加载机制的研究(2):资源加载和activity生命周期管理 Android apk动态加载机制的研究(2):资源加载和activity生命周期管理

    Android apk动态加载机制的研究(二):资源加载和activity生命周期管理 转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客) 前言 为了更好地阅读本文,你需要先阅读Android apk动态加载机制的研究这篇文章,在此文中,博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周

  • Android apk动态加载机制的研究(二):资源加载和activity生命周期管理 Android apk动态加载机制的研究(二):资源加载和activity生命周期管理

    前言 为了更好地阅读本文,你需要先阅读Android apk动态加载机制的研究这篇文章,在此文中,博主分析了Android中apk的动态加载机制,并在文章的最后指出需要解决的两个复杂问题:资源的访问和activity生命周期的管理,而本文将会分析这两个复杂问题的解决方法.需要说明的一点是,我们不可能调起任何一个未安装的apk,这在技术上是无法实现的,我们调起的apk必须受某种规范的约束,只有在这种约束下开发的apk,我们才能将其调起.另外,本文给出的解决方案也不是完美的,但是逻辑已经可以正常地跑

  • Android开发之ContentProvider组合LoaderManager加载数据(源代码分享)

    Android开发之ContentProvider结合LoaderManager加载数据(源代码分享) contentprovider作为Android的四大存储方式之一,有着广泛的应用性,它暴露了数据地址,可以让其他应用访问数据,可以用于存储图片.通讯录等信息. 按照惯例我们还是看下谷歌官方文档对contentprovider的解释,contentprovider管理一个结构化的数据集.它封装了数据,并提供定义数据安全机制.contentProvider的标准接口实现多线程机制来连接数据,一般

  • Android源码梳理(一):setContentView(...)与LayoutInflater的加载机制分析

    1.背景 做Android应用开发,大家都知道,显示一个界面,就是在activity的子类中重写onCreate方法,在里面调用setContentView(-),那么你在启动activity的时候,就你显示一个界面了.因此,接下来,我就通过源码流程来简单的梳理一下setContentView(-)的加载机制. 2.源码梳理 2.1 在Activity源码当中,setContentView有三个重载的方法,如下: public void setContentView(@LayoutRes int

  • android对网络图片保留缓存 随时加载

    最近开发的android应用中写了一个加载网络图片并保留缓存的类,可随时点击打开,这样速度快了很多,分享给大家参考. 实现原理就是从网上下载图片数据,一边将数据转成drawable并加载到指定的imageview 一边保存成download_image.jpg,在点击imageview时候用intent将图片打开 我将处理图片的过程写成了类 package com.example.downloadandopenimage; import java.io.File; import java.io.

  • Android4.4 Framework分析——Android默许Home应用Launcher3的加载过程分析 Android4.4 Framework分析——Android默许Home应用Launcher3的加载过程分析

    Android4.4 Framework分析--Android默认Home应用Launcher3的加载过程分析 本文主要介绍Android4.4默认Home应用Launcher3的启动过程和Launcher3的数据加载过程.Launcher的启动是开机时,ActivityManagerService准备好后开始的,下图是它的启动序列图: step1,SystemServer中,ActivityManagerService准备好了. step3, boolean resumeTopActiviti

最新文章
  • 你们会去用新的LTS嘛?

    之前unity普遍不被喜好,我到现在还在用10.10,但是随着新的LTS 12.04的出现,大家会去升级嘛? --cut-- shiweifu在2012-04-25 06:57:4回答到: 我主力是fc17 beta xfce FrankCj在2012-04-25 07:40:0回答到: 不会,10.4快,速度是王道 lackrp在2012-04-25 07:53:3回答到: 会,我有升级强迫症.现在我的桌面系统就是Xubuntu 12.04,比较喜欢xfce的小清新 darktiny在2012

  • 粉粉日记app启动密码和账号密码有什么不同? 粉粉日记app启动密码和账号密码有什么不同?

    粉粉日记app启动密码和账号密码有什么不同? 粉粉日记的密码,分2种,是2个不同的功能 1.启动时的密码锁密码(设置粉粉启动密码); 2.粉粉账号的密码(用来同步数据和使用粉粉社区的);

  • 万圣节送闺蜜的祝福语

    1.万圣节到了,收到此短信者:财鬼老鬼大鬼小鬼各路妖怪保佑你,天神地神风神雨神各路神仙祝福你,保你平平安安财源滚滚! 2.万圣节大调查:开心鬼在写短信,幸福鬼在发短信,机灵鬼在转发短信,小气鬼在收藏短信.嘿嘿,万圣节快乐! 3.嘿,嘿嘿,嘿嘿嘿,嘿嘿嘿嘿,嗬,嗬嗬,嗬嗬嗬,嗬嗬嗬嗬,嘎,嘎嘎,嘎嘎嘎,嘎嘎嘎嘎,这就是传说中的恐怖勾魂短信.提前祝你:万圣节快乐! 4.万圣节要到了,看看你达到了哪种富裕境界:第一境界是有钱能使鬼推磨,第二境界是有钱能使磨推鬼,第三境界是有磨能使鬼推钱,第四境界是有磨

  • 怎么让网站看上去更吸引人? 怎么让网站看上去更吸引人?

    视觉设计有一个天然的困难,因为视觉风格是一个很主观的感受,所以设计师很难说服领导和其他人认可这种感受.如果恰巧你的设计和需求方的审美一致那都好说,但是如果不一致,那就有设计师好受的了.另外视觉设计也很难被衡量评判,也许团队都认为设计的不错,但是有什么客观的证据去证明这个设计真的就是对的吗?所以视觉设计师这个职业上升天然就有一道坎. 但是更可悲的是,我发现很多设计师并没有很努力去改进这种状态,因为长期的压抑和抱怨,很多设计师更乐忠于学习技巧工具和新的设计风格,而不是对设计方法和设计流程探索研究.设

  • 土茯苓煲乳鸽 做法

    [原材料]乳鸽1只.土茯苓30克.姜10克.葱15克 [调味料]盐8克.味精2克.胡椒粉3克.料酒15克 [制作过程] 1. 乳鸽洗净斩成大块,土茯苓切片,姜去皮切片,葱切段: 2. 锅中注水烧开,放入乳鸽焯烫去血水,捞出沥干水分: 3. 沙锅中注水,放入乳鸽.土茯苓.姜片煮开,转用小火煲50分钟,加入调叶料煮入味,撒上葱段即可: 4. 准备:6分钟,烹饪:50分钟 [特别提示] 乳鸽过水时可加入少许姜片,以去除腥味. 鸽子为鸽种,鸟属,鸠鸽科,鸽形目,孵卵纲,脊椎动物门.人类养鸽历史悠久,而肉

  • 利用链轮技术两个星期让百度为您臣服 利用链轮技术两个星期让百度为您臣服

    链轮是SEO比较新颖的一种技术,简单来说就是一种四个以上的网站或者博客连在一起的形势.例如有A.B.C.D四个站点,其中A.B.C三个为博客,D 是需要优化的主站.链轮一般至少需要三个站点链接在一起,D是你要优化的主站,这里就可以A链向B,再链向D;B链向C,再链向D;C链向A,再链向D.通过链轮的形势,在文章中把要优化的关键词锚文本指向主站,同时在结尾处用另一篇文章的标题做锚文本指向下一个博客中的文章(示意图如下),增加主站关键词的权重和反向链接,最后达到主站在搜索引擎结果中排名的提升.链轮的

  • 全球七大已灭绝生物 万年前就曾消失的巨型海狸 全球七大已灭绝生物 万年前就曾消失的巨型海狸

    由于气候和早期人类大肆捕杀等原因,导致很多生物遭到灭绝,其中不乏一些令人不可思议的巨型动物.如:阿根廷巨鸟.地懒.巨犀.雕齿兽.巨型海狸等. 地球远古时期曾存在着一些庞大物种,但由于气候和早期人类大肆捕杀等原因,导致它们灭绝消失,其中不乏一些令人不可思议的巨型动物,图片是地球七大灭绝巨型动物. 阿根廷巨鸟与迄今发现最大的飞行鸟类截然不同,它的翼展完全伸展可达到7.3米长. 地懒和巨犀是为数不多的巨型陆地哺乳动物,其体长可达到6米,体重达到4082公斤. 雕齿兽是体型庞大的装甲哺乳动物,大约于1万

  • 纽伦堡火炉王读书笔记

    纽伦堡火炉王读书笔记 陶器是世界上最早的手工制作品,人类在旧石器时代晚期便已掌握制陶术.陶器是中国人在东汉时期的独创. 许多行业都需要陶器,科技越发达,人们对它就越依赖. 尽管陶器如此重要,有关陶器的故事却不多.<纽伦堡火炉王>是英国女作家奥维达为我们创作的优秀作品. <纽伦堡火炉王>的故事始于十九世纪末的奥地利小城哈尔.故事的主角是九岁的男孩奥古斯丁·斯图拉.在一百多年前,他的曾祖从地下挖出纽伦堡艺术家奥古斯丁·希斯柴戈尔在1532年所制造的陶瓷火炉. 男孩与火炉制创者同名,它拥

  • 怎么教儿童英语

    张小姐的女儿小炜今年5周岁了,最近迷上了看国外动画片,虽然不是译制片,但小炜却看得津津有味.张小姐觉得,何不趁此让孩子早点开始学英语呢?但转念一想,自己的英语底子很差,怕辅导不了孩子,报名参加业余少儿英语班吧,又怕太早进入正规的语言学习环境后,反而会抹杀孩子的兴趣. 了解三大障碍 "作为家长,首先要了解孩子在学习英语方面到底存在哪些困难,因为不同的障碍可以寻求不同的解决办法."英孚青少年儿童学校中国区总教务长Ian说,经调查,3至6岁的孩子学习英语的困难主要表现在三个方面: 39%的孩

  • 618电商大战在即 坚持正品才是王道

    原本无关痛痒的三个阿拉伯数字,现在却成为电商大战的代名词-- 谈及618,很多人第一反应的就是电商大战.这三个数字已经不重要,这个日子也并不再特殊.反而是硝烟弥漫的电商大战吸引不少人的眼球.从最开始的京东店庆,演变为全电子商务战线的价格大战,这当中充斥着的是竞争还是炒作?到底6·18电商大战的真正意义和目的何在?什么才是电商企业最应该关注的? 2013年的618电商大战来的更早了些.早在几个月前,各大主流电商网站便已开始纷纷布局.无论是策划还是执行,无论是手段还是方法,各电商无所不用其极.5月3

热门推荐
  • 中秋博饼的方法 中秋博饼的方法 中秋博饼的方法,会饼一套,骰子一副(6个),大碗一个(有点深度,而且要瓷碗才好,才能让骰子在碗里跳起来) 看看下面这两个超级博饼大碗吧! 本站阅读配图 博饼的方法: 中秋会饼每会63块饼,隐含七九六十三之数,因为三.九是我国民间的吉利数. 戏饼以"会"计算,一般一"会"以四五人为宜."会"饼模仿科举制,设状元饼(最大的)一个.对堂(榜眼)饼二个.三红(探花)饼四个.四进(进士)饼八个.二举(举人)饼16个.一秀(秀才)饼32个.这是象征古代四级科
  • 李彦宏自曝管理百度受迪斯尼启发:永远比别人做得好一点 李彦宏自曝管理百度受迪斯尼启发:永远比别人做得好一点 第三届中国文化产业峰会今日在西安开幕,作为全国工商联副主席.中国民营文化产业商会会长,百度董事长兼CEO李彦宏注重强调了用商业推动文化产业发展的重要性. 李彦宏自曝管理百度受迪斯尼理念启发:永远比别人做得好一点 李彦宏表示:"用文化的方式做商业还是用商业的模式做文化,这两者都是相互促进的,而作为互联网企业,我们的使命就是用商业的方式推动文化的发展和繁荣." 对此,李彦宏从以下三个维度阐述了商业模式对文化产业的推进作用. 管理模式:借用迪斯尼"捡垃圾"理念 李彦宏自曝
  • 天天酷跑饭团胖胖值得入手吗 饭团胖胖上线时间 天天酷跑饭团胖胖值得入手吗 饭团胖胖上线时间 天天酷跑前期曝光了新系列精灵,其中就有饭团胖胖,不少玩家都在关注饭团胖胖什么时候上线,那么天天酷跑饭团胖胖什么时候出?饭团胖胖值得入手吗?现在小编就为各位玩家带来了天天酷跑饭团胖胖实力分析,希望能在游戏里帮到各位玩家! 给各位天天酷跑的玩家们带来饭团胖胖实力分析. 天天酷跑饭团大大什么时候出? 饭团大大的技能是冲刺吃任意小头像加分 这个技能在设定上并没有什么特别的地方.我们萌萌哒的小双鱼和饭团大大一样,都是冲刺的时候吃小头像加分哟.因为浪漫双鱼是通过软妹币充值上线的,这个地位和黄金奖池更新没啥差
  • pc端怎么管理小米3 pc端怎么管理小米3 打开小米手机助手!打开后如图 这里就要开启usb调试才能连接成功,那么小米3usb调试在哪?一起来了解一下. 1.在小米3手机中打开"设置"程序进入"全部设置"界面. 2.看到"关于手机"选项栏目后,点击进入可以看到Android版本的选项. 3.连续快速点击"Android版本"四次,即可打开开发者选项. 4.在开发者选项界面就可以找到USB调试模式了,点击即可打开USB调试模式. 连接成功后如下图所示: 可以通过云服务管理