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.PackageInfo;
     21 import android.os.Build;
     22 import android.os.SystemService;
     23 import android.os.ZygoteProcess;
     24 import android.text.TextUtils;
     25 import android.util.AndroidRuntimeException;
     26 import android.util.Log;
     27 
     28 import com.android.internal.annotations.GuardedBy;
     29 
     30 import java.io.File;
     31 import java.io.IOException;
     32 import java.util.ArrayList;
     33 import java.util.Arrays;
     34 import java.util.List;
     35 import java.util.concurrent.TimeoutException;
     36 
     37 /** @hide */
     38 public class WebViewZygote {
     39     private static final String LOGTAG = "WebViewZygote";
     40 
     41     private static final String WEBVIEW_ZYGOTE_SERVICE_32 = "webview_zygote32";
     42     private static final String WEBVIEW_ZYGOTE_SERVICE_64 = "webview_zygote64";
     43     private static final String WEBVIEW_ZYGOTE_SOCKET = "webview_zygote";
     44 
     45     /**
     46      * Lock object that protects all other static members.
     47      */
     48     private static final Object sLock = new Object();
     49 
     50     /**
     51      * Instance that maintains the socket connection to the zygote. This is null if the zygote
     52      * is not running or is not connected.
     53      */
     54     @GuardedBy("sLock")
     55     private static ZygoteProcess sZygote;
     56 
     57     /**
     58      * Variable that allows us to determine whether the WebView zygote Service has already been
     59      * started.
     60      */
     61     @GuardedBy("sLock")
     62     private static boolean sStartedService = false;
     63 
     64     /**
     65      * Information about the selected WebView package. This is set from #onWebViewProviderChanged().
     66      */
     67     @GuardedBy("sLock")
     68     private static PackageInfo sPackage;
     69 
     70     /**
     71      * Cache key for the selected WebView package's classloader. This is set from
     72      * #onWebViewProviderChanged().
     73      */
     74     @GuardedBy("sLock")
     75     private static String sPackageCacheKey;
     76 
     77     /**
     78      * Flag for whether multi-process WebView is enabled. If this is false, the zygote
     79      * will not be started.
     80      */
     81     @GuardedBy("sLock")
     82     private static boolean sMultiprocessEnabled = false;
     83 
     84     public static ZygoteProcess getProcess() {
     85         synchronized (sLock) {
     86             if (sZygote != null) return sZygote;
     87 
     88             waitForServiceStartAndConnect();
     89             return sZygote;
     90         }
     91     }
     92 
     93     public static String getPackageName() {
     94         synchronized (sLock) {
     95             return sPackage.packageName;
     96         }
     97     }
     98 
     99     public static boolean isMultiprocessEnabled() {
    100         synchronized (sLock) {
    101             return sMultiprocessEnabled && sPackage != null;
    102         }
    103     }
    104 
    105     public static void setMultiprocessEnabled(boolean enabled) {
    106         synchronized (sLock) {
    107             sMultiprocessEnabled = enabled;
    108 
    109             // When toggling between multi-process being on/off, start or stop the
    110             // service. If it is enabled and the zygote is not yet started, bring up the service.
    111             // Otherwise, bring down the service. The name may be null if the package
    112             // information has not yet been resolved.
    113             final String serviceName = getServiceNameLocked();
    114             if (serviceName == null) return;
    115 
    116             if (enabled) {
    117                 if (!sStartedService) {
    118                     SystemService.start(serviceName);
    119                     sStartedService = true;
    120                 }
    121             } else {
    122                 SystemService.stop(serviceName);
    123                 sStartedService = false;
    124                 sZygote = null;
    125             }
    126         }
    127     }
    128 
    129     public static void onWebViewProviderChanged(PackageInfo packageInfo, String cacheKey) {
    130         synchronized (sLock) {
    131             sPackage = packageInfo;
    132             sPackageCacheKey = cacheKey;
    133 
    134             // If multi-process is not enabled, then do not start the zygote service.
    135             if (!sMultiprocessEnabled) {
    136                 return;
    137             }
    138 
    139             final String serviceName = getServiceNameLocked();
    140             sZygote = null;
    141 
    142             // The service may enter the RUNNING state before it opens the socket,
    143             // so connectToZygoteIfNeededLocked() may still fail.
    144             if (SystemService.isStopped(serviceName)) {
    145                 SystemService.start(serviceName);
    146             } else {
    147                 SystemService.restart(serviceName);
    148             }
    149             sStartedService = true;
    150         }
    151     }
    152 
    153     private static void waitForServiceStartAndConnect() {
    154         if (!sStartedService) {
    155             throw new AndroidRuntimeException("Tried waiting for the WebView Zygote Service to " +
    156                     "start running without first starting the service.");
    157         }
    158 
    159         String serviceName;
    160         synchronized (sLock) {
    161             serviceName = getServiceNameLocked();
    162         }
    163         try {
    164             SystemService.waitForState(serviceName, SystemService.State.RUNNING, 5000);
    165         } catch (TimeoutException e) {
    166             Log.e(LOGTAG, "Timed out waiting for " + serviceName);
    167             return;
    168         }
    169 
    170         synchronized (sLock) {
    171             connectToZygoteIfNeededLocked();
    172         }
    173     }
    174 
    175     @GuardedBy("sLock")
    176     private static String getServiceNameLocked() {
    177         if (sPackage == null)
    178             return null;
    179 
    180         if (Arrays.asList(Build.SUPPORTED_64_BIT_ABIS).contains(
    181                     sPackage.applicationInfo.primaryCpuAbi)) {
    182             return WEBVIEW_ZYGOTE_SERVICE_64;
    183         }
    184 
    185         return WEBVIEW_ZYGOTE_SERVICE_32;
    186     }
    187 
    188     @GuardedBy("sLock")
    189     private static void connectToZygoteIfNeededLocked() {
    190         if (sZygote != null) {
    191             return;
    192         }
    193 
    194         if (sPackage == null) {
    195             Log.e(LOGTAG, "Cannot connect to zygote, no package specified");
    196             return;
    197         }
    198 
    199         final String serviceName = getServiceNameLocked();
    200         if (!SystemService.isRunning(serviceName)) {
    201             Log.e(LOGTAG, serviceName + " is not running");
    202             return;
    203         }
    204 
    205         try {
    206             sZygote = new ZygoteProcess(WEBVIEW_ZYGOTE_SOCKET, null);
    207 
    208             // All the work below is usually done by LoadedApk, but the zygote can't talk to
    209             // PackageManager or construct a LoadedApk since it's single-threaded pre-fork, so
    210             // doesn't have an ActivityThread and can't use Binder.
    211             // Instead, figure out the paths here, in the system server where we have access to
    212             // the package manager. Reuse the logic from LoadedApk to determine the correct
    213             // paths and pass them to the zygote as strings.
    214             final List<String> zipPaths = new ArrayList<>(10);
    215             final List<String> libPaths = new ArrayList<>(10);
    216             LoadedApk.makePaths(null, false, sPackage.applicationInfo, zipPaths, libPaths);
    217             final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);
    218             final String zip = (zipPaths.size() == 1) ? zipPaths.get(0) :
    219                     TextUtils.join(File.pathSeparator, zipPaths);
    220 
    221             ZygoteProcess.waitForConnectionToZygote(WEBVIEW_ZYGOTE_SOCKET);
    222 
    223             Log.d(LOGTAG, "Preloading package " + zip + " " + librarySearchPath);
    224             sZygote.preloadPackageForAbi(zip, librarySearchPath, sPackageCacheKey,
    225                                          Build.SUPPORTED_ABIS[0]);
    226         } catch (Exception e) {
    227             Log.e(LOGTAG, "Error connecting to " + serviceName, e);
    228             sZygote = null;
    229         }
    230     }
    231 }
    232