您的位置:首页 > 移动开发

Android5.0以上WebView的兼容问题

2017-01-17 21:15 543 查看
最近在维护一个项目同时要兼容Android4.4和Android5.0两种机型,在调试Android5.0的时候多次因为WebView而造成程序崩溃。在项目完成之后,就来总结一下WebView的兼容性问题。

1. All WebView methods must be called on the same thread.

在Android5.0,WebView添加了线程检测,要求WebView的所有方法必须在相同的线程中被调用。当然如果使用WebView比较简单的情况下,WebView的参数设置和网页的加载在同一个方法里,就不会遇到这些问题。但是使用WebView承载太多的功能就会将问题暴露出来。

使用场景介绍:一份试卷,一道大题中有n个小题【以html的形式展示,通过Js调用小题点击事件】,点击小题的编号创建一个Fragment,在Fragment中向服务器请求数据,然后将数据组装成Html文件,然后通过WebView显示出来。下面我们贴一下相关代码:

protected void initViews() {
if (examinationId == null || examCategoryId == null || questionId == null) {
showNoDataView();
return;
}
onlineTestRest.setRootUrl(application.serverInfo.getIpAddressAndPort());
LayoutParams layoutParams   = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
mQuestionWebView = new WebView(getActivity());
mQuestionWebView.setLayoutParams(layoutParams);
mQuestionWebView.getSettings().setJavaScriptEnabled(true);
mQuestionWebView.getSettings().setPluginState(PluginState.ON);
mQuestionWebView.enableSlowWholeDocumentDraw();
//      View级别  硬件加速 setlayertype
//      您可以在运行时用以下的代码关闭单个view的硬件加速:
//      注:您不能在view级别开启硬件加速
mQuestionWebView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
Log.d(TAG, "oncreate"+Thread.currentThread().getName()+"|"+Thread.currentThread().getId());
mQuestionWebView.setWebViewClient(new WebViewClient() {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
Log.d(TAG,"onPageFinishe"+Thread.currentThread().getName()+"|"+Thread.currentThread().getId());
showContentView();
Log.d(TAG, "onPageFinished");
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
Log.d(TAG, "onPageStarted"+url+"   |   "+view);
}

});

mQuestionWebView.addJavascriptInterface(new ExamWebViewJsInterface(getActivity()), "contact");
question_content_Linear.addView(mQuestionWebView);
initHtmlContent(examinationId, examCategoryId, questionId);

mCorrectRateTextView.setText(String.format(getString(R.string.exam_question_correct_rate),
questionCorrectStatistics.getNumber(),
(int) (questionCorrectStatistics.getCorrectCount() * 100 / questionCorrectStatistics.getStudentCount())));
}

@Background
protected void initHtmlContent(String examinationId, String examCategoryId, String questionId) {
String result = "";
// 访问网络
try {
result = onlineTestRest.queryClassRoomExplainJson(examinationId, examCategoryId, questionId);
//          LogUtil.logCatDebug(getClass(), "result=" + result);
} catch (Exception e) {
LogUtil.logCatDebug(getClass(), e.toString());
}
parseExamHtml(result);
}

private void parseExamHtml(String result) {
// 网络异常
if (StringUtil.isNullOrEmpty(result)) {
showNoDataView();
return;
}
// 解析数据
try {
exmUtil = new ReadExmUtil(getActivity(), result, application.serverInfo.getIpAddressAndPort(),false);
Integer index=questionCorrectStatistics.getNumber();
if(index!=null) index-=1;
QuestionUtils.setQuestionIndex(exmUtil, index);
exmUtil.handleExamList();
//TODO
} catch (Exception e) {
showNoDataView();
return;
}
allContent = EducExamFactory.createGroupApi(StaticUtil.EDUC_ONEQUESTION_EXPLAIN,
application.serverInfo.getIpAddressAndPort()).execute(getActivity(), exmUtil);
savedPath = FileUtil.saveHtmlFile(allContent,
String.format("education.teacher_exam_preview_%s", examinationId), StaticUtil.OBJTESTHTML_DIR);
if (savedPath == null) {
showNoDataView();
return;
}
initWebViewContent();
Log.d(TAG,"parseExamHtml"+ Thread.currentThread().getName()+"|"+Thread.currentThread().getId());

}

protected void initWebViewContent() {
myHandle.sendEmptyMessage(100);
}

Handler myHandle = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 100:
Log.d(TAG,"handleMessage"+ Thread.currentThread().getName()+"|"+Thread.currentThread().getId());
mQuestionWebView.loadUrl("file://" + savedPath);
break;

default:
break;
}
};

};


我们要分析几个关键方法,
initViews
handleMessage
中涉及到WebView方法的调用,
initViews
handleMessage
initHtmlContent
中涉及到线程的变换,
initViews
运行在
onCreate()
中,即在main线程中;
initHtmlContent
要进行数据的获取和html组装等耗时操作,所以要放在新建一个后台线程;
myhandle
是在Fragment中新建的,线程应该和Fragemnt一致。所以我们要保证

我们看一下打印的Log信息:

02-08 13:33:06.558: D/QuestionDetailListFragment(31497): oncreate     main|1
02-08 13:33:06.694: D/QuestionDetailListFragment(31497): handleMessage     main|1
02-08 13:33:06.694: D/QuestionDetailListFragment(31497): parseExamHtml     pool-2-thread-1|1872

02-08 13:33:07.189: D/QuestionDetailListFragment(31497): onPageFinishe    main|1
02-08 13:33:23.583: D/QuestionDetailListFragment(31497): oncreate      main|1
02-08 13:33:23.664: D/QuestionDetailListFragment(31497): parseExamHtml     pool-2-thread-1|1872
02-08 13:33:23.664: D/QuestionDetailListFragment(31497): handleMessage     JavaBridge|1915


首次加载的时候
oncreate
handleMessage
在同一个线程(main)中,在第二次加载的时候
oncreate
handleMessage
在不同线程,就会出现下面的异常信息。
JavaBridge
线程是WebView通过Js调用Android方法新开的线程,而不是在UI线程中,所以会报异常。

E/QuestionDetailListFragment_(23794): A runtime exception was thrown while executing code in a runnable
E/QuestionDetailListFragment_(23794): java.lang.RuntimeException: java.lang.Throwable: A WebView method was called on thread 'JavaBridge'. All WebView methods must be called on the same thread. (Expected Looper Looper (main, tid 1) {2912b0ee} called on Looper (JavaBridge, tid 1915) {2c3f1fdc}, FYI main Looper is Looper (main, tid 1) {2912b0ee})


解决方案就是让
mQuestionWebView.loadUrl("file://" + savedPath);
也运行在UI线程就可以了,因为
onCreate()
一直运行在UI线程中,这样就保持了线程的统一。下面直接贴代码:

protected void initWebViewContent() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mQuestionWebView.loadUrl("file://" + savedPath);
}
});
}


总结:

1.WebView调用JS会重新开启一个线程,而不是在UI线程中;

2.Handle不一定都在UI线程中,而是根据Looper来确定;

3.WebView在Android5.0之后会要求所有的方法都必须在同一线程中调用;

4.如果遇到线程不一致的情况下,先跟踪WebView每个方法的调用线程,然后将线程统一。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: