说明:

下面将要说的MtkBrwoser,是指在vender/mediatek目录下的Browser应用。

现象:

在使用MtkBrowser浏览网页时,选择 Save for offline reading 功能,提示:Failed to save web page.

Save for offline reading Failed to save web page.

分析:
  • 跟踪上面提到的提示信息 Failed to save web page.,在Browser目录下搜索这个字符串,找到字符串出处:

    1. grep "Failed to save web page" -nrwi vendor/mediatek/proprietary/packages/apps/Browser/

    搜索结果如下:

    1. <string name="saved_page_failed">Failed to save web page.</string>

    然后搜索此字符串的引用,搜到如下两个文件:

    1. vendor/mediatek/proprietary/packages/apps/Browser/src/com/android/browser/Tab.java
    2. vendor/mediatek/proprietary/packages/apps/Browser/src/com/android/browser/Controller.java

    在这两个文件中,一共有5次引用,我们分别在这些引用处插入log,然后单编push进机器,将log打印出来。
    经过跟踪log,我们发现只输出了如下log:

    1. Ctrl >> webview.savePage() return false.

    而此log出自如下代码:

    1. webview = source.getWebView();
    2. BrowserSavePageClient savePageClient = new BrowserSavePageClient(source);
    3. webview.setSavePageClient(savePageClient);
    4. if (!webview.savePage()) {
    5. Log.d(SAVE_PAGE_LOGTAG, "webview.savePage() return false.");
    6. Log.e("XIAFEI", "Ctrl >> webview.savePage() return false.");
    7. Toast.makeText(mActivity, R.string.saved_page_failed, Toast.LENGTH_LONG).show();
    8. }
    9. break;

    如log所示,webview.savePage() == false,紧接着我们跟踪 savePage 方法。

  • savePage 方法是webview 的,我们找到它的出处:

\frameworks\base\core\java\android\webkit\WebView.java

  1. public boolean savePage() {
  2. checkThread();
  3. if (TRACE) {
  4. Log.d(LOGTAG, "savePage");
  5. }
  6. initChromiumClassIfNeccessary();
  7. if (mCls == null) {
  8. Log.e(LOGTAG, "Can't get WebViewChromium Save Page Interface");
  9. return false;
  10. }
  11. try {
  12. Method savePageMethod = mCls.getDeclaredMethod("savePage");
  13. if (savePageMethod == null) {
  14. Log.e(LOGTAG, "Get Null from webviewchromium savePage method");
  15. return false;
  16. }
  17. return (Boolean) savePageMethod.invoke(mProvider);
  18. } catch (ReflectiveOperationException ex) {
  19. Log.e(LOGTAG, "get Save Page Interface Exception->" + ex);
  20. return false;
  21. }
  22. }

然后我们将 log 过滤设置为此文件的 tag,拿到如下信息:

E/WebView(10128): get set Save Page Client Interface Exception->java.lang.NoSuchMethodException: setSavePageClient [class java.lang.Object] D/WebView(10128): savePage E/WebView(10128): get Save Page Interface Exception->java.lang.NoSuchMethodException: savePage []

我们看到这里出现了 NoSuchMethodException ,而这个 Exception 是由 ReflectiveOperationException 捕获的,因此我们可以推断出,问题大致出在:

  1. Method savePageMethod = mCls.getDeclaredMethod("savePage");

接下来我们就有了新的目标—— mCls,在当前类里面搜索 mCls ,找到它初始化的地方:

  1. private Class<?> mCls;
  2. private void initChromiumClassIfNeccessary() {
  3. if (mCls != null) {
  4. return;
  5. }
  6. try {
  7. Application initialApplication = AppGlobals.getInitialApplication();
  8. if (initialApplication == null) {
  9. throw new ReflectiveOperationException("Applicatin not found");
  10. }
  11. String packageName = initialApplication.getString(
  12. com.android.internal.R.string.config_webViewPackageName);
  13. Context webViewContext = initialApplication.createPackageContext(packageName,
  14. Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
  15. initialApplication.getAssets().addAssetPath(
  16. webViewContext.getApplicationInfo().sourceDir);
  17. ClassLoader clazzLoader = webViewContext.getClassLoader();
  18. String className = "com.android.webview.chromium.WebViewChromium";
  19. mCls = Class.forName(className, true, clazzLoader);
  20. } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
  21. Log.e(LOGTAG, "get Webview Class Exception->" + ex);
  22. } catch (ReflectiveOperationException ex) {
  23. Log.e(LOGTAG, "get Webview Class Exception->" + ex);
  24. }
  25. }

在上述代码中,我们发现这两行代码:

  1. String className = "com.android.webview.chromium.WebViewChromium";
  2. mCls = Class.forName(className, true, clazzLoader);

这里是想通过类加载器加载 com.android.webview.chromium.WebViewChromium 类,我们在代码库里面全局搜索发现,自 L 版本开始,这个类已经被干掉了……
**

  • WebViewChromium 类被干掉了,却还在调用它的方法,不出问题才怪呢!

