Android 处理WebView not install(源码分析定位)

Posted 新根

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 处理WebView not install(源码分析定位)相关的知识,希望对你有一定的参考价值。

最近在处理游戏项目中Bugly上的bug, 发现一个WebView Installed问题,涉及FrameWork层中WebView的部分细节,记录下来。

Bugly上捕捉的日志:

08-27 06:20:16.860 17374 17374 E WebViewFactory: Chromium WebView package does not exist
10708-27 06:20:16.860 17374 17374 E WebViewFactory: android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed
10808-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebViewFactory.getWebViewContextAndSetProvider(WebViewFactory.java:270)
10908-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebViewFactory.getProviderClass(WebViewFactory.java:330)
11008-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:194)
11108-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.getFactory(WebView.java:2325)
11208-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.ensureProviderCreated(WebView.java:2320)
11308-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.setOverScrollMode(WebView.java:2379)
11408-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.View.<init>(View.java:4023)
11508-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.View.<init>(View.java:4146)
11608-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.ViewGroup.<init>(ViewGroup.java:580)
11708-27 06:20:16.860 17374 17374 E WebViewFactory: at android.widget.AbsoluteLayout.<init>(AbsoluteLayout.java:55)
11808-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.<init>(WebView.java:627)
11908-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.<init>(WebView.java:572)
12008-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.<init>(WebView.java:555)
12108-27 06:20:16.860 17374 17374 E WebViewFactory: at android.webkit.WebView.<init>(WebView.java:542)
12208-27 06:20:16.860 17374 17374 E WebViewFactory: at java.lang.reflect.Constructor.newInstance0(Native Method)
12308-27 06:20:16.860 17374 17374 E WebViewFactory: at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
12408-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.createView(LayoutInflater.java:645)
12508-27 06:20:16.860 17374 17374 E WebViewFactory: at com.android.internal.policy.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:58)
12608-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.onCreateView(LayoutInflater.java:717)
12708-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:785)
12808-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:727)
12908-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflate(LayoutInflater.java:858)
13008-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
13108-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflate(LayoutInflater.java:861)
13208-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
13308-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflate(LayoutInflater.java:861)
13408-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
13508-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflate(LayoutInflater.java:861)
13608-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:821)
13708-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.inflate(LayoutInflater.java:518)
13808-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.inflate(LayoutInflater.java:426)
13908-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.LayoutInflater.inflate(LayoutInflater.java:377)
14008-27 06:20:16.860 17374 17374 E WebViewFactory: at android.view.View.inflate(View.java:21010)
14108-27 06:20:16.860 17374 17374 E WebViewFactory: at com.duoku.platform.single.ui.DKVerifyActivity.onCreate(Native Method)
14208-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.Activity.performCreate(Activity.java:6679)
14308-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
14408-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2618)
14508-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2726)
14608-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.ActivityThread.-wrap12(ActivityThread.java)
14708-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1477)
14808-27 06:20:16.860 17374 17374 E WebViewFactory: at android.os.Handler.dispatchMessage(Handler.java:102)
14908-27 06:20:16.860 17374 17374 E WebViewFactory: at android.os.Looper.loop(Looper.java:154)
15008-27 06:20:16.860 17374 17374 E WebViewFactory: at android.app.ActivityThread.main(ActivityThread.java:6119)
15108-27 06:20:16.860 17374 17374 E WebViewFactory: at java.lang.reflect.Method.invoke(Native Method)
15208-27 06:20:16.860 17374 17374 E WebViewFactory: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:969)
15308-27 06:20:16.860 17374 17374 E WebViewFactory: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:859)
设备机型系统版本ROM
NX16A8116KPAndroid 7.1.1,level 25NextBook/NX16A8116KP

1.源码分析

本篇源码,基于Android7.1 来介绍。

WebViewFactory#getWebViewContextAndSetProvider开始看起:

/frameworks/base/core/java/android/webkit/WebViewFactory.java

    private static Context getWebViewContextAndSetProvider() 
        try 
            WebViewProviderResponse response = null;
            response = getUpdateService().waitForAndGetProvider();
       
            if (response.status != LIBLOAD_SUCCESS
                    && response.status != LIBLOAD_FAILED_WAITING_FOR_RELRO) 
					// 报错点
                throw new MissingWebViewPackageException("Failed to load WebView provider: "
                        + getWebViewPreparationErrorReason(response.status));
            
            //... 省略部分源码
            catch (RemoteException | PackageManager.NameNotFoundException e) 
                 throw new MissingWebViewPackageException("Failed to load WebView provider: " + e);
           
    
	public static final int LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES = 4;
	//根据status 获取error msg
	private static String getWebViewPreparationErrorReason(int error) 
       switch (error) 
            case LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES:
                return "No WebView installed"; // 匹配上报错信息
        
        return "Unknown";
    

