// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.base.library_loader; import android.content.Context; import android.util.Log; import org.chromium.base.CommandLine; import org.chromium.base.JNINamespace; import org.chromium.base.SysUtils; import org.chromium.base.TraceEvent; /** * This class provides functionality to load and register the native libraries. * Callers are allowed to separate loading the libraries from initializing them. * This may be an advantage for Android Webview, where the libraries can be loaded * by the zygote process, but then needs per process initialization after the * application processes are forked from the zygote process. * * The libraries may be loaded and initialized from any thread. Synchronization * primitives are used to ensure that overlapping requests from different * threads are handled sequentially. * * See also base/android/library_loader/library_loader_hooks.cc, which contains * the native counterpart to this class. */ @JNINamespace("base::android") public class LibraryLoader { private static final String TAG = "LibraryLoader"; // Guards all access to the libraries private static final Object sLock = new Object(); // One-way switch becomes true when the libraries are loaded. private static boolean sLoaded = false; // One-way switch becomes true when the libraries are initialized ( // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in // library_loader_hooks.cc). private static boolean sInitialized = false; // One-way switch becomes true if the system library loading failed, // and the right native library was found and loaded by the hack. // The flag is used to report UMA stats later. private static boolean sNativeLibraryHackWasUsed = false; // TODO(cjhopman): Remove this once it's unused. /** * Doesn't do anything. */ @Deprecated public static void setLibraryToLoad(String library) { } /** * TODO: http://crbug.com/354655 * remove this method once WebViewChromiumFactoryProvider.java * changes the call to ensureInitialized(null). */ public static void ensureInitialized() throws ProcessInitException { ensureInitialized(null); } /** * This method blocks until the library is fully loaded and initialized. * * @param context The context in which the method is called, the caller * may pass in a null context if it doesn't know in which context it * is running, or it doesn't need to work around the issue * http://b/13216167. * * When the context is not null and native library was not extracted * by Android package manager, the LibraryLoader class * will extract the native libraries from APK. This is a hack used to * work around some Sony devices with the following platform bug: * http://b/13216167. */ public static void ensureInitialized(Context context) throws ProcessInitException { synchronized (sLock) { if (sInitialized) { // Already initialized, nothing to do. return; } loadAlreadyLocked(context); initializeAlreadyLocked(CommandLine.getJavaSwitchesOrNull()); } } /** * Checks if library is fully loaded and initialized. */ public static boolean isInitialized() { synchronized (sLock) { return sInitialized; } } /** * Loads the library and blocks until the load completes. The caller is responsible * for subsequently calling ensureInitialized(). * May be called on any thread, but should only be called once. Note the thread * this is called on will be the thread that runs the native code's static initializers. * See the comment in doInBackground() for more considerations on this. * * @throws ProcessInitException if the native library failed to load. */ public static void loadNow(Context context) throws ProcessInitException { synchronized (sLock) { loadAlreadyLocked(context); } } /** * initializes the library here and now: must be called on the thread that the * native will call its "main" thread. The library must have previously been * loaded with loadNow. * @param initCommandLine The command line arguments that native command line will * be initialized with. */ public static void initialize(String[] initCommandLine) throws ProcessInitException { synchronized (sLock) { initializeAlreadyLocked(initCommandLine); } } // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code private static void loadAlreadyLocked(Context context) throws ProcessInitException { try { if (!sLoaded) { assert !sInitialized; long startTime = System.currentTimeMillis(); boolean useChromiumLinker = Linker.isUsed(); if (useChromiumLinker) Linker.prepareLibraryLoad(); for (String library : NativeLibraries.LIBRARIES) { Log.i(TAG, "Loading: " + library); if (useChromiumLinker) Linker.loadLibrary(library); else try { System.loadLibrary(library); if (context != null) { LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously( context); } } catch (UnsatisfiedLinkError e) { if (context != null && LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context, library)) { sNativeLibraryHackWasUsed = true; } else { throw e; } } } if (useChromiumLinker) Linker.finishLibraryLoad(); long stopTime = System.currentTimeMillis(); Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)", stopTime - startTime, startTime % 10000, stopTime % 10000)); sLoaded = true; } } catch (UnsatisfiedLinkError e) { throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e); } // Check that the version of the library we have loaded matches the version we expect Log.i(TAG, String.format( "Expected native library version number \"%s\"," + "actual native library version number \"%s\"", NativeLibraries.VERSION_NUMBER, nativeGetVersionNumber())); if (!NativeLibraries.VERSION_NUMBER.equals(nativeGetVersionNumber())) { throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION); } } // Invoke base::android::LibraryLoaded in library_loader_hooks.cc private static void initializeAlreadyLocked(String[] initCommandLine) throws ProcessInitException { if (sInitialized) { return; } if (!nativeLibraryLoaded(initCommandLine)) { Log.e(TAG, "error calling nativeLibraryLoaded"); throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI); } // From this point on, native code is ready to use and checkIsReady() // shouldn't complain from now on (and in fact, it's used by the // following calls). sInitialized = true; CommandLine.enableNativeProxy(); TraceEvent.setEnabledToMatchNative(); // Record histogram for the Chromium linker. if (Linker.isUsed()) nativeRecordChromiumAndroidLinkerHistogram(Linker.loadAtFixedAddressFailed(), SysUtils.isLowEndDevice()); nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed); } // Only methods needed before or during normal JNI registration are during System.OnLoad. // nativeLibraryLoaded is then called to register everything else. This process is called // "initialization". This method will be mapped (by generated code) to the LibraryLoaded // definition in base/android/library_loader/library_loader_hooks.cc. // // Return true on success and false on failure. private static native boolean nativeLibraryLoaded(String[] initCommandLine); // Method called to record statistics about the Chromium linker operation, // i.e. whether the library failed to be loaded at a fixed address, and // whether the device is 'low-memory'. private static native void nativeRecordChromiumAndroidLinkerHistogram( boolean loadedAtFixedAddressFailed, boolean isLowMemoryDevice); // Get the version of the native library. This is needed so that we can check we // have the right version before initializing the (rest of the) JNI. private static native String nativeGetVersionNumber(); private static native void nativeRecordNativeLibraryHack(boolean usedHack); }