Android 插件化开发<一;

Posted 清浅岁月

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 插件化开发<一;相关的知识,希望对你有一定的参考价值。

android 插件化开发有两方面,一是代码的加载,二是资源的加载。

基于上一篇Android activity的启动方式先对代码的加载说一下,下一篇说一下资源的的加载。

插件化:将一个未安装的apk下载到本地,在未安装的情况下,宿主app可以打开apk 的activity,严格的插件化和组件化需要大家百度科普一下。上一篇<activity的启动>中提到最后是调用ActivityThread的中的performLaunchActivity方法,

Activity activity = null;
try 
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    r.intent.prepareToEnterProcess();
    if (r.state != null) 
        r.state.setClassLoader(cl);
    

方法中的r.packageInfo.getClassLoader(),packaginfo是LoadApk的对象,方法是怎么实现的具体看一下:

public ClassLoader getClassLoader() 
    synchronized (this) 
        if (mClassLoader == null) 
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        
        return mClassLoader;
    
通过getClassLoader获取了一个ClassLoader, LoadApk,文件的相关信息,诸如Apk文件的代码和资源,甚至代码里面的Activity,Service等四大组件的信息我们都可以通过此对象获取。看一下如何获取:

    if (mPackageName.equals("android")) 
        // Note: This branch is taken for system server and we don't need to setup
        // jit profiling support.
        if (mClassLoader != null) 
            // nothing to update
            return;
        

        if (mBaseClassLoader != null) 
            mClassLoader = mBaseClassLoader;
         else 
            mClassLoader = ClassLoader.getSystemClassLoader();
        

        return;
    

    // Avoid the binder call when the package is the current application package.
    // The activity manager will perform ensure that dexopt is performed before
    // spinning up the process.
    if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) 
        VMRuntime.getRuntime().vmInstructionSet();
        try 
            ActivityThread.getPackageManager().notifyPackageUse(mPackageName,
                    PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE);
         catch (RemoteException re) 
            throw re.rethrowFromSystemServer();
        
    

    if (mRegisterPackage) 
        try 
            ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
         catch (RemoteException e) 
            throw e.rethrowFromSystemServer();
        
    

    // Lists for the elements of zip/code and native libraries.
    //
    // Both lists are usually not empty. We expect on average one APK for the zip component,
    // but shared libraries and splits are not uncommon. We expect at least three elements
    // for native libraries (app-based, system, vendor). As such, give both some breathing
    // space and initialize to a small value (instead of incurring growth code).
    final List<String> zipPaths = new ArrayList<>(10);
    final List<String> libPaths = new ArrayList<>(10);
    makePaths(mActivityThread, mApplicationInfo, zipPaths, libPaths);

    final boolean isBundledApp = mApplicationInfo.isSystemApp()
            && !mApplicationInfo.isUpdatedSystemApp();

    String libraryPermittedPath = mDataDir;
    if (isBundledApp) 
        // This is necessary to grant bundled apps access to
        // libraries located in subdirectories of /system/lib
        libraryPermittedPath += File.pathSeparator +
                                System.getProperty("java.library.path");
    

    final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

    // If we're not asked to include code, we construct a classloader that has
    // no code path included. We still need to set up the library search paths
    // and permitted path because NativeActivity relies on it (it attempts to
    // call System.loadLibrary() on a classloader from a LoadedApk with
    // mIncludeCode == false).
    if (!mIncludeCode) 
        if (mClassLoader == null) 
            StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(
                "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
                librarySearchPath, libraryPermittedPath, mBaseClassLoader);
            StrictMode.setThreadPolicy(oldPolicy);
        

        return;
    

    /*
     * With all the combination done (if necessary, actually create the java class
     * loader and set up JIT profiling support if necessary.
     *
     * In many cases this is a single APK, so try to avoid the StringBuilder in TextUtils.
     */
    final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
            TextUtils.join(File.pathSeparator, zipPaths);

    if (ActivityThread.localLOGV)
        Slog.v(ActivityThread.TAG, "Class path: " + zip +
                ", JNI path: " + librarySearchPath);

    boolean needToSetupJitProfiles = false;
    if (mClassLoader == null) 
        // Temporarily disable logging of disk reads on the Looper thread
        // as this is early and necessary.
        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();

        mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
                mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                libraryPermittedPath, mBaseClassLoader);

        StrictMode.setThreadPolicy(oldPolicy);
        // Setup the class loader paths for profiling.
        needToSetupJitProfiles = true;
    

    if (addedPaths != null && addedPaths.size() > 0) 
        final String add = TextUtils.join(File.pathSeparator, addedPaths);
        ApplicationLoaders.getDefault().addPath(mClassLoader, add);
        // Setup the new code paths for profiling.
        needToSetupJitProfiles = true;
    

    // Setup jit profile support.
    //
    // It is ok to call this multiple times if the application gets updated with new splits.
    // The runtime only keeps track of unique code paths and can handle re-registration of
    // the same code path. There's no need to pass `addedPaths` since any new code paths
    // are already in `mApplicationInfo`.
    //
    // It is NOT ok to call this function from the system_server (for any of the packages it
    // loads code from) so we explicitly disallow it there.
    if (needToSetupJitProfiles && !ActivityThread.isSystem()) 
        setupJitProfileSupport();
    

拿到classLoader后,将activity加载进来。大家应该还记得这个,发送消息给ActivityThread的中的H,Handler对象,重写HandlerMessage的方法里面的,其中handlerLauncherActivity里面调用的是performLaunchActivity方法。重点看一下getPackaeInfoNoCheck()方法,看一下 LoadApk是如何产生的。