从上可知,调用WebViewUpdateService #waitForAndGetProvider() 返回status 是4,因此抛出MissingWebViewPackageException异常。

App进程是通过跨进程方式来获取WebView有关数据,接下来看下WebView的Server端WebViewUpdateService

接下来看下waitForAndGetProvider()

/frameworks/base/services/core/java/com/android/server/webkit/WebViewUpdateService.java

        @Override // 该方法是Binder 调用
        public WebViewProviderResponse waitForAndGetProvider() 
            //...
            return WebViewUpdateService.this.mImpl.waitForAndGetProvider();
        

从上可知,WebViewUpdateService 是通过WebViewUpdateServiceImpl对象来真正操作WebView有关数据的。
这里涉及是代理方式,提供Sevice api 层,真正的操作是在Impl中。

/frameworks/base/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java

    WebViewProviderResponse waitForAndGetProvider() 
        return mWebViewUpdater.waitForAndGetProvider();
    

从上可知,WebViewUpdateServiceImpl对象是通过调用WebViewUpdater对象来进行有关操作。

接下来看下,WebViewUpdateServiceImpl#WebViewUpdater静态内部类中:

        private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
		
        public WebViewProviderResponse waitForAndGetProvider() 
            PackageInfo webViewPackage = null;
            final long NS_PER_MS = 1000000;
            final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
            boolean webViewReady = false;
            int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS; // 默认是加载成功的状态
            synchronized (mLock) 
                webViewReady = webViewIsReadyLocked();
                while (!webViewReady)  // 这里通过周期性循环阻塞,等待webview load 状态
                    final long timeNowMs = System.nanoTime() / NS_PER_MS;
                    if (timeNowMs >= timeoutTimeMs) break;
                    try 
                        mLock.wait(timeoutTimeMs - timeNowMs);
                     catch (InterruptedException e) 
                    webViewReady = webViewIsReadyLocked();
                
                // Make sure we return the provider that was used to create the relro file
                webViewPackage = mCurrentWebViewPackage;
                if (webViewReady) 
                 else if (!mAnyWebViewInstalled) 
				    //关键点:发现没有webview install 
                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
                 else 
                     //当前WebView的relro 创建没有完成的状态,超时等待
                    webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO; 
                
            
            if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
            return new WebViewProviderResponse(webViewPackage, webViewStatus);
        

从上可知,会先检查WebView 是否load 状态,若是没有,则会等待1000毫秒,若是还没loady 成功,则会判定为LIBLOAD_FAILED_WAITING_FOR_RELRO
若是没有install,则会判定为LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES

app 端调用webview Server端返回的status 是LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES。因此,WebView Server端 中mAnyWebViewInstalled 是为false。

接下来,全局检索下mAnyWebViewInstalled是如何赋值的。

找到findPreferredWebViewPackage() 中会去对 mAnyWebViewInstalled赋值false。

        private PackageInfo findPreferredWebViewPackage() 
		    //获取到webview 的信息列表
            ProviderAndPackageInfo[] providers =
                getValidWebViewPackagesAndInfos(false /* onlyInstalled */);

            String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
            //选择用户选择的webview
            for (ProviderAndPackageInfo providerAndPackage : providers) 
                if (providerAndPackage.provider.packageName.equals(userChosenProvider)
                        && isInstalledPackage(providerAndPackage.packageInfo)
                        && isEnabledPackage(providerAndPackage.packageInfo)) 
                    return providerAndPackage.packageInfo;
                
            
            //当用户没选择或者选择错误,使用安装可使用且注册条件是availableByDefault=true 的webview(列表中第一个)
            for (ProviderAndPackageInfo providerAndPackage : providers) 
                if (providerAndPackage.provider.availableByDefault
                        && isInstalledPackage(providerAndPackage.packageInfo)
                        && isEnabledPackage(providerAndPackage.packageInfo)) 
                    return providerAndPackage.packageInfo;
                
            

            //当没有可用或者安装的webview,使用默认availableByDefault=true的webview 。
            for (ProviderAndPackageInfo providerAndPackage : providers) 
                if (providerAndPackage.provider.availableByDefault) 
                    return providerAndPackage.packageInfo;
                
            
			//没有availableByDefault=true的情况下,赋值false
            mAnyWebViewInstalled = false;
            throw new WebViewFactory.MissingWebViewPackageException(
                    "Could not find a loadable WebView package");
        

