问题出现情景如下:
在接入广告sdk的时候,调用播放广告的时候,判断广告已经预加载成功,此时去播放广告的时候程序闪退,报错如下
在查阅相关资料后,得知更新UI是要主线程来更新的,即UI线程更新。如果在主线线程之外的线程中直接更新页面显示常会报错。抛出异常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
解决办法有多种,我采用的是runOnUiThread();
代码如下
public void Show(String codeId){
wangmengID = codeId;
if(mttRewardVideoAd != null){
System.out.println(MainActivity.TAG + "穿山甲/" + "视频可播放");
mttRewardVideoAd.setShowDownLoadBar(true);
mttRewardVideoAd.setRewardAdInteractionListener(m_adlistener);
//在播放广告的时候会刷新界面,刷新UI只能在主线程中进行,不然会闪退
//所以在runOnUiThread中播放广告
UnityPlayer.currentActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mttRewardVideoAd.showRewardVideoAd(UnityPlayer.currentActivity);
mttRewardVideoAd = null;
}
});
}
}
具体原理在网上有很多相关资料:
自己从别处摘抄过来防止在遇到这种情况
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
更新UI是要主线程来更新的,即UI线程更新。如果在主线线程之外的线程中直接更新页面显示常会报错。抛出异常:android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程
public static void showToastSafe(final Activity activity,
final String text, final int duration) {
// 方法1 activity.runOnUiThread
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
});
}
handler.post
public static void showToastSafe(final Context context, final String text) {
// 方法2 handler.post 内部其实是做handler的流程 sendMsg之类的 功能是一样的 调用更简练
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
});
}
imageview.postDelayed(new Runnable() {
@Override
public void run() {
Intent mIntent = new Intent(MainActivity.this,
SecondActivity.class);
startActivity(mIntent);
finish();
}
}, 2000); 1
1,如果post方法是handler的,则Runnable执行在handler依附线程中,可能是主线程,也可能是其他线程。
2,如果post方法是View的,则一定是运行在主线程中的,因为所有view都自带一个handler,所有handler都有post方法,所以它的Runnable是运行在主线程中的
先从最简单的runOnUiThread()来看源码:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
这是runOnUiThread()方法的源码,从源码可以看到,当一个Runnable进来时,判断当前线程是否是主线程,如果是主线程直接调用其run()方法了,不是主线程则调用handler的post()回到了handler。不管是onResume还是OnCreate里调用runOnUiThread()只要是主线程中调用,其Runnable的run()方法立刻就执行。
再看一下view.post()的源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
它先判断了一个叫attachInfo是不是为null,如果不是null,调用了Handler的post()方法回到了handler,那么mAttachInfo是一个什么东东呢?下面我们再看一段代码:
/**
* Returns true if this view is currently attached to a window.
*/
public boolean isAttachedToWindow() {
return mAttachInfo != null;
}ViewRootImpl
如上mAttachInfo是用来判断是否attached到window的,这里留一个疑问即view什么时候attached到window上的呢?当为null的时候调用ViewRootImpl.getRunQueue().post(action),那么它的调用时机是什么呢?通过跟踪源码可以得到在ViewRootImpl的一系列方法中,比如draw(boolean)等。而ViewRootImpl.getRunQueue().post(action)的处理action是handler.postDelayed(handlerAction.action, handlerAction.delay)最后也回归到了handler。
抛开runOnUiThread不说因为上面已经说了该方法在主线程立刻会执行的,Handler().post()是在生命周期onResume之后执行的,而view.post()是在onAttachedWindow之后执行,也就是说ViewRootImpl.getRunQueue().post(action)是在onAttachedWindow之后执行的。由此我们得出一个结论,当程序启动一个activity时,OnCreate、onStart、onResume任务都添加到了主线程Looper的messageQueue中,在这个三个生命周期使用handler.post()都添加到messageQueue队列尾部,等待执行。而View.post(),最终也会添加到messageQueue队列中,等待onAttachedToWindow执行之后执行。
当在onResume之后点击某一button时打印的日志,由此看见当前面条件都满足时,在调用这三个post()方法时,都添加到messageQueue中,在每一个任务量小的时基本是同时执行的。那么我们就可以得出一个结论,在界面绘制成功以后再调用这三个方法时,当在子线程中调用时其效果是一样的,当在主线程中runOnUiThread是立刻执行该任务,而其他两个是加载到messageQueue队尾,当前面任务全部执行完毕再执行。
参考资料:
view.post和Handler.post区别:
https://blog.csdn.net/johnlee175/article/details/52369173
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
activity_main.xml:
MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View view = findViewById(R.id.text_view);
runOnUiThread(new Runnable() {
@Override
public void run() {
System.err.println(Thread.currentThread() + " + Activity.runOnUiThread(Runnable)");
}
});
if (view != null) {
view.post(new Runnable() {
@Override
public void run() {
System.err.println(Thread.currentThread() + " + View.post(Runnable)");
}
});
}
}
自定义TextView覆写的”生命周期”回调方法:
onAttachedToWindow();
onDetachedFromWindow();
onWindowVisibilityChanged(int visibility);
onWindowFocusChanged(boolean hasWindowFocus);
onVisibilityChanged(View changedView, int visibility);
onFinishInflate();
onLayout(boolean changed, int left, int top, int right, int bottom);
onMeasure(int widthMeasureSpec, int heightMeasureSpec);
onDraw(Canvas canvas);
onSizeChanged(int w, int h, int oldw, int oldh);
下面是日志:
launch:
after first onMeasure, getMeasuredWidth() and getMeasuredHeight() worked;
onSizeChanged or after, getMeasuredWidth()/getMeasuredHeight()/getWidth()/getHeight() worked, until after onDetachedFromWindow;
08-30 12:33:07.680 9816-9816/com.test W/System.err: onFinishInflate
08-30 12:33:07.681 9816-9816/com.test W/System.err: Thread[main,5,main] + Activity.runOnUiThread(Runnable)
08-30 12:33:07.708 9816-9816/com.test W/System.err: onAttachedToWindow
08-30 12:33:07.708 9816-9816/com.test W/System.err: onWindowVisibilityChanged
08-30 12:33:07.708 9816-9816/com.test W/System.err: onVisibilityChanged
08-30 12:33:07.712 9816-9816/com.test W/System.err: onMeasure
08-30 12:33:07.771 9816-9816/com.test W/System.err: onMeasure
08-30 12:33:07.771 9816-9816/com.test W/System.err: onSizeChanged
08-30 12:33:07.771 9816-9816/com.test W/System.err: onLayout
08-30 12:33:07.802 9816-9816/com.test W/System.err: Thread[main,5,main] + View.post(Runnable)
08-30 12:33:07.802 9816-9816/com.test W/System.err: onWindowFocusChanged
08-30 12:33:07.804 9816-9816/com.test W/System.err: onMeasure
08-30 12:33:07.804 9816-9816/com.test W/System.err: onLayout
08-30 12:33:07.804 9816-9816/com.test W/System.err: onDraw
home:
08-30 12:35:38.628 9816-9816/com.test W/System.err: onWindowFocusChanged
08-30 12:35:38.747 9816-9816/com.test W/System.err: onWindowVisibilityChanged
08-30 12:35:38.989 9816-9816/com.test W/System.err: onVisibilityChanged
re-foreground:
08-30 12:35:59.959 9816-9816/com.test W/System.err: onVisibilityChanged
08-30 12:35:59.967 9816-9816/com.test W/System.err: onWindowVisibilityChanged
08-30 12:35:59.988 9816-9816/com.test W/System.err: onWindowFocusChanged
08-30 12:36:00.001 9816-9816/com.test W/System.err: onDraw
back:
08-30 12:36:25.576 9816-9816/com.test W/System.err: onWindowFocusChanged
08-30 12:36:25.668 9816-9816/com.test W/System.err: onWindowVisibilityChanged
08-30 12:36:25.678 9816-9816/com.test W/System.err: onDetachedFromWindow
re-launch or rotate-screen:
08-30 12:36:50.175 9816-9816/com.test W/System.err: onFinishInflate
08-30 12:36:50.175 9816-9816/com.test W/System.err: Thread[main,5,main] + Activity.runOnUiThread(Runnable)
08-30 12:36:50.182 9816-9816/com.test W/System.err: onAttachedToWindow
08-30 12:36:50.182 9816-9816/com.test W/System.err: onWindowVisibilityChanged
08-30 12:36:50.182 9816-9816/com.test W/System.err: onVisibilityChanged
08-30 12:36:50.183 9816-9816/com.test W/System.err: onMeasure
08-30 12:36:50.219 9816-9816/com.test W/System.err: onMeasure
08-30 12:36:50.220 9816-9816/com.test W/System.err: onSizeChanged
08-30 12:36:50.220 9816-9816/com.test W/System.err: onLayout
08-30 12:36:50.222 9816-9816/com.test W/System.err: Thread[main,5,main] + View.post(Runnable)
08-30 12:36:50.222 9816-9816/com.test W/System.err: onWindowFocusChanged
08-30 12:36:50.232 9816-9816/com.test W/System.err: onMeasure
08-30 12:36:50.232 9816-9816/com.test W/System.err: onLayout
08-30 12:36:50.232 9816-9816/com.test W/System.err: onDraw
lock-screen or show-status-panel:
08-30 12:38:12.882 9816-9816/com.test W/System.err: onWindowFocusChanged
unlock-screen or hide-status-panel:
08-30 12:38:12.882 9816-9816/com.test W/System.err: onWindowFocusChanged
关于View.post和Activity.runOnUiThread的札记
先看Activity的runOnUiThread的源码:
final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
可以看出, 如果当前是主线程则直接执行, 否则立即投递到主线程Handler执行;
再看View的post的源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
如果mAttachInfo不为null, 则投递到它的Handler中执行, 否则加入到ViewRootImpl.getRunQueue()这个队列里.
那么什么时候, mAttachInfo不为null呢? 这里我直接给出答案: (请按照此流程浏览源码)
我们跳过一些路径, 从Activity的onResume()开始说, 它是在handleResumeActivity()方法里的performResumeActivity()里完成的.
在这之后, handleResumeActivity()里, 有调用WindowManagerImpl.addView()将PhoneWindow.mDecorView(DecorView类型, 继承自FrameLayout)添加到窗体中.
然后路径到了WindowManagerGlobal.addView()(一个 Activity对应一个WindowManagerImpl, 但WindowManagerGlobal进程唯一), 这里new ViewRootImpl(), 调用此对象的setView().
setView()里赋值ViewRootImpl的mAttachInfo, 然后首次调用requestLayout(), 此方法里调用scheduleTraversals(), 进而调用 performTraversals().
performTraversals()里调用顶层view的dispatchAttachedToWindow(), 因为顶层view是FrameLayout, 所以调用ViewGroup的dispatchAttachedToWindow(), 里面遍历调用所有子view的dispatchAttachedToWindow(), 并传入mAttachInfo(一直传递), 直到调用View类的dispatchAttachedToWindow().
此时, mAttachInfo不再为null, 而且各层的onAttachedToWindow()得到回调.
另外, onAttachedToWindow()真正执行顺序依次是 View.onAttachedToWindow(), ViewGroup.onAttachedToWindow(), DecorView.onAttachedToWindow()(当然表面的调用顺序是反过来的, 使用super执行).
所以你在Activity的onCreate()里调用view.post, 实际走的是:ViewRootImpl.getRunQueue().post(action); 看代码:
static final class RunQueue {
private final ArrayList mActions = new ArrayList();
void post(Runnable action) {
postDelayed(action, 0);
}
void postDelayed(Runnable action, long delayMillis) {
HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
handlerAction.delay = delayMillis;
synchronized (mActions) {
mActions.add(handlerAction);
}
}
void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList actions = mActions;
final int count = actions.size();
for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
actions.clear();
}
}
}
也就是post只是加入到了数组中, 关键是什么时候调用executeActions(Handler)?
答案还是在ViewRootImpl的performTraversals()里调用, 而且还是使用mAttachInfo的Handler.
其实, 这个Handler是在ViewRootImpl的构造方法中传递给mAttachInfo(AttachInfo)的构造器的, 这个Handler在ViewRootImpl中使用主线程Looper构建, 属于ViewRootHandler实例.