Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.webkit;
     18 
     19 import android.app.LoadedApk;
     20 import android.content.pm.ApplicationInfo;
     21 import android.content.pm.PackageInfo;
     22 import android.os.AsyncTask;
     23 import android.os.Build;
     24 import android.os.ChildZygoteProcess;
     25 import android.os.Process;
     26 import android.os.ZygoteProcess;
     27 import android.text.TextUtils;
     28 import android.util.Log;
     29 
     30 import com.android.internal.annotations.GuardedBy;
     31 
     32 import java.io.File;
     33 import java.util.ArrayList;
     34 import java.util.List;
     35 
     36 /** @hide */
     37 public class WebViewZygote {
     38     private static final String LOGTAG = "WebViewZygote";
     39 
     40     /**
     41      * Lock object that protects all other static members.
     42      */
     43     private static final Object sLock = new Object();
     44 
     45     /**
     46      * Instance that maintains the socket connection to the zygote. This is {@code null} if the
     47      * zygote is not running or is not connected.
     48      */
     49     @GuardedBy("sLock")
     50     private static ChildZygoteProcess sZygote;
     51 
     52     /**
     53      * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
     54      */
     55     @GuardedBy("sLock")
     56     private static PackageInfo sPackage;
     57 
     58     /**
     59      * Original ApplicationInfo for the selected WebView package before stub fixup. This is set from
     60      * #onWebViewProviderChanged().
     61      */
     62     @GuardedBy("sLock")
     63     private static ApplicationInfo sPackageOriginalAppInfo;
     64 
     65     /**
     66      * Flag for whether multi-process WebView is enabled. If this is {@code false}, the zygote
     67      * will not be started.
     68      */
     69     @GuardedBy("sLock")
     70     private static boolean sMultiprocessEnabled = false;
     71 
     72     public static ZygoteProcess getProcess() {
     73         synchronized (sLock) {
     74             if (sZygote != null) return sZygote;
     75 
     76             connectToZygoteIfNeededLocked();
     77             return sZygote;
     78         }
     79     }
     80 
     81     public static String getPackageName() {
     82         synchronized (sLock) {
     83             return sPackage.packageName;
     84         }
     85     }
     86 
     87     public static boolean isMultiprocessEnabled() {
     88         synchronized (sLock) {
     89             return sMultiprocessEnabled && sPackage != null;
     90         }
     91     }
     92 
     93     public static void setMultiprocessEnabled(boolean enabled) {
     94         synchronized (sLock) {
     95             sMultiprocessEnabled = enabled;
     96 
     97             // When toggling between multi-process being on/off, start or stop the
     98             // zygote. If it is enabled and the zygote is not yet started, launch it.
     99             // Otherwise, kill it. The name may be null if the package information has
    100             // not yet been resolved.
    101             if (enabled) {
    102                 // Run on a background thread as this waits for the zygote to start and we don't
    103                 // want to block the caller on this. It's okay if this is delayed as anyone trying
    104                 // to use the zygote will call it first anyway.
    105                 AsyncTask.THREAD_POOL_EXECUTOR.execute(WebViewZygote::getProcess);
    106             } else {
    107                 // No need to run this in the background, it's very brief.
    108                 stopZygoteLocked();
    109             }
    110         }
    111     }
    112 
    113     public static void onWebViewProviderChanged(PackageInfo packageInfo,
    114                                                 ApplicationInfo originalAppInfo) {
    115         synchronized (sLock) {
    116             sPackage = packageInfo;
    117             sPackageOriginalAppInfo = originalAppInfo;
    118 
    119             // If multi-process is not enabled, then do not start the zygote service.
    120             if (!sMultiprocessEnabled) {
    121                 return;
    122             }
    123 
    124             stopZygoteLocked();
    125         }
    126     }
    127 
    128     @GuardedBy("sLock")
    129     private static void stopZygoteLocked() {
    130         if (sZygote != null) {
    131             // Close the connection and kill the zygote process. This will not cause
    132             // child processes to be killed by itself. But if this is called in response to
    133             // setMultiprocessEnabled() or onWebViewProviderChanged(), the WebViewUpdater
    134             // will kill all processes that depend on the WebView package.
    135             sZygote.close();
    136             Process.killProcess(sZygote.getPid());
    137             sZygote = null;
    138         }
    139     }
    140 
    141     @GuardedBy("sLock")
    142     private static void connectToZygoteIfNeededLocked() {
    143         if (sZygote != null) {
    144             return;
    145         }
    146 
    147         if (sPackage == null) {
    148             Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
    149             return;
    150         }
    151 
    152         try {
    153             sZygote = Process.zygoteProcess.startChildZygote(
    154                     "com.android.internal.os.WebViewZygoteInit",
    155                     "webview_zygote",
    156                     Process.WEBVIEW_ZYGOTE_UID,
    157                     Process.WEBVIEW_ZYGOTE_UID,
    158                     null,  // gids
    159                     0,  // runtimeFlags
    160                     "webview_zygote",  // seInfo
    161                     sPackage.applicationInfo.primaryCpuAbi,  // abi
    162                     null);  // instructionSet
    163 
    164             // All the work below is usually done by LoadedApk, but the zygote can't talk to
    165             // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
    166             // doesn't have an ActivityThread and can't use Binder.
    167             // Instead, figure out the paths here, in the system server where we have access to
    168             // the package manager. Reuse the logic from LoadedApk to determine the correct
    169             // paths and pass them to the zygote as strings.
    170             final List<String> zipPaths = new ArrayList<>(10);
    171             final List<String> libPaths = new ArrayList<>(10);
    172             LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
    173             final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
    174             final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
    175                     TextUtils.join(File.pathSeparator, zipPaths);
    176 
    177             String libFileName = WebViewFactory.getWebViewLibrary(sPackage.applicationInfo);
    178 
    179             // In the case where the ApplicationInfo has been modified by the stub WebView,
    180             // we need to use the original ApplicationInfo to determine what the original classpath
    181             // would have been to use as a cache key.
    182             LoadedApk.makePaths(null, false, sPackageOriginalAppInfo, zipPaths, null);
    183             final String cacheKey = (zipPaths.size() == 1) ? zipPaths.get(0) :
    184                     TextUtils.join(File.pathSeparator, zipPaths);
    185 
    186             ZygoteProcess.waitForConnectionToZygote(sZygote.getPrimarySocketAddress());
    187 
    188             Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
    189             sZygote.preloadPackageForAbi(zip, librarySearchPath, libFileName, cacheKey,
    190                                          Build.SUPPORTED_ABIS[0]);
    191         } catch (Exception e) {
    192             Log.e(LOGTAG, "Error connecting to webview zygote", e);
    193             stopZygoteLocked();
    194         }
    195     }
    196 }
    197