Android消息机制

一:子线程更新UI

安卓是单线程模型,那么子线程中是否能更新UI呢,答案是可以。

我们可以自己给它一个ViewRoot(WindowManager的addView中会走到new ViewRootImpl),这样ViewRoot的线程和view更新的线程在同一线程中,checkThread方法便可以执行通过,或者Activity中的ViewRootImpl初始化之前(onResume)更新UI。

第一种情况:WindowManager可以提供ViewRoot,这样就可以用WindowManager更新UI,WindowManager内部是通过Handler机制(addView在队列里加入view更新的消息队列,通过handler取出更新UI)所以需要Looper.prepare和looper开启子线程消息循环。

new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Looper.prepare();
                TextView tx = new TextView(MainActivity.this);
                WindowManager windowManager = MainActivity.this.getWindowManager();
                WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                        200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                        WindowManager.LayoutParams.TYPE_TOAST, PixelFormat.OPAQUE);
                windowManager.removeViewImmediate(tx);
                windowManager.addView(tx, params);
                Looper.loop();
//                ((TextView)findViewById(R.id.txt)).setText("子线程改变ui");
            }
        }).start();

第二种情况:

Activity的onResume中才会初始化ViewRootImpl,所以在onCreate和onResume周期之间的简短时间内可以执行子线程更新UI操作

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                ((TextView)findViewById(R.id.txt)).setText("子线程改变ui");
            }
        }).start();

二:子线程弹Toast

源码分析

public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }
        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN; ##TN真正实现toast显示到Window上
        tn.mNextView = mNextView;
        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

TN源码

private static class TN extends ITransientNotification.Stub {
       ................
        final Handler mHandler;
        WindowManager mWM;
        TN(String packageName, @Nullable Looper looper) {
            .........
             if (looper == null) {
                // Use Looper.myLooper() if looper is not specified.
                looper = Looper.myLooper();
                if (looper == null) {
                    throw new RuntimeException(
                            ## 直接子线程中显示toast会报这个错误
                            "Can't toast on a thread that has not called Looper.prepare()");
                }
            }
            mHandler = new Handler(looper, null) {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                        case SHOW: {
                            IBinder token = (IBinder) msg.obj;
                            handleShow(token);
                            break;
                        }
                        case HIDE: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            break;
                        }
                        case CANCEL: {
                            handleHide();
                            // Don't do this in handleHide() because it is also invoked by
                            // handleShow()
                            mNextView = null;
                            try {
                                getService().cancelToast(mPackageName, TN.this);
                            } catch (RemoteException e) {
                            }
                            break;
                        }
                    }
                }
            };
        }

        public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            // If a cancel/hide is pending - no need to show - at this point
            // the window token is already invalid and no need to do any work.
            if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) {
                return;
            }
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Since the notification manager service cancels the token right
                // after it notifies us to cancel the toast there is an inherent
                // race and we may attempt to add a window after the token has been
                // invalidated. Let us hedge against that.
                try {
                    mWM.addView(mView, mParams);
                    trySendAccessibilityEvent();
                } catch (WindowManager.BadTokenException e) {
                    /* ignore */
                }
            }
        }
      ............
    }

怎么正确显示toast呢

new Thread(new Runnable() {
        @Override
        public void run() {
             #子线程开启
              Looper.prepare();
              Toast.makeText(MainActivity.this,"子线程吐司",Toast.LENGTH_LONG).show();
              Looper.loop();
         }
}).start();

三:Loop.looper()方法为什么不会导致ANR

安卓整体的运行是由事件驱动的GUI单线程模型,接收事件消息并处理,导致ANR的原因,第一是事件未能得到处理,第二是已经处理但是未能及时处理完成。代码里的体现就是当Loop.looper轮训不到事件的时候,整个应用就退出了,所以没有轮训拿到事件应用是不可能一直有序运行的。

你可能感兴趣的:(Android消息机制)