public void handleMessage(Message msg) 
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) 
        case LAUNCH_ACTIVITY: 
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

            r.packageInfo = getPackageInfoNoCheck(
                    r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
         break;

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
        CompatibilityInfo compatInfo) 
    return getPackageInfo(ai, compatInfo, null, false, true, false);
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())) 
            if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
                    : "Loading resource-only package ") + aInfo.packageName
                    + " (in " + (mBoundApplication != null
                            ? mBoundApplication.processName : null)
                    + ")");
            packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

            if (mSystemThread && "android".equals(aInfo.packageName)) 
                packageInfo.installSystemApplicationInfo(aInfo,
                        getSystemContext().mPackageInfo.getClassLoader());
            

            if (differentUser) 
                // Caching not supported across users
             else if (includeCode) 
                mPackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
             else 
                mResourcePackages.put(aInfo.packageName,
                        new WeakReference<LoadedApk>(packageInfo));
            
        
        return packageInfo;

大家注意一下,第三个参数,在调用的时候是穿的参数是null即baseLoader,所以走的是这个:

packageInfo =
                new LoadedApk(this, aInfo, compatInfo, baseLoader,
                        securityViolation, includeCode &&
                        (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
跟进:

public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
        CompatibilityInfo compatInfo, ClassLoader baseLoader,
        boolean securityViolation, boolean includeCode, boolean registerPackage) 

    mActivityThread = activityThread;
    setApplicationInfo(aInfo);
    mPackageName = aInfo.packageName;
    mBaseClassLoader = baseLoader;
    mSecurityViolation = securityViolation;
    mIncludeCode = includeCode;
    mRegisterPackage = registerPackage;
    mDisplayAdjustments.setCompatibilityInfo(compatInfo);
文章前面的getClassLoader方法里面调用的是:

 
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) 
    if (mPackageName.equals("android")) 
        // Note: This branch is taken for system server and we don't need to setup
        // jit profiling support.
        if (mClassLoader != null) 
            // nothing to update
            return;
        

        if (mBaseClassLoader != null) 
            mClassLoader = mBaseClassLoader;
         else 
            mClassLoader = ClassLoader.getSystemClassLoader();
        

        return;
    

因为mBaseClassLoader是null直接调ClassLoader.getSystemClassLoader()

@CallerSensitive
public static ClassLoader getSystemClassLoader() 
    return SystemClassLoader.loader;

static private class SystemClassLoader 
    public static ClassLoader loader = ClassLoader.createSystemClassLoader();
private static ClassLoader createSystemClassLoader() 
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");

    // String[] paths = classPath.split(":");
    // URL[] urls = new URL[paths.length];
    // for (int i = 0; i < paths.length; i++) 
    // try 
    // urls[i] = new URL("file://" + paths[i]);
    // 
    // catch (Exception ex) 
    // ex.printStackTrace();
    // 
    // 
    //
    // return new java.net.URLClassLoader(urls, null);

    // TODO Make this a java.net.URLClassLoader once we have those?
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());

最后new 了一个PathClassLoader,也就是LoaderAPK最后返回的是是一个PathClassLoader的对象去加载

activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);

public Activity newActivity(ClassLoader cl, String className,
        Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException 
    return (Activity)cl.loadClass(className).newInstance();
里面的cl即使PathClassLoader对象,进一步看一下PathClassLoader:

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 librarySearchPath, ClassLoader parent) 
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    
public class BaseDexClassLoader extends ClassLoader 
    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) 
        throw new RuntimeException("Stub!");
    

    protected Class<?> findClass(String name) throws ClassNotFoundException 
        throw new RuntimeException("Stub!");
    

    protected URL findResource(String name) 
        throw new RuntimeException("Stub!");
    

    protected Enumeration<URL> findResources(String name) 
        throw new RuntimeException("Stub!");
    

    public String findLibrary(String name) 
        throw new RuntimeException("Stub!");
    

    protected synchronized Package getPackage(String name) 
        throw new RuntimeException("Stub!");
    

    public String toString() 
        throw new RuntimeException("Stub!");
    
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package dalvik.system;

import java.io.File;

public class DexClassLoader extends BaseDexClassLoader 
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) 
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    


PathClassLoader和DexClassLoader,
它们都继承自BaseDexClassLoader,这两个类有什么区别呢?其实看一下它们的源码注释就一目了然了。

Android系统通过PathClassLoader来加载系统类和主dex中的类。
而DexClassLoader则用于加载其他dex文件中的类。
他们都是继承自BaseDexClassLoader,具体的加载方法是findClass。

 
 @Override
    protected Class<?> loadClass(String className, boolean resolve)
           throws ClassNotFoundException 
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) 
            clazz = findClass(className);
        

        return clazz;
    
在PathClassLoader和BaseDexClassLoader都米有找到loadClass,最后在ClassLoader中找见,调的是findClass,父类里面没有实现,子类PathClassLoader也没有实现,BaseDexClassLoader也没有实现,我是不是看到假的源码了,我看的API是25的。 ClassLoader的方法,findClass方法,PathClassLoader和BaseDexClassLoader都木有重写是什么鬼?求解释。

protected Class<?> findClass(String name) throws ClassNotFoundException 
    throw new ClassNotFoundException(name);



以上是关于Android 插件化开发<一;的主要内容,如果未能解决你的问题,请参考以下文章

Android 插件化多开原理 | 使用插件化技术的恶意应用 | 插件化的其它风险 | 应用开发推荐方案

Android组件化和插件化开发

Activity插件化解决方案

Android 插件化插件化技术弊端 ( 恶意插件化程序的解决方向 | 常用的插件化虚拟引擎 )

Android 插件化使用插件化引擎对应用进行重打包的恶意软件特征 ( 检测困难 | 成本低 | 恶意插件可更换 | 容易传播 )

干货分享腾讯出品Android插件化开发指南+项目实战(附源码)