Android5.0以上WebView的兼容问题_all webview methods must be called on the same thr-CSDN博客

Android5.0以上WebView的兼容问题

最近在维护一个项目同时要兼容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;
            }
        };

    };

我们要分析几个关键方法, initViewshandleMessage中涉及到WebView方法的调用,initViewshandleMessageinitHtmlContent中涉及到线程的变换,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

首次加载的时候oncreatehandleMessage在同一个线程(main)中,在第二次加载的时候 oncreatehandleMessage在不同线程,就会出现下面的异常信息。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每个方法的调用线程,然后将线程统一。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值