android动态库存放路径问题

1.首先来看下我们是如何使用自己的动态库:

public class MyActivity extends Activity {
    /** Called when the activity is first created. */
    //WebView webView;

    public native int sum(int i, int j);
    /*static{
    	System.loadLibrary("test");
    }*/
    @Override
    public void onCreate(Bundle savedInstanceState) {
        System.loadLibrary("test");
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        TextView tv = new TextView(this);
        tv.setText("这是一个测试Android的helloWorld! sum() = " + sum(2, 3));
        setContentView(tv);
    }
}

2.动态库的加载剖析
@system.java

public static void loadLibrary(String libName) {
        Runtime.getRuntime().loadLibrary(libName, VMStack.getCallingClassLoader());
    }

 

@Runtime.java

void loadLibrary(String libraryName, ClassLoader loader) {
        if (loader != null) {
            String filename = loader.findLibrary(libraryName);
            if (filename == null) {
                // It's not necessarily true that the ClassLoader used
                // System.mapLibraryName, but the default setup does, and it's
                // misleading to say we didn't find "libMyLibrary.so" when we
                // actually searched for "liblibMyLibrary.so.so".
                throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
                                               System.mapLibraryName(libraryName) + "\"");
            }
            String error = doLoad(filename, loader);
            if (error != null) {
                throw new UnsatisfiedLinkError(error);
            }
            return;
        }

        String filename = System.mapLibraryName(libraryName);
        List<String> candidates = new ArrayList<String>();
        String lastError = null;
        for (String directory : mLibPaths) {
            String candidate = directory + filename;
            candidates.add(candidate);

            if (IoUtils.canOpenReadOnly(candidate)) {
                String error = doLoad(candidate, loader);
                if (error == null) {
                    return; // We successfully loaded the library. Job done.
                }
                lastError = error;
            }
        }

        if (lastError != null) {
            throw new UnsatisfiedLinkError(lastError);
        }
        throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
    }


2.1 VMStack.getCallingClassLoader()返回什么呢?这依赖于我们运行场景,示例在Activity中加载动态库,如果你了解activity的启动的话,不难发现返回一个PathClassLoader(service同理)

@ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");

        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
@LoadedApk.java
public ClassLoader getClassLoader() {
        synchronized (this) {
            if (mClassLoader != null) {
                return mClassLoader;
            }

            if (mIncludeCode && !mPackageName.equals("android")) {
                // 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())) {
                    final String isa = VMRuntime.getRuntime().vmInstructionSet();
                    try {
                        ActivityThread.getPackageManager().performDexOptIfNeeded(mPackageName, isa);
                    } catch (RemoteException re) {
                        // Ignored.
                    }
                }

                final ArrayList<String> zipPaths = new ArrayList<>();
                final ArrayList<String> libPaths = new ArrayList<>();

                if (mRegisterPackage) {
                    try {
                        ActivityManagerNative.getDefault().addPackageDependency(mPackageName);
                    } catch (RemoteException e) {
                    }
                }

                zipPaths.add(mAppDir);
                if (mSplitAppDirs != null) {
                    Collections.addAll(zipPaths, mSplitAppDirs);
                }

                libPaths.add(mLibDir);

                /*
                 * The following is a bit of a hack to inject
                 * instrumentation into the system: If the app
                 * being started matches one of the instrumentation names,
                 * then we combine both the "instrumentation" and
                 * "instrumented" app into the path, along with the
                 * concatenation of both apps' shared library lists.
                 */

                String instrumentationPackageName = mActivityThread.mInstrumentationPackageName;
                String instrumentationAppDir = mActivityThread.mInstrumentationAppDir;
                String[] instrumentationSplitAppDirs = mActivityThread.mInstrumentationSplitAppDirs;
                String instrumentationLibDir = mActivityThread.mInstrumentationLibDir;

                String instrumentedAppDir = mActivityThread.mInstrumentedAppDir;
                String[] instrumentedSplitAppDirs = mActivityThread.mInstrumentedSplitAppDirs;
                String instrumentedLibDir = mActivityThread.mInstrumentedLibDir;
                String[] instrumentationLibs = null;

                if (mAppDir.equals(instrumentationAppDir)
                        || mAppDir.equals(instrumentedAppDir)) {
                    zipPaths.clear();
                    zipPaths.add(instrumentationAppDir);
                    if (instrumentationSplitAppDirs != null) {
                        Collections.addAll(zipPaths, instrumentationSplitAppDirs);
                    }
                    zipPaths.add(instrumentedAppDir);
                    if (instrumentedSplitAppDirs != null) {
                        Collections.addAll(zipPaths, instrumentedSplitAppDirs);
                    }

                    libPaths.clear();
                    libPaths.add(instrumentationLibDir);
                    libPaths.add(instrumentedLibDir);

                    if (!instrumentedAppDir.equals(instrumentationAppDir)) {
                        instrumentationLibs = getLibrariesFor(instrumentationPackageName);
                    }
                }

                if (mSharedLibraries != null) {
                    for (String lib : mSharedLibraries) {
                        if (!zipPaths.contains(lib)) {
                            zipPaths.add(0, lib);
                        }
                    }
                }

                if (instrumentationLibs != null) {
                    for (String lib : instrumentationLibs) {
                        if (!zipPaths.contains(lib)) {
                            zipPaths.add(0, lib);
                        }
                    }
                }

                final String zip = TextUtils.join(File.pathSeparator, zipPaths);
                final String lib = TextUtils.join(File.pathSeparator, libPaths);

                /*
                 * With all the combination done (if necessary, actually
                 * create the class loader.
                 */

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

                // 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, lib,
                        mBaseClassLoader);

                StrictMode.setThreadPolicy(oldPolicy);
            } else {
                if (mBaseClassLoader == null) {
                    mClassLoader = ClassLoader.getSystemClassLoader();
                } else {
                    mClassLoader = mBaseClassLoader;
                }
            }
            return mClassLoader;
        }
    }