从上可知,当没有availableByDefault=true的情况下, mAnyWebViewInstalled 会被赋值false。

接下来看下,getValidWebViewPackagesAndInfos()获取可用的Webview Provider包信息:

        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)) // 这里传入onlyInstalled为false,添加全部
                        providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
                    
                 catch (NameNotFoundException e) 
                    // Don't add non-existent packages
                
            
            return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
        

SystemImplSystemInterface接口的实现类。

接下来看下,SystemImplgetWebViewPackages()

/frameworks/base/services/core/java/com/android/server/webkit/SystemImpl.java

    /**
     * Returns all packages declared in the framework resources as potential WebView providers.
     * @hide
     * */
    @Override
    public WebViewProviderInfo[] getWebViewPackages() 
        return mWebViewProviderPackages;
    

通过检索,发现是SystemImpl构造函数中解析xml,对mWebViewProviderPackages进行赋值操作

   private final WebViewProviderInfo[] mWebViewProviderPackages;
 
    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); // 解析xml 中Webview Provider信息
            XmlUtils.beginDocument(parser, TAG_START);
            while(true) //循环解析
                XmlUtils.nextElement(parser);
                String element = parser.getName();
                if (element == null) 
                    break;
                
                if (element.equals(TAG_WEBVIEW_PROVIDER)) //解析webviewproviders 标签
                    String packageName = parser.getAttributeValue(null, TAG_PACKAGE_NAME);
					//...
                    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));
				    //统计isFallback="true"的情况
                    if (currentProvider.isFallback) 
                        numFallbackPackages++;
						// 不允许存在availableByDefault="false"的情况
                        if (!currentProvider.availableByDefault) 
                            throw new AndroidRuntimeException(
                                    "Each WebView fallback package must be available by default.");
                        
						// 最多允许一个isFallback="true"的情况
                        if (numFallbackPackages > 1) 
                            throw new AndroidRuntimeException(
                                    "There can be at most one WebView fallback package.");
                        
                    
					//统计 availableByDefault="true"且isFallback="false"的情况
                    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();
        
		// 必须存在一个availableByDefault="true"且isFallback="false"的默认WebViewProvider
        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.xml来获取到可用的webview Provider 信息的。
新增的WebviewProvider 必须是availableByDefault="true"且isFallback="true"的注册,或者替换掉默认的webviewprovider

接下来,看下Webview Provider的配置文件:
/frameworks/base/core/res/res/xml/config_webview_packages.xml

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

一般系统默认的是com.android.webview,处于首个availableByDefault="true"的webviewprovider的优先级最高。

2.分析原因

对该国产ROM的猜测:

国产手机厂商肯定对config_webview_packages进行改动,添加了厂商自带的WebView

查看方式

先执行adb pull system/framework/framework.apk 先获取到包含该xml 的resource资源,
接着执行aapt d xmltree framework-res.apk res/xml/config_webview_packages.xml命令便可打开该xml ,查看当前手机ROM 中webview的配置项。

除此之外,也可以通过执行adb shell "pm list packages | grep webview"来查看当前手机安装了哪些webview包程序:

3.解决方案

自定义WebView,重写setOverScrollMode(),捕捉该异常。

public class FixWebView extends WebView 
    //...
    @Override
    public void setOverScrollMode(int mode) 
        try 
            super.setOverScrollMode(mode);
         catch (Exception e) 
            // 若是发生webview 有关的crash ,则表明system webview 正在更新或者不存在(部分机型上可能存在该问题)
            if (e != null && e.getMessage()!=null&&e.getMessage().toLowerCase().contains("webview")) 
                e.printStackTrace();
             else 
                throw e;
            
        
    

以上是关于Android 处理WebView not install(源码分析定位)的主要内容,如果未能解决你的问题,请参考以下文章

Android 处理WebView not install(源码分析定位)

Android WebView,错误 net::Name_Not_Resolved

Android之webView打开http链接页面无法加载显示net:ERR_CLEARTEXT_NOT_PERMITTED

Android之webView打开http链接页面无法加载显示net:ERR_CLEARTEXT_NOT_PERMITTED

flutter -webview 报错 err_cleartext_not_permitted

Webview&Viewpager滑动冲突解决方案