• [Android Pro] https://blog.csdn.net/gaugamela/article/details/79143309


    原文地址:https://blog.csdn.net/gaugamela/article/details/79143309

    最近遇到这样一个问题:
    第三方的SDK除了Jar包外,还提供了对应的so文件。
    APK集成SDK后进行测试,发现一切正常。
    但将APK作为系统应用集成到ROM时,发现so获取失败。

    看了一下SDK的代码,发现由于底层库的需求,SDK没有直接利用System.loadLibrary来加载so,
    而是主动获取so的路径,对应的代码如下:

    protected static String findNativeLibraryPath(Context context, String libraryName) {
        if (context == null) {
            return null;
        }
    
        if (TextUtils.isEmpty(libraryName)) {
            return null;
        }
    
        String dexPath = context.getPackageCodePath();
        //注意这个地方
        //取的是ApplicationInfo中的nativeLibraryDir
        String nativeLibraryDir = context.getApplicationInfo().nativeLibraryDir;
    
        //创建PathClassLoader
        PathClassLoader pathClassLoader = new PathClassLoader(dexPath, nativeLibraryDir,
                ClassLoader.getSystemClassLoader());
    
        //利用PathClassLoader来获取so的路径
        return pathClassLoader.findLibrary(libraryName);
    }

    为了解释这个问题及寻找解决方案,我们就必须查找对应的源码了。 
    接下来,我们就以8.0的代码为例,看看相应的流程。

     一、PathClassLoader相关内容 
    我们先来看看PathClassLoader相关的源码:

    public class PathClassLoader extends BaseDexClassLoader {
        ...........
        //前文传入ApplicationInfo的nativeLibraryDir
        //对应此处的librarySearchPath
        public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
            super(dexPath, null, librarySearchPath, parent);
        }
    }

    PathClassLoader继承BaseDexClassLoader,仅实现了两个构造函数, 
    主要的逻辑还是实现于BaseDexClassLoader中。

    1.1 BaseDexClassLoader相关内容 
    我们来看看BaseDexClassLoader中的代码:

    public class BaseDexClassLoader extends ClassLoader {
        .............
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
            super(parent);
            //注意这里构造了DexPathList, 同样传入了librarySearchPath
            this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);
            .................
        }
        ...........
        @Override
        public String findLibrary(String name) {
            return pathList.findLibrary(name);
        }
        .......
    }

    从代码不难发现,findLibrary查找的实际上是DexPathList, 
    后者于BaseDexClassLoader的构造函数中创建。

    1.2 DexPathList相关内容 
    继续跟进DexPathList对应的代码:

    final class DexPathList {
        .......
        public DexPathList(ClassLoader definingContext, String dexPath,
                String librarySearchPath, File optimizedDirectory) {
            ...........
            //从下面的代码,容易看出native path包括传入的librarySearchPath
            //及"java.library.path"对应的路径
            this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
            this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
    
            List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
            allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
    
            //最后将信息保存到nativeLibraryPathElements
            this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
            .....................
        }
        ...........
        //容易看出DexPathList就是从nativeLibraryPathElements中获取结果
        public String findLibrary(String libraryName) {
            String fileName = System.mapLibraryName(libraryName);
    
            for (NativeLibraryElement element : nativeLibraryPathElements) {
                String path = element.findNativeLibrary(fileName);
    
                if (path != null) {
                    return path;
                }
            }
    
            return null;
        }
        ...............
    }

    至此,我们应该可以得出结论,so路径无法找到的原因就是:
    ApplicationInfo.nativeLibraryDir及”java.library.path”对应的路径中没有so。
    那么APK被集成到系统时,so的路径到底在哪里?

    为了解决这个问题,我们就要看看APK创建过程相关的代码了。

    二、APK创建过程相关内容 
    APK被系统创建时,会调用handleBindApplication函数,我们截取其中一部分:

    private void handleBindApplication(AppBindData data) {
        ...........
        //判断APK是否支持了测试框架
        if (ii != null) {
            .........
            //创建LoadedApk
            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
                    //初次调用ContextImpl的classLoader
                    appContext.getClassLoader(), false, true, false);
            //以LoadedApk重新创建Context
            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
            try {
                //再次调用ContextImpl的classLoader
                final ClassLoader cl = instrContext.getClassLoader();
            ........
        }
        ...........
    }

    当APK支持测试框架时才会进入上述逻辑。
    不过在Apk加载service、activity等组件时,
    最终都会用到ContextImpl的getClassLoader函数。
    因此,可以以这段代码为例进行分析。

    我们来看看ContextImpl的getClassLoader函数:

    public ClassLoader getClassLoader() {
        //初次调用时,返回的是ClassLoader.getSystemClassLoader()
        //再次调用时,调用的是LoadedApk.getClassLoader()
        return mClassLoader != null ? mClassLoader
                : (mPackageInfo != null ? mPackageInfo.getClassLoader()
                        : ClassLoader.getSystemClassLoader());
    }

    当Apk信息解析完毕后,会生成LoadedApk信息。
    一旦有了LoadedApk信息后,ContextImpl返回的就是LoadedApk.getClassLoader的结果。

    我们仅关注APK解析完毕,有了LoadedApk信息后的代码流程。
    因此,直接跟进LoadedApk相关的代码:

    public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader == null) {
                //初次调用时,会进入此分支
                createOrUpdateClassLoaderLocked(null /*addedPaths*/);
            }
            return mClassLoader;
        }
    }

    我们来看看createOrUpdateClassLoaderLocked函数,重点关注路径相关的信息:

    private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
        ..........
        //这两个对象保存最终的结果,
        //其中libPaths保存的是so相关的路径
        final List<String> zipPaths = new ArrayList<>(10);
        final List<String> libPaths = new ArrayList<>(10);
    
        //判断是否为BundledApp(主要为系统应用)
        final boolean isBundledApp = mApplicationInfo.isSystemApp()
                && !mApplicationInfo.isUpdatedSystemApp();
    
        //构建libPaths等
        makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);
    
        //之后利用libPaths等信息构造ClassLoader
        ...........
    }

    按照代码流程,接下来我们分析下makePaths函数, 
    主要关注libPaths相关的内容:

    public static void makePaths(ActivityThread activityThread,
                             boolean isBundledApp,
                             ApplicationInfo aInfo,
                             List<String> outZipPaths,
                             List<String> outLibPaths) {
        //取出app对应的sourceDir
        final String appDir = aInfo.sourceDir;
    
        //这里取出了ApplicationInfo中的nativeLibraryDir
        final String libDir = aInfo.nativeLibraryDir;
        final String[] sharedLibraries = aInfo.sharedLibraryFiles;
    
        //清空结果并增加sourceDir
        outZipPaths.clear();
        outZipPaths.add(appDir);
        ...........
        if (outLibPaths != null) {
            outLibPaths.clear();
        }
        ............
        if (outLibPaths != null) {
            if (outLibPaths.isEmpty()) {
                //输出结果中增加nativeLibraryDir
                outLibPaths.add(libDir);
            }
    
            //当ApplicationInfo指定了Abi时
            if (aInfo.primaryCpuAbi != null) {
                if (aInfo.targetSdkVersion < Build.VERSION_CODES.N) {
                    outLibPaths.add("/system/fake-libs" +
                            (VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
                }
    
                //前文中outZipPaths已经增加了ApplicationInfo.sourceDir
                //增加abi对应的路径
                for (String apk : outZipPaths) {
                    outLibPaths.add(apk + "!/lib/" + aInfo.primaryCpuAbi);
                }
            }
    
            //系统APK
            if (isBundledApp) {
                ........
                outLibPaths.add(System.getProperty("java.library.path"));
            }
    
            //如果还有shared library, 还需要增加shared library对应的路径
            if (sharedLibraries != null) {
                for (String lib : sharedLibraries) {
                    if (!outZipPaths.contains(lib)) {
                        outZipPaths.add(0, lib);
                        appendApkLibPathIfNeeded(lib, aInfo, outLibPaths);
                    }
                }
            }
            ...........
        }
    }

    至此我们应该可以看出,对于一个APK而言: 
    ApplicationInfo.nativeLibraryDir只是so存储路径的一部分。 
    整体的so路径可能还包括!/lib/abi部分、system/lib部分及shared library部分,例如:

    nativeLibraryDirectories=[/data/app/test-app/lib/arm, /data/app/test-app/base.apk!/lib/armeabi-v7a, /vendor/lib, /system/lib]]]

    关于so何时被拷贝到上述目录,就需要分析so拷贝的流程,本博客暂不对此进行分析。

    三、解决方案
    了解问题的原因后,解决方案其实就呼之欲出了。
    从前文的代码,我们容易看出:
    APK对应的so路径就保存Context对应的ClassLoader中。
    该ClassLoader调用Context.getClassLoader就能获取。

    在前文提及的LoadedApk的createOrUpdateClassLoaderLocked中,
    创建ClassLoader的代码如下:

    private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
        .............
        if (mClassLoader == null) {
            .........
            mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
                    mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
                    libraryPermittedPath, mBaseClassLoader);
            .......
        }
    }

    我们跟进一下ApplicationLoaders.getClassLoader函数:

    private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                   String librarySearchPath, String libraryPermittedPath,
                                   ClassLoader parent, String cacheKey) {
        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
    
        synchronized (mLoaders) {
            if (parent == null) {
                parent = baseParent;
            }
    
            if (parent == baseParent) {
                .............
                PathClassLoader pathClassloader = PathClassLoaderFactory.createClassLoader(
                                                      zip,
                                                      librarySearchPath,
                                                      libraryPermittedPath,
                                                      parent,
                                                      targetSdkVersion,
                                                      isBundled);
                .........
            }
            ...........
            PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
            ...........
            return pathClassloader;
        }
    }

    容易看出,上述代码无论进入哪个分支, 
    getClassLoader最终返回的是一个PathClassLoader函数。

    于是,文中开头部分提及findNativeLibraryPath改为如下方式即可:

    protected static String findNativeLibraryPath(AVLContext context, String libraryName) {
        if (context == null) {
            return null;
        }
    
        if (TextUtils.isEmpty(libraryName)) {
            return null;
        }
    
        PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
        return classLoader.findLibrary(libraryName);
    }

    经过测试,此种方法可以满足我们当前的各种集成场景的需求。

    
    
    
    
    
  • 相关阅读:
    POJ 1129 深搜&四色染图&模拟
    POJ 1011 很经典的树枝拼凑的深度搜索
    HDU 2564 词组缩写
    Java中的split函数的用法
    java中next和nextline的区别
    简探this和super
    再探Java中的继承加载顺序
    Java面向接口编程小例子 2
    Dos命令整理集(持续更新)
    VMware WorkStation9.0虚拟机如何运行WINPE
  • 原文地址:https://www.cnblogs.com/0616--ataozhijia/p/10007246.html
Copyright © 2020-2023  润新知