说明:
下面将要说的MtkBrwoser,是指在vender/mediatek目录下的Browser应用。
现象:
在使用MtkBrowser浏览网页时,选择 Save for offline reading
功能,提示 Failed to save web page.
分析:
- 跟踪上面提到的提示信息
Failed to save web page.
,在Browser目录下搜索这个字符串,找到字符串出处:
grep "Failed to save web page" -nrwi vendor/mediatek/proprietary/packages/apps/Browser/
搜索结果如下:
Failed to save web page.
然后搜索此字符串的引用,搜到如下两个文件:
vendor/mediatek/proprietary/packages/apps/Browser/src/com/android/browser/Tab.java
vendor/mediatek/proprietary/packages/apps/Browser/src/com/android/browser/Controller.java
在这两个文件中,一共有5次引用,我们分别在这些引用处插入log,然后单编push进机器,将log打印出来。
经过跟踪log,我们发现只输出了如下log:
Ctrl >> webview.savePage() return false.
而此log出自如下代码:
webview = source.getWebView();
BrowserSavePageClient savePageClient = new BrowserSavePageClient(source);
webview.setSavePageClient(savePageClient);
if (!webview.savePage()) {
Log.d(SAVE_PAGE_LOGTAG, "webview.savePage() return false.");
Log.e("XIAFEI", "Ctrl >> webview.savePage() return false.");
Toast.makeText(mActivity, R.string.saved_page_failed, Toast.LENGTH_LONG).show();
}
break;
如log所示,webview.savePage() == false
,紧接着我们跟踪 savePage
方法。
-
savePage
方法是webview
的,我们找到它的出处:
\frameworks\base\core\java\android\webkit\WebView.java
public boolean savePage() {
checkThread();
if (TRACE) {
Log.d(LOGTAG, "savePage");
}
initChromiumClassIfNeccessary();
if (mCls == null) {
Log.e(LOGTAG, "Can't get WebViewChromium Save Page Interface");
return false;
}
try {
Method savePageMethod = mCls.getDeclaredMethod("savePage");
if (savePageMethod == null) {
Log.e(LOGTAG, "Get Null from webviewchromium savePage method");
return false;
}
return (Boolean) savePageMethod.invoke(mProvider);
} catch (ReflectiveOperationException ex) {
Log.e(LOGTAG, "get Save Page Interface Exception->" + ex);
return false;
}
}
然后我们将 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
捕获的,因此我们可以推断出,问题大致出在:
Method savePageMethod = mCls.getDeclaredMethod("savePage");
接下来我们就有了新的目标—— mCls
,在当前类里面搜索 mCls
,找到它初始化的地方:
private Class> mCls;
private void initChromiumClassIfNeccessary() {
if (mCls != null) {
return;
}
try {
Application initialApplication = AppGlobals.getInitialApplication();
if (initialApplication == null) {
throw new ReflectiveOperationException("Applicatin not found");
}
String packageName = initialApplication.getString(
com.android.internal.R.string.config_webViewPackageName);
Context webViewContext = initialApplication.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
String className = "com.android.webview.chromium.WebViewChromium";
mCls = Class.forName(className, true, clazzLoader);
} catch (android.content.pm.PackageManager.NameNotFoundException ex) {
Log.e(LOGTAG, "get Webview Class Exception->" + ex);
} catch (ReflectiveOperationException ex) {
Log.e(LOGTAG, "get Webview Class Exception->" + ex);
}
}
在上述代码中,我们发现这两行代码:
String className = "com.android.webview.chromium.WebViewChromium";
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
com.google.android.webview
等等,这个目录不是 Google GMS
包的目录吗?这其中似乎有着某种不可告人的秘密......
与此同时,我们发现,在没有 GMS
包的机器上,这个功能是 OK
的!直觉告诉我们,这个问题很有可能是 GMS
包的某个或某些东西覆盖了原始的实现方式。
结论:
这个问题就是 GMS
包里面的 WebViewGoogle
引起的,在它的 Android.mk 文件里面有这么一行代码:
LOCAL_OVERRIDES_PACKAGES := webview
正是因为这行代码,WebViewGoogle
在编译时,覆盖了 Native
的 webview
,进而就导致了上面的问题。
PS:在默认情况下,GMS
包的 Chrome
应用会覆盖 Native
的 Browser
,而 Chrome
没有上述功能,也没有上述问题。也就是说,在默认情况下,MtkBrowser
是不存在的,也就没有 Failed to save web page
的问题。
解决方法:
1、简单粗暴的方法:
去掉 MtkBrowser
里面的 Save for offline reading
选项,干掉这个功能。
优点:安全,不用担心修改
GMS
,影响CTS
测试。
缺点:功能少了,治标不治本,客户不一定能接受。
2、民间方法:
注释掉前面 config.xml
文件里面的
com.google.android.webview
再注释掉 gms.mk
文件里面的 WebViewGoogle
, 让它不参与编译即可。
优点:功能保住了。
缺点:绝对会影响CTS
测试,因为WebViewGoogle
是GMS mandatory core packages
。
3、MTK解决方法:
优点:安全,不用担心修改
GMS
,影响CTS
测试,也不用担心MtkBrowser
有什么别的问题了,一劳永逸。
缺点:Chrome
的可定制性太差,客户不一定接受。
4、MTK
基于 L
平台的解决方法:
修改:
vendor\google\apps\WebViewGoogle\Android.mk
注释掉前面提到的代码:
#LOCAL_OVERRIDES_PACKAGES :=webview
修改:
/frameworks/base/core/java/android/webkit/WebViewFactory.java
的 getWebViewPackageName
方法,修改为:
public static String getWebViewPackageName() {
//return AppGlobals.getInitialApplication().getString(
// com.android.internal.R.string.config_webViewPackageName);
Application initialApplication = AppGlobals.getInitialApplication();
if(initialApplication.getPackageName().equals("com.android.browser")){
return "com.android.webview";
} else{
return initialApplication.getString(com.android.internal.R.string.config_webViewPackageName);
}
}
优点:
MTK
提供的方法,可靠性相对较高。而且有HTMLViewer
乱码现象时,只需要多加一个判断,包名写HTMLViewer
的包名就好。
缺点:可能影响CTS
或GTS
测试。
PS:经验证不可行,至少在M
平台不可行。
5、自己的方法:
在上述第 4
点 MTK
的方法的基础上,接着修改:
\frameworks\base\core\java\android\webkit\WebView.java
插入如下 START 和 END 标识的代码即可。
private void initChromiumClassIfNeccessary() {
if (mCls != null) {
return;
}
try {
Application initialApplication = AppGlobals.getInitialApplication();
if (initialApplication == null) {
throw new ReflectiveOperationException("Applicatin not found");
}
String packageName = initialApplication.getString(
com.android.internal.R.string.config_webViewPackageName);
/// START. By Xia.Fei, 20160929. Save for offline reading.
String callerPkgName = initialApplication.getPackageName();
Log.e("XIAFEI", "WV >> initChromiumClassIfNeccessary, callerPkgName = "+callerPkgName);
if(callerPkgName.equals("com.android.browser")){
packageName = "com.android.webview";
}
/// END. By Xia.Fei, 20160929. Save for offline reading.
Context webViewContext = initialApplication.createPackageContext(packageName,
Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
initialApplication.getAssets().addAssetPath(
webViewContext.getApplicationInfo().sourceDir);
ClassLoader clazzLoader = webViewContext.getClassLoader();
String className = "com.android.webview.chromium.WebViewChromium";
mCls = Class.forName(className, true, clazzLoader);
} catch (android.content.pm.PackageManager.NameNotFoundException ex) {
Log.e(LOGTAG, "get Webview Class Exception->" + ex);
} catch (ReflectiveOperationException ex) {
Log.e(LOGTAG, "get Webview Class Exception->" + ex);
}
}
缺点:未验证是否影响
CTS
和GTS
。
PS:参考上面第4
点修改之后,需要Clean
编译。