WindowManager实现真正意义上的子线程更新View

WindowManager实现真正意义上的子线程更新View

我们日常工作时不会写子线程中更新View的代码,因为我们都知道子线程不能更新View。

在这里和大家同步一下,准确的来说不是子线程不能更新View,准确的来说是View不能在非View创建出的线程中更新。

具体原因我给大家讲一下。

这次发现主要是因为要实现一个一直动的悬浮窗,又不能使用SurfaceView,才发现的。

先从View开始讲

我们如果使用子线程更新View 会抛出这样的异常

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)

可以看出异常是从 ViewRootImpl中抛出方法是requestLayout中的checkThread

熟知自定义 View的话应该知道requestLayout是什么方法,那么我们来看一下checkThread

void checkThread() {
if (mThread != Thread.currentThread()) {
    throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
}

可以看的 如果mThread != Thread.currentThread() ,mThread不等于当前方法调用线程时抛出异常

那么接着看看  mThread是如何创建的

public ViewRootImpl(Context context, Displaydisplay) {
    /* ① 从WindowManagerGlobal中获取一个IWindowSession的实例。它是ViewRootImpl和
      WMS进行通信的代理 */
    mWindowSession= WindowManagerGlobal.getWindowSession(context.getMainLooper());
    // **②保存参数display**,在后面setView()调用中将会把窗口添加到这个Display上
    mDisplay= display;
    CompatibilityInfoHolder cih = display.getCompatibilityInfo();
    mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
    /* **③ 保存当前线程到mThread。**这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主程。
      在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检
      查发起请求的thread与这个mThread是否相同。倘若不同则会拒绝这个请求并抛出一个异常*/
    mThread= Thread.currentThread();
    ...
}

代码比较长我就放一点,可以看的mThread是在创建ViewRootImpl时,赋予的当前线程,此时是不是懂了一点,代码不是直接赋予的主线程,而是当前方法调用线程

那么ViewRootImpl是什么时候创建的呢,具体源码不细讲了,是在Activity onResume时,调用WindowManager 实际上是WindowManagerImp类是的AddView

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...

            ViewRootImpl root;
            View panelParentView = null;
        ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
...
        }
    }

所以我们正常创建的View都是从主线程创建的,那么ViewRootImpl所持有的线程也就是主线程

此时答案和解决方式已经很明了,我们想办法从子线程里创建一个ViewRootImpl不就行了

接着思考就是ViewRootImpl是WindowManager中addView方法调用的,那我们直接从子线程里addView就行了,其实就是这么简单

new Thread() {
    @Override
    public void run() {
        Looper.prepare();
        WindowManager  wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
        view = View.inflate(MainActivity.this, R.layout.item, null);
        tv = (TextView) view.findViewById(R.id.tv);
        params = new WindowManager.LayoutParams();
        params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        params.width = (int) (60 * getResources().getDisplayMetrics().density);
        params.height = (int) (60 * getResources().getDisplayMetrics().density);
        params.gravity = Gravity.LEFT | Gravity.TOP;
        params.format = PixelFormat.TRANSPARENT;
        width = wm.getDefaultDisplay().getWidth();
        height = wm.getDefaultDisplay().getHeight();
        params.y = height / 2 - params.height / 2;
        wm.addView(view, params);
        });
        Looper.loop();
    }
}.start();

就是我们常用的WindowManager创建View的方式

注意要写Looper,因为View更新的时候要使用子线程中的handler

此时就可以子线程更新View了,做多复杂的操作都不会影响主线程,是不是很新奇很简单

仔细封装封装又一个主线程呢。。。

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值