美文网首页
Android Webview 加载流程

Android Webview 加载流程

作者: yangweigbh | 来源:发表于2017-05-23 18:44 被阅读1305次

    App要使用webview,需要将webview的so库加载到自己的虚拟内存空间,so库代码区是可以共享的,但是数据区不能共享,但是数据区有1.28M是在重定位后就是只读的(RELRO)。如果每个App都去创建自己的这部分数据,就会造成浪费

    Paste_Image.png

    android做了一个优化,就是每个应用加载webview的so库都加载到同一base上,然后系统启动后由SystemServer fork出一个进程,先把so库加载到base上,然后把这1.28M(RELRO)dump到文件里,以后每个应用只要映射这个文件就可以了,这样可以节约空间

    Paste_Image.png

    ZygoteInit.java

        static void preload() {
            ...
            // Ask the WebViewFactory to do any initialization that must run in the zygote process,
            // for memory sharing purposes.
            WebViewFactory.prepareWebViewInZygote();
            ...
        }
    

    WebViewFactory.java

        public static void prepareWebViewInZygote() {
            try {
                System.loadLibrary("webviewchromium_loader");
                long addressSpaceToReserve =
                        SystemProperties.getLong(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                        CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
                sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
    
                if (sAddressSpaceReserved) {
                    if (DEBUG) {
                        Log.v(LOGTAG, "address space reserved: " + addressSpaceToReserve + " bytes");
                    }
                } else {
                    Log.e(LOGTAG, "reserving " + addressSpaceToReserve +
                            " bytes of address space failed");
                }
            } catch (Throwable t) {
                // Log and discard errors at this stage as we must not crash the zygote.
                Log.e(LOGTAG, "error preparing native loader", t);
            }
        }
    

    加载webviewchromium_loader, 通过nativeReserveAddressSpace在zygote进程中预留一段内存。

    loader.cpp

    jboolean DoReserveAddressSpace(jlong size) {
      size_t vsize = static_cast<size_t>(size);
    
      void* addr = mmap(NULL, vsize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
      if (addr == MAP_FAILED) {
        ALOGE("Failed to reserve %zd bytes of address space for future load of "
              "libwebviewchromium.so: %s",
              vsize, strerror(errno));
        return JNI_FALSE;
      }
      gReservedAddress = addr;
      gReservedSize = vsize;
      ALOGV("Reserved %zd bytes at %p", vsize, addr);
      return JNI_TRUE;
    }
    

    SystemServer.java

    private void startOtherServices() {
       if (!mOnlyCore) {
         Slog.i(TAG, "WebViewFactory preparation");
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "WebViewFactoryPreparation");
         mWebViewUpdateService.prepareWebViewInSystemServer();
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
       }
    }
    

    WebViewUpdateService.java

        public void prepareWebViewInSystemServer() {
            mImpl.prepareWebViewInSystemServer();
        }
    

    WebViewUpdateServiceImpl.java

        void prepareWebViewInSystemServer() {
            updateFallbackStateOnBoot();
            mWebViewUpdater.prepareWebViewInSystemServer();
        }
    
          private void updateFallbackStateOnBoot() {
            if (!mSystemInterface.isFallbackLogicEnabled()) return;
    
            WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
            updateFallbackState(webviewProviders, true);
        }
    

    SystemImpl.java

        private SystemImpl() {
            int numFallbackPackages = 0;
            int numAvailableByDefaultPackages = 0;
            int numAvByDefaultAndNotFallback = 0;
            XmlResourceParser parser = null;
            List<WebViewProviderInfo> webViewProviders = new ArrayList<WebViewProviderInfo>();
            try {
                parser = AppGlobals.getInitialApplication().getResources().getXml(
                        com.android.internal.R.xml.config_webview_packages);
                XmlUtils.beginDocument(parser, TAG_START);
                while(true) {
                    XmlUtils.nextElement(parser);
                    String element = parser.getName();
                    if (element == null) {
                        break;
                    }
                    if (element.equals(TAG_WEBVIEW_PROVIDER)) {
                        String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME);
                        if (packageName == null) {
                            throw new AndroidRuntimeException(
                                    "WebView provider in framework resources missing package name");
                        }
                        String description = parser.getAttributeValue(null, TAG_DESCRIPTION);
                        if (description == null) {
                            throw new AndroidRuntimeException(
                                    "WebView provider in framework resources missing description");
                        }
                        boolean availableByDefault = "true".equals(
                                parser.getAttributeValue(null, TAG_AVAILABILITY));
                        boolean isFallback = "true".equals(
                                parser.getAttributeValue(null, TAG_FALLBACK));
                        WebViewProviderInfo currentProvider = new WebViewProviderInfo(
                                packageName, description, availableByDefault, isFallback,
                                readSignatures(parser));
                        if (currentProvider.isFallback) {
                            numFallbackPackages++;
                            if (!currentProvider.availableByDefault) {
                                throw new AndroidRuntimeException(
                                        "Each WebView fallback package must be available by default.");
                            }
                            if (numFallbackPackages > 1) {
                                throw new AndroidRuntimeException(
                                        "There can be at most one WebView fallback package.");
                            }
                        }
                        if (currentProvider.availableByDefault) {
                            numAvailableByDefaultPackages++;
                            if (!currentProvider.isFallback) {
                                numAvByDefaultAndNotFallback++;
                            }
                        }
                        webViewProviders.add(currentProvider);
                    }
                    else {
                        Log.e(TAG, "Found an element that is not a WebView provider");
                    }
                }
            } catch (XmlPullParserException | IOException e) {
                throw new AndroidRuntimeException("Error when parsing WebView config " + e);
            } finally {
                if (parser != null) parser.close();
            }
            if (numAvailableByDefaultPackages == 0) {
                throw new AndroidRuntimeException("There must be at least one WebView package "
                        + "that is available by default");
            }
            if (numAvByDefaultAndNotFallback == 0) {
                throw new AndroidRuntimeException("There must be at least one WebView package "
                        + "that is available by default and not a fallback");
            }
            mWebViewProviderPackages =
                    webViewProviders.toArray(new WebViewProviderInfo[webViewProviders.size()]);
        }
    

    SystemImpl在创建时会从config_webview_packages中获取webview provider,默认是com.android.webview

    config_webview_packages.xml

    <webviewproviders>
        <!-- The default WebView implementation -->
        <webviewprovider description="Android WebView" packageName="com.android.webview" availableByDefault="true">
        </webviewprovider>
    </webviewproviders>
    

    SystemImpl.java

        @Override
        public WebViewProviderInfo[] getWebViewPackages() {
            return mWebViewProviderPackages;
        }
    

    WebViewUpdateServiceImpl.java

        private void updateFallbackState(WebViewProviderInfo[] webviewProviders, boolean isBoot) {
            // If there exists a valid and enabled non-fallback package - disable the fallback
            // package, otherwise, enable it.
            WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
            if (fallbackProvider == null) return;
            boolean existsValidNonFallbackProvider = existsValidNonFallbackProvider(webviewProviders);
    
            boolean isFallbackEnabled = false;
            try {
                isFallbackEnabled = isEnabledPackage(
                        mSystemInterface.getPackageInfoForProvider(fallbackProvider));
            } catch (NameNotFoundException e) {
                // No fallback package installed -> early out.
                return;
            }
    
            if (existsValidNonFallbackProvider
                    // During an OTA the primary user's WebView state might differ from other users', so
                    // ignore the state of that user during boot.
                    && (isFallbackEnabled || isBoot)) {
                mSystemInterface.uninstallAndDisablePackageForAllUsers(mContext,
                        fallbackProvider.packageName);
            } else if (!existsValidNonFallbackProvider
                    // During an OTA the primary user's WebView state might differ from other users', so
                    // ignore the state of that user during boot.
                    && (!isFallbackEnabled || isBoot)) {
                // Enable the fallback package for all users.
                mSystemInterface.enablePackageForAllUsers(mContext,
                        fallbackProvider.packageName, true);
            }
        }
    

    一般没有fallback的provider,直接return。

    回到WebViewUpdateServiceImpl的prepareWebViewInSystemServer

    mWebViewUpdater.prepareWebViewInSystemServer

            public void prepareWebViewInSystemServer() {
                try {
                    synchronized(mLock) {
                        mCurrentWebViewPackage = findPreferredWebViewPackage();
                        // Don't persist the user-chosen setting across boots if the package being
                        // chosen is not used (could be disabled or uninstalled) so that the user won't
                        // be surprised by the device switching to using a certain webview package,
                        // that was uninstalled/disabled a long time ago, if it is installed/enabled
                        // again.
                        mSystemInterface.updateUserSetting(mContext,
                                mCurrentWebViewPackage.packageName);
                        onWebViewProviderChanged(mCurrentWebViewPackage);
                    }
                } catch (Throwable t) {
                    // Log and discard errors at this stage as we must not crash the system server.
                    Slog.e(TAG, "error preparing webview provider from system server", t);
                }
            }
    

    从SystemImpl提供的provider中,如果有用户设定的,就返回之,否则返回availableByDefault并且安装,endabled provider

            private PackageInfo findPreferredWebViewPackage() {
                ProviderAndPackageInfo[] providers =
                    getValidWebViewPackagesAndInfos(false /* onlyInstalled */);
    
                String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
    
                // If the user has chosen provider, use that
                for (ProviderAndPackageInfo providerAndPackage : providers) {
                    if (providerAndPackage.provider.packageName.equals(userChosenProvider)
                            && isInstalledPackage(providerAndPackage.packageInfo)
                            && isEnabledPackage(providerAndPackage.packageInfo)) {
                        return providerAndPackage.packageInfo;
                    }
                }
    
                // User did not choose, or the choice failed; use the most stable provider that is
                // installed and enabled for the device owner, and available by default (not through
                // user choice).
                for (ProviderAndPackageInfo providerAndPackage : providers) {
                    if (providerAndPackage.provider.availableByDefault
                            && isInstalledPackage(providerAndPackage.packageInfo)
                            && isEnabledPackage(providerAndPackage.packageInfo)) {
                        return providerAndPackage.packageInfo;
                    }
                }
    
                // Could not find any installed and enabled package either, use the most stable and
                // default-available provider.
                for (ProviderAndPackageInfo providerAndPackage : providers) {
                    if (providerAndPackage.provider.availableByDefault) {
                        return providerAndPackage.packageInfo;
                    }
                }
    
                mAnyWebViewInstalled = false;
                throw new WebViewFactory.MissingWebViewPackageException(
                        "Could not find a loadable WebView package");
            }
    
    private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) {
                WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
                List<ProviderAndPackageInfo> providers = new ArrayList<>();
                for(int n = 0; n < allProviders.length; n++) {
                    try {
                        PackageInfo packageInfo =
                            mSystemInterface.getPackageInfoForProvider(allProviders[n]);
                        if ((!onlyInstalled || isInstalledPackage(packageInfo))
                                && isValidProvider(allProviders[n], packageInfo)) {
                            providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
                        }
                    } catch (NameNotFoundException e) {
                        // Don't add non-existent packages
                    }
                }
                return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
            }
    
          onWebViewProviderChanged(mCurrentWebViewPackage);
    
            private void onWebViewProviderChanged(PackageInfo newPackage) {
                synchronized(mLock) {
                    mAnyWebViewInstalled = true;
                    if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
                        mCurrentWebViewPackage = newPackage;
    
                        // The relro creations might 'finish' (not start at all) before
                        // WebViewFactory.onWebViewProviderChanged which means we might not know the
                        // number of started creations before they finish.
                        mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
                        mNumRelroCreationsFinished = 0;
                        mNumRelroCreationsStarted =
                            mSystemInterface.onWebViewProviderChanged(newPackage);
                        // If the relro creations finish before we know the number of started creations
                        // we will have to do any cleanup/notifying here.
                        checkIfRelrosDoneLocked();
                    } else {
                        mWebViewPackageDirty = true;
                    }
                }
            }
    

    SystemImpl.java

        @Override
        public int onWebViewProviderChanged(PackageInfo packageInfo) {
            return WebViewFactory.onWebViewProviderChanged(packageInfo);
        }
    

    WebViewFactory.java

        public static int onWebViewProviderChanged(PackageInfo packageInfo) {
            String[] nativeLibs = null;
            try {
                nativeLibs = WebViewFactory.getWebViewNativeLibraryPaths(packageInfo);
                if (nativeLibs != null) {
                    long newVmSize = 0L;
    
                    for (String path : nativeLibs) {
                        if (path == null || TextUtils.isEmpty(path)) continue;
                        if (DEBUG) Log.d(LOGTAG, "Checking file size of " + path);
                        File f = new File(path);
                        if (f.exists()) {
                            newVmSize = Math.max(newVmSize, f.length());
                            continue;
                        }
                        if (path.contains("!/")) {
                            String[] split = TextUtils.split(path, "!/");
                            if (split.length == 2) {
                                try (ZipFile z = new ZipFile(split[0])) {
                                    ZipEntry e = z.getEntry(split[1]);
                                    if (e != null && e.getMethod() == ZipEntry.STORED) {
                                        newVmSize = Math.max(newVmSize, e.getSize());
                                        continue;
                                    }
                                }
                                catch (IOException e) {
                                    Log.e(LOGTAG, "error reading APK file " + split[0] + ", ", e);
                                }
                            }
                        }
                        Log.e(LOGTAG, "error sizing load for " + path);
                    }
    
                    if (DEBUG) {
                        Log.v(LOGTAG, "Based on library size, need " + newVmSize +
                                " bytes of address space.");
                    }
                    // The required memory can be larger than the file on disk (due to .bss), and an
                    // upgraded version of the library will likely be larger, so always attempt to
                    // reserve twice as much as we think to allow for the library to grow during this
                    // boot cycle.
                    newVmSize = Math.max(2 * newVmSize, CHROMIUM_WEBVIEW_DEFAULT_VMSIZE_BYTES);
                    Log.d(LOGTAG, "Setting new address space to " + newVmSize);
                    SystemProperties.set(CHROMIUM_WEBVIEW_VMSIZE_SIZE_PROPERTY,
                            Long.toString(newVmSize));
                }
            } catch (Throwable t) {
                // Log and discard errors at this stage as we must not crash the system server.
                Log.e(LOGTAG, "error preparing webview native library", t);
            }
            return prepareWebViewInSystemServer(nativeLibs);
        }
    

    从WebViewProvider里取出nativelib path

        private static int prepareWebViewInSystemServer(String[] nativeLibraryPaths) {
            if (DEBUG) Log.v(LOGTAG, "creating relro files");
            int numRelros = 0;
    
            // We must always trigger createRelRo regardless of the value of nativeLibraryPaths. Any
            // unexpected values will be handled there to ensure that we trigger notifying any process
            // waiting on relro creation.
            if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
                if (DEBUG) Log.v(LOGTAG, "Create 32 bit relro");
                createRelroFile(false /* is64Bit */, nativeLibraryPaths);
                numRelros++;
            }
    
            if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
                if (DEBUG) Log.v(LOGTAG, "Create 64 bit relro");
                createRelroFile(true /* is64Bit */, nativeLibraryPaths);
                numRelros++;
            }
            return numRelros;
        }
    
        private static void createRelroFile(final boolean is64Bit, String[] nativeLibraryPaths) {
            final String abi =
                    is64Bit ? Build.SUPPORTED_64_BIT_ABIS[0] : Build.SUPPORTED_32_BIT_ABIS[0];
    
            // crashHandler is invoked by the ActivityManagerService when the isolated process crashes.
            Runnable crashHandler = new Runnable() {
                @Override
                public void run() {
                    try {
                        Log.e(LOGTAG, "relro file creator for " + abi + " crashed. Proceeding without");
                        getUpdateService().notifyRelroCreationCompleted();
                    } catch (RemoteException e) {
                        Log.e(LOGTAG, "Cannot reach WebViewUpdateService. " + e.getMessage());
                    }
                }
            };
    
            try {
                if (nativeLibraryPaths == null
                        || nativeLibraryPaths[0] == null || nativeLibraryPaths[1] == null) {
                    throw new IllegalArgumentException(
                            "Native library paths to the WebView RelRo process must not be null!");
                }
                int pid = LocalServices.getService(ActivityManagerInternal.class).startIsolatedProcess(
                        RelroFileCreator.class.getName(), nativeLibraryPaths, "WebViewLoader-" + abi, abi,
                        Process.SHARED_RELRO_UID, crashHandler);
                if (pid <= 0) throw new Exception("Failed to start the relro file creator process");
            } catch (Throwable t) {
                // Log and discard errors as we must not crash the system server.
                Log.e(LOGTAG, "error starting relro file creator for abi " + abi, t);
                crashHandler.run();
            }
        }
    

    启动一个isolated的进程,运行RelroFileCreator的main方法

        private static class RelroFileCreator {
            // Called in an unprivileged child process to create the relro file.
            public static void main(String[] args) {
                boolean result = false;
                boolean is64Bit = VMRuntime.getRuntime().is64Bit();
                try{
                    if (args.length != 2 || args[0] == null || args[1] == null) {
                        Log.e(LOGTAG, "Invalid RelroFileCreator args: " + Arrays.toString(args));
                        return;
                    }
                    Log.v(LOGTAG, "RelroFileCreator (64bit = " + is64Bit + "), " +
                            " 32-bit lib: " + args[0] + ", 64-bit lib: " + args[1]);
                    if (!sAddressSpaceReserved) {
                        Log.e(LOGTAG, "can't create relro file; address space not reserved");
                        return;
                    }
                    result = nativeCreateRelroFile(args[0] /* path32 */,
                                                   args[1] /* path64 */,
                                                   CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                                   CHROMIUM_WEBVIEW_NATIVE_RELRO_64);
                    if (result && DEBUG) Log.v(LOGTAG, "created relro file");
                } finally {
                    // We must do our best to always notify the update service, even if something fails.
                    try {
                        getUpdateService().notifyRelroCreationCompleted();
                    } catch (RemoteException e) {
                        Log.e(LOGTAG, "error notifying update service", e);
                    }
    
                    if (!result) Log.e(LOGTAG, "failed to create relro file");
    
                    // Must explicitly exit or else this process will just sit around after we return.
                    System.exit(0);
                }
            }
        }
    

    在native层创建relro文件,然后加载native库,将GNU_RELRO数据段dump到relro文件里

    loader.cpp

    jboolean DoCreateRelroFile(const char* lib, const char* relro) {
      // Try to unlink the old file, since if this is being called, the old one is
      // obsolete.
      if (unlink(relro) != 0 && errno != ENOENT) {
        // If something went wrong other than the file not existing, log a warning
        // but continue anyway in the hope that we can successfully overwrite the
        // existing file with rename() later.
        ALOGW("Failed to unlink old file %s: %s", relro, strerror(errno));
      }
      static const char tmpsuffix[] = ".XXXXXX";
      char relro_tmp[strlen(relro) + sizeof(tmpsuffix)];
      strlcpy(relro_tmp, relro, sizeof(relro_tmp));
      strlcat(relro_tmp, tmpsuffix, sizeof(relro_tmp));
      int tmp_fd = TEMP_FAILURE_RETRY(mkstemp(relro_tmp));
      if (tmp_fd == -1) {
        ALOGE("Failed to create temporary file %s: %s", relro_tmp, strerror(errno));
        return JNI_FALSE;
      }
      android_dlextinfo extinfo;
      extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO;
      extinfo.reserved_addr = gReservedAddress;
      extinfo.reserved_size = gReservedSize;
      extinfo.relro_fd = tmp_fd;
      void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
      int close_result = close(tmp_fd);
      if (handle == NULL) {
        ALOGE("Failed to load library %s: %s", lib, dlerror());
        unlink(relro_tmp);
        return JNI_FALSE;
      }
      if (close_result != 0 ||
          chmod(relro_tmp, S_IRUSR | S_IRGRP | S_IROTH) != 0 ||
          rename(relro_tmp, relro) != 0) {
        ALOGE("Failed to update relro file %s: %s", relro, strerror(errno));
        unlink(relro_tmp);
        return JNI_FALSE;
      }
      ALOGV("Created relro file %s for library %s", relro, lib);
      return JNI_TRUE;
    }
    

    创建webview时

    
        protected WebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,  
                Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) {  
            ......  
      
            ensureProviderCreated();  
            mProvider.init(javaScriptInterfaces, privateBrowsing);  
      
            ......  
        } 
      
        private void ensureProviderCreated() {  
            checkThread();  
            if (mProvider == null) {  
                // As this can get called during the base class constructor chain, pass the minimum  
                // number of dependencies here; the rest are deferred to init().  
                mProvider = getFactory().createWebView(this, new PrivateAccess());  
            }  
        }  
      
    public class WebView extends AbsoluteLayout  
            implements ViewTreeObserver.OnGlobalFocusChangeListener,  
            ViewGroup.OnHierarchyChangeListener, ViewDebug.HierarchyHandler {  
        ......  
      
        private static synchronized WebViewFactoryProvider getFactory() {  
            return WebViewFactory.getProvider();  
        }  
      
        ......  
    }  
    
        private static Class<WebViewFactoryProvider> getProviderClass() {
                    ....
                    loadNativeLibrary(clazzLoader);
                    .....
        }
    
        // Assumes that we have waited for relro creation and set sPackageInfo
        private static int loadNativeLibrary(ClassLoader clazzLoader) {
            if (!sAddressSpaceReserved) {
                Log.e(LOGTAG, "can't load with relro file; address space not reserved");
                return LIBLOAD_ADDRESS_SPACE_NOT_RESERVED;
            }
    
            String[] args = getWebViewNativeLibraryPaths(sPackageInfo);
            int result = nativeLoadWithRelroFile(args[0] /* path32 */,
                                                 args[1] /* path64 */,
                                                 CHROMIUM_WEBVIEW_NATIVE_RELRO_32,
                                                 CHROMIUM_WEBVIEW_NATIVE_RELRO_64,
                                                 clazzLoader);
            if (result != LIBLOAD_SUCCESS) {
                Log.w(LOGTAG, "failed to load with relro file, proceeding without");
            } else if (DEBUG) {
                Log.v(LOGTAG, "loaded with relro file");
            }
            return result;
        }
    

    函数DoLoadWithRelroFile会将告诉函数android_dlopen_ext在加载Chromium动态库的时候,将参数relro描述的Chromium GNU_RELRO Section文件内存映射到内存来,并且代替掉已经加载的Chromium动态库的GNU_RELRO Section。这是通过将指定一个ANDROID_DLEXT_USE_RELRO标志实现的

    相关文章

      网友评论

          本文标题:Android Webview 加载流程

          本文链接:https://www.haomeiwen.com/subject/utzwxxtx.html