可为什么要干掉呢?
从这个类名我们可以猜到它可能和 Chrome 有关,细心的我们发现,上面的类加载器 clazzLoader 是通过 webViewContext 得到的,而 webViewContext 又是通过 packageName 得到的,packageName 是指向 config_webViewPackageName 的字符串。
我们全局搜索 config_webViewPackageName ,最终在如下文件中发现了它:\vendor\google\products\gms_overlay\frameworks\base\core\res\res\values\config.xml

  1. <!-- Package name providing WebView implementation. -->
  2. <string name="config_webViewPackageName" translatable="false">com.google.android.webview</string>

等等,这个目录不是 Google GMS 包的目录吗?这其中似乎有着某种不可告人的秘密……
与此同时,我们发现,在没有 GMS 包的机器上,这个功能是 OK 的!直觉告诉我们,这个问题很有可能是 GMS 包的某个或某些东西覆盖了原始的实现方式。


结论:
这个问题就是 GMS 包里面的 WebViewGoogle 引起的,在它的 Android.mk 文件里面有这么一行代码:

  1. LOCAL_OVERRIDES_PACKAGES := webview

正是因为这行代码,WebViewGoogle 在编译时,覆盖了 Nativewebview ,进而就导致了上面的问题。
PS:在默认情况下,GMS包的 Chrome 应用会覆盖 NativeBrowser,而 Chrome 没有上述功能,也没有上述问题。也就是说,在默认情况下,MtkBrowser 是不存在的,也就没有 Failed to save web page 的问题。


解决方法:
1、简单粗暴的方法:
去掉 MtkBrowser 里面的 Save for offline reading 选项,干掉这个功能。

优点:安全,不用担心修改 GMS,影响 CTS 测试。 缺点:功能少了,治标不治本,客户不一定能接受。

2、民间方法:
注释掉前面 config.xml 文件里面的

  1. <!-- Package name providing WebView implementation. -->
  2. <string name="config_webViewPackageName" translatable="false">com.google.android.webview</string>

再注释掉 gms.mk 文件里面的 WebViewGoogle, 让它不参与编译即可。

优点:功能保住了。 缺点:绝对会影响 CTS 测试,因为 WebViewGoogleGMS mandatory core packages

3、MTK解决方法:
[M0] 浏览器保存离线页面失败 - 图1

优点:安全,不用担心修改 GMS,影响 CTS 测试,也不用担心 MtkBrowser 有什么别的问题了,一劳永逸。 缺点:Chrome 的可定制性太差,客户不一定接受。image.gif

4、MTK 基于 L 平台的解决方法:
修改:
vendor\google\apps\WebViewGoogle\Android.mk
注释掉前面提到的代码:
#LOCAL_OVERRIDES_PACKAGES :=webview
修改:
/frameworks/base/core/java/android/webkit/WebViewFactory.java
getWebViewPackageName 方法,修改为:

  1. public static String getWebViewPackageName() {
  2. //return AppGlobals.getInitialApplication().getString(
  3. // com.android.internal.R.string.config_webViewPackageName);
  4. Application initialApplication = AppGlobals.getInitialApplication();
  5. if(initialApplication.getPackageName().equals("com.android.browser")){
  6. return "com.android.webview";
  7. } else{
  8. return initialApplication.getString(com.android.internal.R.string.config_webViewPackageName);
  9. }
  10. }

优点:MTK 提供的方法,可靠性相对较高。而且有 HTMLViewer 乱码现象时,只需要多加一个判断,包名写 HTMLViewer 的包名就好。 缺点:可能影响 CTSGTS 测试。 PS:经验证不可行,至少在 M 平台不可行。

5、自己的方法:
在上述第 4MTK 的方法的基础上,接着修改:
\frameworks\base\core\java\android\webkit\WebView.java
插入如下 START 和 END 标识的代码即可。

  1. private void initChromiumClassIfNeccessary() {
  2. if (mCls != null) {
  3. return;
  4. }
  5. try {
  6. Application initialApplication = AppGlobals.getInitialApplication();
  7. if (initialApplication == null) {
  8. throw new ReflectiveOperationException("Applicatin not found");
  9. }
  10. String packageName = initialApplication.getString(
  11. com.android.internal.R.string.config_webViewPackageName);
  12. /// START. By Xia.Fei, 20160929. Save for offline reading.
  13. String callerPkgName = initialApplication.getPackageName();
  14. Log.e("XIAFEI", "WV >> initChromiumClassIfNeccessary, callerPkgName = "+callerPkgName);
  15. if(callerPkgName.equals("com.android.browser")){
  16. packageName = "com.android.webview";
  17. }
  18. /// END. By Xia.Fei, 20160929. Save for offline reading.
  19. Context webViewContext = initialApplication.createPackageContext(packageName,
  20. Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
  21. initialApplication.getAssets().addAssetPath(
  22. webViewContext.getApplicationInfo().sourceDir);
  23. ClassLoader clazzLoader = webViewContext.getClassLoader();
  24. String className = "com.android.webview.chromium.WebViewChromium";
  25. mCls = Class.forName(className, true, clazzLoader);
  26. } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
  27. Log.e(LOGTAG, "get Webview Class Exception->" + ex);
  28. } catch (ReflectiveOperationException ex) {
  29. Log.e(LOGTAG, "get Webview Class Exception->" + ex);
  30. }
  31. }

缺点:未验证是否影响 CTSGTS。 PS:参考上面第 4 点修改之后,需要 Clean 编译。