@ApplicationLoaders.java
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
    {
        /*
         * This is the parent we use if they pass "null" in.  In theory
         * this should be the "system" class loader; in practice we
         * don't use that and can happily (and more efficiently) use the
         * bootstrap class loader.
         */
        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

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

            /*
             * If we're one step up from the base class loader, find
             * something in our cache.  Otherwise, we create a whole
             * new ClassLoader for the zip archive.
             */
            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;
        }
    }
上面libpath实际上是PackageManagerService给应用指定的,这里一般会包括/data/app/[packagename]/lib、/data/app-lib等

@PackageManagerService.java

private void setNativeLibraryPaths(PackageParser.Package pkg) {
        final ApplicationInfo info = pkg.applicationInfo;
        final String codePath = pkg.codePath;
        final File codeFile = new File(codePath);
        final boolean bundledApp = isSystemApp(info) && !isUpdatedSystemApp(info);
        final boolean asecApp = isForwardLocked(info) || isExternal(info);

        info.nativeLibraryRootDir = null;
        info.nativeLibraryRootRequiresIsa = false;
        info.nativeLibraryDir = null;
        info.secondaryNativeLibraryDir = null;

        if (isApkFile(codeFile)) {
            // Monolithic install
            if (bundledApp) {
                // If "/system/lib64/apkname" exists, assume that is the per-package
                // native library directory to use; otherwise use "/system/lib/apkname".
                final String apkRoot = calculateBundledApkRoot(info.sourceDir);
                final boolean is64Bit = VMRuntime.is64BitInstructionSet(
                        getPrimaryInstructionSet(info));

                // This is a bundled system app so choose the path based on the ABI.
                // if it's a 64 bit abi, use lib64 otherwise use lib32. Note that this
                // is just the default path.
                final String apkName = deriveCodePathName(codePath);
                final String libDir = is64Bit ? LIB64_DIR_NAME : LIB_DIR_NAME;
                info.nativeLibraryRootDir = Environment.buildPath(new File(apkRoot), libDir,
                        apkName).getAbsolutePath();

                if (info.secondaryCpuAbi != null) {
                    final String secondaryLibDir = is64Bit ? LIB_DIR_NAME : LIB64_DIR_NAME;
                    info.secondaryNativeLibraryDir = Environment.buildPath(new File(apkRoot),
                            secondaryLibDir, apkName).getAbsolutePath();
                }
            } else if (asecApp) {
                info.nativeLibraryRootDir = new File(codeFile.getParentFile(), LIB_DIR_NAME)
                        .getAbsolutePath();
            } else {
                final String apkName = deriveCodePathName(codePath);
                info.nativeLibraryRootDir = new File(mAppLib32InstallDir, apkName)
                        .getAbsolutePath();
            }

            info.nativeLibraryRootRequiresIsa = false;
            info.nativeLibraryDir = info.nativeLibraryRootDir;
        } else {
            // Cluster install
            info.nativeLibraryRootDir = new File(codeFile, LIB_DIR_NAME).getAbsolutePath();
            info.nativeLibraryRootRequiresIsa = true;

            info.nativeLibraryDir = new File(info.nativeLibraryRootDir,
                    getPrimaryInstructionSet(info)).getAbsolutePath();

            if (info.secondaryCpuAbi != null) {
                info.secondaryNativeLibraryDir = new File(info.nativeLibraryRootDir,
                        VMRuntime.getInstructionSet(info.secondaryCpuAbi)).getAbsolutePath();
            }
        }
    }

