关于子线程操作view的一些实践分析

问题点:

最近在做与H5的交互中发现,通过@javascriptInterface提供的接口是运行在子线程中,但是在子线程中却能够操作View,这个有点颠覆了我学Android以来的认知,子线程不能更新view

于是写了demo做了几个实现,demo使用的sdk,目前测试过android-21,android-25和android-27

demo很简单,就是新建一个子线程,分别在里面作如下操作,来观察程序运行情况

一、API25和27的情况

view操作 android-25,android-27上程序是否崩溃 崩溃原因 修复方法
TextView,Button:setText
Toast:show 子线程未初始化looper showToast前后增加Looper.prepare()和Looper.loop()
Dialog:show 子线程未初始化looper(Can't create handler inside thread that has not called Looper.prepare()) showDialog前后增加Looper.prepare()和Looper.loop()
Dialog:dismiss 子线程不能操作view(Only the original thread that created a view hierarchy can touch its views)

 

一、API21的情况

view操作 android-21是否会崩溃   崩溃原因 修复方法
TextView,Button:setText 子线程不能操作view(Only the original thread that created a view hierarchy can touch its views)

由表格可以发现:

1、API21上肯定会崩溃,这个符合我们对android子线程不能更新view的认知。

2、在API25和27上,我们常用的TextView或者是button是可以在子线程中更新view的但是dialog却不能dismiss,很奇怪,我仔细分析了一下Dialog.dimiss和TextView.setText的源码:

 

一、setText

setText的时候会调用View的invalidate()方法,跟进源码之后发现:最终会调用到ViewGroup中的这句话

该段代码就是表示在父view中更新子view,由于我demo的父view是RelativeLayout,RelativeLayout继承ViewGroup,所以走到了ViewGroup里面的更新view的方法,这里面一系列方法没有对线程的判断

二、Dialog.dismiss

Dialog dismiss时会调用windowManager的removeViewImmediate()方法

继续跟进windowManager的实现类windowManagerImpl,发现会走到ViewRootImpl类里面的这个方法,然后这里面有一个checkThread方法,这个就会导致抛出子线程不能更新view的错误。

继续研究源码,然后发现ViewRootImpl类里面也有一个ViewGroup类似的方法,viewGroup里面虽然有这个checkThread,但是更新view的时候没有调用到这个方法,所以ViewGroup里面更新view不会报错。

至此终于找到了罪魁祸首,综合以上,理论上来说,由于指观察了API21,25,27目前最起码25以上的API只要父view是继承ViewGroup就不会崩溃。

 

但是为什么dialog和toastshow的时候为什么只要初始化一下looper依然能在更新view呢?,下面对他们的show方法读了一下源码,

 

三、Dialog show和Toast show

这两个方法只要在子线程初始化一下Looper(Looper.prepare(),Looper.loop())就可以show出来。

这时候为什么可以在子线程更新View呢,还是分析源码,dialog和toast类似,会走到ViewrootImpl的checkThread方法,但是这时候确没有报异常,这时候观察源码

发现每次show的时候都会重新初始化这个ViewrootImpl,初始化的时候会给ViewrootImpl中的mThread赋值

就是当前线程,此时在子线程中,更新View的时候还是这个子线程,所有无论怎么判断,这两个线程都是同一个线程,所以不会报错

发布了4 篇原创文章 · 获赞 1 · 访问量 1318
展开阅读全文

Can't create handler inside thread has not called Looper.prepare() 错误

04-15

我在android环境下创建一个小游戏。这个游戏使用一个线程绘制在一个SurfaceView 上面。在线程的run()方法中,我测试看游戏是否结束,如果游戏结束就显示游戏结束对话框,但是却给出“Can't create handler inside thread has not called Looper.prepare()”的错误消息。我觉得是当一个非UI线程试图去介入UI线程的时候,发生了错误。有什么好的方法可以显示这样的一个游戏结束对话框? 贴出我的代码: public class BouncingBallActivity extends Activity{ private static final int DIALOG_GAMEOVER_ID = 0; private BouncingBallView bouncingBallView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); bouncingBallView = new BouncingBallView(this); bouncingBallView.resume(); setContentView(bouncingBallView); } protected Dialog onCreateDialog(int id) { switch (id) { case DIALOG_GAMEOVER_ID: AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Game Over.") .setCancelable(false) .setPositiveButton("Try Again", new DialogInterface.OnClickListener() { public void onClick(DialogInterface arg0, int arg1) { bouncingBallView.resume(); } }) .setNegativeButton("Exit", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { BouncingBallActivity.this.finish(); } }); AlertDialog gameOverDialog = builder.create(); return gameOverDialog; default: return null; } } class BouncingBallView extends SurfaceView implements Runnable { SurfaceHolder surfaceViewHolder; Canvas canvas; Context context; Thread drawingThread; boolean drawingThreadIsRunning; boolean isInitialised; Ball ball; ArtificialIntelligence ai; BouncingBallView(Context context) { // } public void pause() { isInitialised = false; drawingThreadIsRunning = false; boolean joiningWasSuccessful = false; while(!joiningWasSuccessful) try { drawingThread.join(); joiningWasSuccessful = true; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void resume() { isInitialised = false; drawingThread = new Thread(this); drawingThread.setName("Drawing Thread"); drawingThreadIsRunning = true; drawingThread.start(); } public void run() { while(drawingThreadIsRunning) { if(!surfaceViewHolder.getSurface().isValid()) continue; if(gameOver()) BouncingBallActivity.this.showDialog(DIALOG_GAMEOVER_ID); try{ canvas = surfaceViewHolder.lockCanvas(); if(!isInitialised)init(canvas); update(); surfaceViewHolder.unlockCanvasAndPost(canvas); }catch(Exception e) { Log.e(BouncingBallActivity.this.toString(),String.format("%s: Just as the emperor had foreseen!\n(This error is expected. Canvas destroyed while animations continue.)", e.toString())); } } } private void init(Canvas canvas) { ball = new Ball(canvas, Color.GREEN); ai = new ArtificialIntelligence(canvas, (int) (ball.getX()+100),canvas.getWidth()); isInitialised = true; } } } 问答

Android如何在自定义view中发送消息给主线程

03-15

求求各位大神支招,小弟在自己开发一个东西,遇到了一个小问题。 ![![图片说明](https://img-ask.csdn.net/upload/201603/15/1458054699_661302.jpg) 图片说明](https://img-ask.csdn.net/upload/201603/15/1458054689_616589.jpg) 我在自定义view的onDraw里,当条件达到后就开启一个子线程发送消息给主线程,让主线程中的handler接收消息并执行相应的动作,可是在Log的输出下只能输出“子线程,”而没有”handlemessage“,并且异常退出,退出代码如下: 03-15 22:50:34.593: W/dalvikvm(27492): threadid=1: thread exiting with uncaught exception (group=0x41c6ece0) 03-15 22:50:34.593: W/dalvikvm(27492): threadid=1: uncaught exception occurred 03-15 22:50:34.593: W/System.err(27492): java.lang.NullPointerException 03-15 22:50:34.593: W/System.err(27492): at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4094) 03-15 22:50:34.593: W/System.err(27492): at android.os.Handler.dispatchMessage(Handler.java:110) 03-15 22:50:34.593: W/System.err(27492): at android.os.Looper.loop(Looper.java:193) 03-15 22:50:34.593: W/System.err(27492): at android.app.ActivityThread.main(ActivityThread.java:5348) 03-15 22:50:34.593: W/System.err(27492): at java.lang.reflect.Method.invokeNative(Native Method) 03-15 22:50:34.593: W/System.err(27492): at java.lang.reflect.Method.invoke(Method.java:515) 03-15 22:50:34.593: W/System.err(27492): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:829) 03-15 22:50:34.594: W/System.err(27492): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:645) 03-15 22:50:34.594: W/System.err(27492): at dalvik.system.NativeStart.main(Native Method) 03-15 22:50:34.594: W/dalvikvm(27492): threadid=1: calling UncaughtExceptionHandler 03-15 22:50:34.594: I/dalvikvm(27492): +++ calling Ljava/lang/ThreadGroup;.uncaughtException 03-15 22:50:34.594: D/dalvikvm(27492): threadid=11: bye! 03-15 22:50:34.595: E/AndroidRuntime(27492): FATAL EXCEPTION: main 03-15 22:50:34.595: E/AndroidRuntime(27492): Process: com.example.wyccustomview3, PID: 27492 03-15 22:50:34.595: E/AndroidRuntime(27492): java.lang.NullPointerException 03-15 22:50:34.595: E/AndroidRuntime(27492): at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:4094) 03-15 22:50:34.595: E/AndroidRuntime(27492): at android.os.Handler.dispatchMessage(Handler.java:110) 03-15 22:50:34.595: E/AndroidRuntime(27492): at android.os.Looper.loop(Looper.java:193) 03-15 22:50:34.595: E/AndroidRuntime(27492): at android.app.ActivityThread.main(ActivityThread.java:5348) 03-15 22:50:34.595: E/AndroidRuntime(27492): at java.lang.reflect.Method.invokeNative(Native Method) 03-15 22:50:34.595: E/AndroidRuntime(27492): at java.lang.reflect.Method.invoke(Method.java:515) 03-15 22:50:34.595: E/AndroidRuntime(27492): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:829) 03-15 22:50:34.595: E/AndroidRuntime(27492): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:645) 03-15 22:50:34.595: E/AndroidRuntime(27492): at dalvik.system.NativeStart.main(Native Method) 问答

Android线程与ui绘制的问题

02-22

当然知道只能在主线程更新UI,不过我有个非常费时的UI绘制操作(就是一堆自定义的view退出时保存在文件里,然后进入时读取文件绘制ui恢复界面,实测短板出现在view的绘制过程) 然后我一开始觉得只要View不添加就只是对象而已,然后尝试新开一个线程把这些控件new出来,然后在主线程addView,结果在我的平板(5.1.1)上完美运行,在手机(4.2.2)上闪退。 继续测试手机,新开一个线程,一个执行一句new一个edittext就崩溃,不过更加奇怪的是换成textview就正常。完全不明就里。 **ps,大家有什么替代方法,也望不吝赐教,感激不尽** 按照大家的提醒贴代码(实际的代码太长不便理解,我就写了一个TextView和EditText的,意思一样,而且方便大家理解。) @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); new Thread(new Runnable() { @Override public void run() { try { new TextView(mActivity); } catch (Exception e) { Log.d("debug", Log.getStackTraceString(e)); return; } Log.d("debug", "成功地new了一个TextView"); try { new EditText(mActivity); } catch (Exception e) { Log.d("debug", Log.getStackTraceString(e)); return; } Log.d("debug", "成功地new了一个EditText"); } }).start(); } 附上Logcat输出: 02-23 03:29:13.144 16364-16440/com.example.multicalc D/debug: 成功地new了一个TextView 02-23 03:29:13.156 16364-16440/com.example.multicalc D/debug: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:197) at android.os.Handler.<init>(Handler.java:111) at android.widget.Editor$UserDictionaryListener.<init>(Editor.java:3836) at android.widget.Editor.<init>(Editor.java:192) at android.widget.TextView.createEditorIfNeeded(TextView.java:8475) at android.widget.TextView.<init>(TextView.java:1114) at android.widget.EditText.<init>(EditText.java:60) at android.widget.EditText.<init>(EditText.java:56) at android.widget.EditText.<init>(EditText.java:52) at com.example.multicalc.matrix.ui.WorkWindow$1.run(WorkWindow.java:174) at java.lang.Thread.run(Thread.java:856) 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览