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显示出来。下面我们贴一下相关代码:
我们要分析几个关键方法,
我们看一下打印的Log信息:
首次加载的时候
解决方案就是让
总结:
1.WebView调用JS会重新开启一个线程,而不是在UI线程中;
2.Handle不一定都在UI线程中,而是根据Looper来确定;
3.WebView在Android5.0之后会要求所有的方法都必须在同一线程中调用;
4.如果遇到线程不一致的情况下,先跟踪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每个方法的调用线程,然后将线程统一。
相关文章推荐
- android5.0以上webview无法加载图片问题
- mWebView拖动出现黑快、黑屏问题处理(兼容2.X版本)
- WebView注入Javascript的版本兼容问题
- webview 5.0以上 图片不显示问题
- android5.0以上webview无法播放腾讯优酷等网络视频
- Android5.0 WebView中Http和Https混合问题
- webview4.4以上版本使用loadurl加载过长js文件失效问题
- Webview打开本地文件、图片选择的解决方案。版本兼容问题
- 关于WebView不能加载网页,配置,兼容适配问题
- 部分android手机WebView无法成功同步Cookie问题的解决方案(5.0系统以上)
- Android5.0 WebView中Http和Https混合问题
- android4.4以上 webview加在图片的缩放问题
- Android5.0以上Webview上传文件注意事项
- Webview 打开本地图片库兼容问题
- Cordova的WebView兼容问题-X5引擎插件
- WebView 兼容问题
- Android截图包含webview时,webview部分空白(其他部分正常),此问题发生在系统4.4及以上
- Android5.0 WebView中Http和Https混合问题
- Android 5.1版本以上WebView内存泄漏问题及快速解决方法
- Android截图包含webview时,webview部分空白(其他部分正常),此问题发生在系统4.4及以上