2.2 现在回到PathClassLoader(PathClassLoader extends BaseDexClassLoader)

@BaseDexClassLoader.java

@Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }
其中pathList为DexPathList

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
@DexPathList.java

public String findLibrary(String libraryName) {
        String fileName = System.mapLibraryName(libraryName);
        for (File directory : nativeLibraryDirectories) {
            String path = new File(directory, fileName).getPath();
            if (IoUtils.canOpenReadOnly(path)) {
                return path;
            }
        }
        return null;
    }
其中nativeLibraryDirectories由path1、path2两部分组成

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }

        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }

        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }

            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }

        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    

private static File[] splitLibraryPath(String path) {
        // Native libraries may exist in both the system and
        // application library paths, and we use this search order:
        //
        //   1. this class loader's library path for application libraries
        //   2. the VM's library path from the system property for system libraries
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        ArrayList<File> result = splitPaths(path, System.getProperty("java.library.path"), true);
        return result.toArray(new File[result.size()]);
    }

private static ArrayList<File> splitPaths(String path1, String path2,
            boolean wantDirectories) {
        ArrayList<File> result = new ArrayList<File>();

        splitAndAdd(path1, wantDirectories, result);
        splitAndAdd(path2, wantDirectories, result);
        return result;
    }

这里path1就是我们上面提到的PackageManagerService为应用指定的libpath

2.3 接下来我们看下path2,读取的系统属性System.getProperty("java.library.path"),而这个系统属性的初始化如下:

* <tr><td>java.library.path</td>  <td>Search path for JNI libraries</td>     <td>{@code /vendor/lib:/system/lib}</td></tr>

@System.java(android旧版本)

    public static String getProperty(String prop, String defaultValue) {
        if (prop.isEmpty()) {
            throw new IllegalArgumentException();
        }
        return getProperties().getProperty(prop, defaultValue);
    }
    public static Properties getProperties() {
        if (systemProperties == null) {
            initSystemProperties();
        }
        return systemProperties;
    }
<p>    private static void initSystemProperties() {
        VMRuntime runtime = VMRuntime.getRuntime();
        Properties p = new Properties();</p><p>        String projectUrl = "<a target=_blank href="http://www.android.com/">http://www.android.com/</a>";
        String projectName = "The Android Project";</p><p>        p.put("java.boot.class.path", runtime.bootClassPath());
        p.put("java.class.path", runtime.classPath());</p><p>        // None of these four are meaningful on Android, but these keys are guaranteed
        // to be present for System.getProperty. For java.class.version, we use the maximum
        // class file version that dx currently supports.
        p.put("java.class.version", "50.0");
        p.put("java.compiler", "");
        p.put("java.ext.dirs", "");
        p.put("java.version", "0");</p><p>        p.put("java.home", getenv("JAVA_HOME", "/system"));</p><p>        p.put("java.io.tmpdir", "/tmp");
        p.put("java.library.path", getenv("LD_LIBRARY_PATH"));</p>



 

上面的系统环境变量“LD_LIBRARY_PATH”,在init.rc文件中进行配置

export LD_LIBRARY_PATH /vendor/lib:/system/lib

3.总结:加载动态库时,查找路径基于当前ClassLoader的nativeLibraryDirectories,不是固定的,但大多数情况下一般包含:/data/app/[packagename]/lib、/vendor/lib、/system/lib...

示例在某android5.0系统上的动态库查找路径如下:

dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.lance.contactdbtest-2/base.apk"],nativeLibraryDirectories=[/data/app/com.example.lance.contactdbtest-2/lib/arm64, /vendor/lib64, /system/lib64]]] couldn't find "libtest.so"

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值