LibraryLoader.java revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.base.library_loader;
6
7import android.content.Context;
8import android.os.SystemClock;
9import android.util.Log;
10
11import org.chromium.base.CommandLine;
12import org.chromium.base.JNINamespace;
13import org.chromium.base.SysUtils;
14import org.chromium.base.TraceEvent;
15
16/**
17 * This class provides functionality to load and register the native libraries.
18 * Callers are allowed to separate loading the libraries from initializing them.
19 * This may be an advantage for Android Webview, where the libraries can be loaded
20 * by the zygote process, but then needs per process initialization after the
21 * application processes are forked from the zygote process.
22 *
23 * The libraries may be loaded and initialized from any thread. Synchronization
24 * primitives are used to ensure that overlapping requests from different
25 * threads are handled sequentially.
26 *
27 * See also base/android/library_loader/library_loader_hooks.cc, which contains
28 * the native counterpart to this class.
29 */
30@JNINamespace("base::android")
31public class LibraryLoader {
32    private static final String TAG = "LibraryLoader";
33
34    // Guards all access to the libraries
35    private static final Object sLock = new Object();
36
37    // One-way switch becomes true when the libraries are loaded.
38    private static boolean sLoaded = false;
39
40    // One-way switch becomes true when the libraries are initialized (
41    // by calling nativeLibraryLoaded, which forwards to LibraryLoaded(...) in
42    // library_loader_hooks.cc).
43    private static boolean sInitialized = false;
44
45    // One-way switch becomes true if the system library loading failed,
46    // and the right native library was found and loaded by the hack.
47    // The flag is used to report UMA stats later.
48    private static boolean sNativeLibraryHackWasUsed = false;
49
50    /**
51     * The same as ensureInitialized(null, false), should only be called
52     * by non-browser processes.
53     *
54     * @throws ProcessInitException
55     */
56    public static void ensureInitialized() throws ProcessInitException {
57        ensureInitialized(null, false);
58    }
59
60    /**
61     *  This method blocks until the library is fully loaded and initialized.
62     *
63     *  @param context The context in which the method is called, the caller
64     *    may pass in a null context if it doesn't know in which context it
65     *    is running, or it doesn't need to work around the issue
66     *    http://b/13216167.
67     *
68     *    When the context is not null and native library was not extracted
69     *    by Android package manager, the LibraryLoader class
70     *    will extract the native libraries from APK. This is a hack used to
71     *    work around some Sony devices with the following platform bug:
72     *    http://b/13216167.
73     *
74     *  @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
75     *    should delete the old workaround libraries or not.
76     */
77    public static void ensureInitialized(
78            Context context, boolean shouldDeleteOldWorkaroundLibraries)
79            throws ProcessInitException {
80        synchronized (sLock) {
81            if (sInitialized) {
82                // Already initialized, nothing to do.
83                return;
84            }
85            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
86            initializeAlreadyLocked();
87        }
88    }
89
90    /**
91     * Checks if library is fully loaded and initialized.
92     */
93    public static boolean isInitialized() {
94        synchronized (sLock) {
95            return sInitialized;
96        }
97    }
98
99    /**
100     * The same as loadNow(null, false), should only be called by
101     * non-browser process.
102     *
103     * @throws ProcessInitException
104     */
105    public static void loadNow() throws ProcessInitException {
106        loadNow(null, false);
107    }
108
109    /**
110     * Loads the library and blocks until the load completes. The caller is responsible
111     * for subsequently calling ensureInitialized().
112     * May be called on any thread, but should only be called once. Note the thread
113     * this is called on will be the thread that runs the native code's static initializers.
114     * See the comment in doInBackground() for more considerations on this.
115     *
116     * @param context The context the code is running, or null if it doesn't have one.
117     * @param shouldDeleteOldWorkaroundLibraries The flag tells whether the method
118     *   should delete the old workaround libraries or not.
119     *
120     * @throws ProcessInitException if the native library failed to load.
121     */
122    public static void loadNow(Context context, boolean shouldDeleteOldWorkaroundLibraries)
123            throws ProcessInitException {
124        synchronized (sLock) {
125            loadAlreadyLocked(context, shouldDeleteOldWorkaroundLibraries);
126        }
127    }
128
129    /**
130     * initializes the library here and now: must be called on the thread that the
131     * native will call its "main" thread. The library must have previously been
132     * loaded with loadNow.
133     */
134    public static void initialize() throws ProcessInitException {
135        synchronized (sLock) {
136            initializeAlreadyLocked();
137        }
138    }
139
140    // Invoke System.loadLibrary(...), triggering JNI_OnLoad in native code
141    private static void loadAlreadyLocked(
142            Context context, boolean shouldDeleteOldWorkaroundLibraries)
143            throws ProcessInitException {
144        try {
145            if (!sLoaded) {
146                assert !sInitialized;
147
148                long startTime = SystemClock.uptimeMillis();
149                boolean useChromiumLinker = Linker.isUsed();
150
151                if (useChromiumLinker) Linker.prepareLibraryLoad();
152
153                for (String library : NativeLibraries.LIBRARIES) {
154                    if (useChromiumLinker) {
155                        if (Linker.isInZipFile()) {
156                            String zipfile = context.getApplicationInfo().sourceDir;
157                            Log.i(TAG, "Loading " + library + " from within " + zipfile);
158                            Linker.loadLibraryInZipFile(zipfile, library);
159                        } else {
160                            Log.i(TAG, "Loading: " + library);
161                            Linker.loadLibrary(library);
162                        }
163                    } else {
164                        try {
165                            System.loadLibrary(library);
166                        } catch (UnsatisfiedLinkError e) {
167                            if (context != null
168                                && LibraryLoaderHelper.tryLoadLibraryUsingWorkaround(context,
169                                                                                     library)) {
170                                sNativeLibraryHackWasUsed = true;
171                            } else {
172                                throw e;
173                            }
174                        }
175                    }
176                }
177                if (useChromiumLinker) Linker.finishLibraryLoad();
178
179                if (context != null
180                    && shouldDeleteOldWorkaroundLibraries
181                    && !sNativeLibraryHackWasUsed) {
182                    LibraryLoaderHelper.deleteWorkaroundLibrariesAsynchronously(
183                        context);
184                }
185
186                long stopTime = SystemClock.uptimeMillis();
187                Log.i(TAG, String.format("Time to load native libraries: %d ms (timestamps %d-%d)",
188                        stopTime - startTime,
189                        startTime % 10000,
190                        stopTime % 10000));
191
192                nativeInitCommandLine(CommandLine.getJavaSwitchesOrNull());
193                CommandLine.enableNativeProxy();
194
195                sLoaded = true;
196            }
197        } catch (UnsatisfiedLinkError e) {
198            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_LOAD_FAILED, e);
199        }
200        // Check that the version of the library we have loaded matches the version we expect
201        Log.i(TAG, String.format(
202                "Expected native library version number \"%s\"," +
203                        "actual native library version number \"%s\"",
204                NativeLibraries.VERSION_NUMBER,
205                nativeGetVersionNumber()));
206        if (!NativeLibraries.VERSION_NUMBER.equals(nativeGetVersionNumber())) {
207            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_NATIVE_LIBRARY_WRONG_VERSION);
208        }
209    }
210
211    // Invoke base::android::LibraryLoaded in library_loader_hooks.cc
212    private static void initializeAlreadyLocked() throws ProcessInitException {
213        if (sInitialized) {
214            return;
215        }
216
217        if (!nativeLibraryLoaded()) {
218            Log.e(TAG, "error calling nativeLibraryLoaded");
219            throw new ProcessInitException(LoaderErrors.LOADER_ERROR_FAILED_TO_REGISTER_JNI);
220        }
221        // From this point on, native code is ready to use and checkIsReady()
222        // shouldn't complain from now on (and in fact, it's used by the
223        // following calls).
224        sInitialized = true;
225
226        // From now on, keep tracing in sync with native.
227        TraceEvent.registerNativeEnabledObserver();
228    }
229
230    // Called after all native initializations are complete.
231    public static void onNativeInitializationComplete() {
232        // Record histogram for the Chromium linker.
233        if (Linker.isUsed()) {
234            nativeRecordChromiumAndroidLinkerHistogram(Linker.loadAtFixedAddressFailed(),
235                    SysUtils.isLowEndDevice());
236        }
237
238        nativeRecordNativeLibraryHack(sNativeLibraryHackWasUsed);
239    }
240
241    private static native void nativeInitCommandLine(String[] initCommandLine);
242
243    // Only methods needed before or during normal JNI registration are during System.OnLoad.
244    // nativeLibraryLoaded is then called to register everything else.  This process is called
245    // "initialization".  This method will be mapped (by generated code) to the LibraryLoaded
246    // definition in base/android/library_loader/library_loader_hooks.cc.
247    //
248    // Return true on success and false on failure.
249    private static native boolean nativeLibraryLoaded();
250
251    // Method called to record statistics about the Chromium linker operation,
252    // i.e. whether the library failed to be loaded at a fixed address, and
253    // whether the device is 'low-memory'.
254    private static native void nativeRecordChromiumAndroidLinkerHistogram(
255            boolean loadedAtFixedAddressFailed,
256            boolean isLowMemoryDevice);
257
258    // Get the version of the native library. This is needed so that we can check we
259    // have the right version before initializing the (rest of the) JNI.
260    private static native String nativeGetVersionNumber();
261
262    private static native void nativeRecordNativeLibraryHack(boolean usedHack);
263}
264