android-如何在子线程中更新ui
itomcoil 2025-04-29 01:23 10 浏览
正如我们知道的,android是不让在子线程中更新ui的。在子线程中更新ui会直接抛出异常
Only the original thread that created a view hierarchy can touch its views
那么这种检查机制在什么时候发生的呢?
那么真的不能在子线程中更新ui么?我们带着这个疑问来看一下系统代码
我们知道android中的view的更新(大小,位置,内容)全部都交给了WindowManager,那么我们带着疑问来看下WindowMagager接口的实现类WindowManagerImpl,中如何控制对view的更新的
我们知道WindowManager中有三个常用方法 addView(),removeView()和updateViewLayout();
接下来我们只分析updateViewLayout()方法。
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
applyDefaultToken(params);方法和Window的层级有关系,这里和我们探讨的view的跟新没有关系,因此跳过
mGlobal.updateViewLayout(view, params); 发现windowManager的更新其实是交给了mGlobal来操作了,那么mGlobal是什么呢?
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
发现mGlobal其实是WindowManaerImpl一个成员变量,而且还是单例。其实WindowManagerImpl的跟新委托给了WindowManagerGlobal
那么WindowManagerGlobal的updateViewLayout()方法里面完成了什么功能呢?
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
前半部分是异常判断,跳过
下面是给view设置布局参数,新的布局参数。
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
下面是找到viewRootImpl,给root重新设置布局参数。
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
那么ViewRootImpl是什么呢?其实是android系统中view和WindowManager通讯的桥梁。比如测量 布局 绘制 时间分发 都是在这里传递给view的
接下来我们分析 root.setLayoutParams(wparams, false);这段代码。
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
代码比较长,这里截取部分代码 requestLayout();
那么requestLayout中做了什么操作呢?
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
终于到了重点 checkThread(),在这个方法中做了一个判断,就是当前更新ui的线程是否和ViewRootImpl创建的线程是否是同一个,不是则抛出异常
下面是checkThread代码
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
那么mThread是什么时候创建的呢?下面我们看下ViewRootImpl的构造方法
那么viewRootImpl对象什么时候创建的呢?其实在WindowManagerImpl的addview中调用了WindowManagerGlobal的addview。在WindowManagerGlobal的addView的时候创建了ViewRootImpl对象
现在我们终于理清楚了,不能在子线程中更新ui的原因。
如果ViewRootImpl是在更新ui的时候,做了一个判断。判断创建自己的线程和更新ui的线程是否是同一个,不是,直接异常。
那么我们能否手动的创建一个子线程,在这个线程中创建一个viewRootImpl呢?
下面我们带着疑问写一个demo
先看效果图
下面是我们点击之后。在子线程中更新ui的效果图
代码的原理是,我们在子线程中通过WindowManager添加一个view,而这个window所有的层级是系统层级。因此有悬浮效果。而我们创建的这个view因为是在子线程中直接创建了一个window,这个window的级别比较高,所以能显示在其他应用上面。而这个window又没有父window,因此其会单独创建ViewRootImpl对象,而这个对象又是在子线程中创建的,那么我们更新ui的时候,在这个子线程中更新能够成功。
下面是核心代码,我们将会一步一步对其进行分析
new Thread() {
@Override
public void run() {
Looper.prepare();
wm = (WindowManager) MyApplication.ctx.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);
view.setOnTouchListener(new View.OnTouchListener() {
private int y;
private int x;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int minX = (int) (event.getRawX() - x);
int minY = (int) (event.getRawY() - y);
params.x = Math.min(width - params.width, Math.max(0, minX + params.x));
params.y = Math.min(height - params.height, Math.max(0, minY + params.y));
wm.updateViewLayout(view, params);
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_UP:
if (params.x > 0 && params.x < width - params.width) {
int x = params.x;
if (x > (width - params.width) / 2) {
params.x = width - params.width;
} else {
params.x = 0;
}
wm.updateViewLayout(view, params);
} else if (params.x == 0 || params.x == (width - params.width)) {
Toast.makeText(MainActivity.this, "被电击了", Toast.LENGTH_SHORT).show();
tv.setText("abcd");
}
break;
}
return true;
}
});
Looper.loop();
}
}.start();
首先准备Looper,之后loop。因为更新view的时候会在当前的子线程中使用handler。而使用handler必须要looper。
接下来拿到windowManager wm = (WindowManager)
MyApplication.ctx.getSystemService(WINDOW_SERVICE);填充view
WindowManager.LayoutParams.TYPESYSTEMERROR; 设置type,将window的级别设置较大,能够显示在其他的window之上params.flags =
WindowManager.LayoutParams.FLAGNOTFOCUSABLE |
WindowManager.LayoutParams.FLAG_NOTTOUCHMODAL;这里是设置window透传,也就是当前view所在的window不阻碍底层的window获得触摸事件。接下来设置window的宽度和高度
params.format = PixelFormat.TRANSPARENT;设置透明 否则的话 圆形view后面显示一层黑色,默认效果是黑色。需要设置,才能体现出圆形。
接下来就是设置Gravity了,这里比较简单,因为想实现悬浮窗口的拖拽效果,因此需要修改WindowManager的LayoutParams的x,y值。因此需要和gravity配合使用
接下来就是将view添加到WindowManager中了
剩下的就是触摸事件了
在松手的时候判断了,更新了view中显示的ui
下面是更新效果图
初始文本为Click
因此能否在子线程中更新ui,由ViewRootImpl在哪个线程中创建决定。因此我们更应该将能更新ui的线程成为ui线程而不是主线程。
本文为头条号作者原创。未经授权,不得转载。
相关推荐
- 点过的网页会变色?没错,这玩意把你的浏览记录漏光了
-
提起隐私泄露这事儿,托尼其实早就麻了。。。平时网购、换手机号、注册各种账号之类的都会咔咔泄露,根本就防不住。但托尼真是没想到,浏览器里会有一个看起来完全人畜无害的功能,也在偷偷泄露我们的个人隐私,而且...
- Axure教程:高保真数据可视化原型
-
本文将介绍如何制作Axure高保真数据可视化原型,供大家参考和学习。高保真数据可视化原型设计,称得上是Axure高阶水平。数据可视化在原型设计中是一个重要的分支,但是对于Axure使用者具有一定要求。...
- Flutter web开发中禁用浏览器后退按钮
-
路由采用的go-router路由框架:finalrootNavigatorKey=GlobalKey<NavigatorState>();finalGoRouterrouter...
- jQuery 控制属性和样式
-
标记的属性each()遍历元素:each(callback)方法主要用于对选择器进行遍历,它接受一个函数为参数,该函数接受一个参数,指代元素的序号。对于标记的属性而言,可以利用each()方法配合th...
- 微信小程序入门教程之二:页面样式
-
这个系列的上一篇教程,教大家写了一个最简单的Helloworld微信小程序。但是,那只是一个裸页面,并不好看。今天接着往下讲,如何为这个页面添加样式,使它看上去更美观,教大家写出实际可以使用的页...
- 如何在Windows11的任务栏中禁用和删除天气小部件图标?
-
微软该公司已在Windows11的任务栏中添加了一个天气小部件图标,作为小部件的入口点。这个功能与之前Win10上的新闻与资讯功能相同,但是有的用户不喜欢想要关闭,不知道如何操作,下面小编为大家带来...
- CSS伪类选择器大全:提升网页交互与样式的神奇工具
-
CSS伪类选择器是前端开发中不可或缺的强大工具,它们允许我们根据元素的状态、位置或用户行为动态地应用样式。本文将全面介绍常用的伪类选择器,并通过代码示例展示其实际应用场景。一、基础交互伪类1.超链接...
- 7个Axure使用小技巧
-
编辑导读:对于Axure原型工具,很少有产品经过系统学习,一般都是直接上手,边摸索边学习,这直接导致很多快捷操作被忽视。笔者在日常工作中总结出以下小技巧,希望对各位有帮助。之前整理了2期Axure的...
- JavaScript黑暗技巧:禁止浏览器点击“后退”按钮
-
浏览网页时,当从A页面点击跳转到B页面后,一般情况下,可以点击浏览器上的“后退”按钮返回A页面。如果进入B页面后,B页面想让访问者留下,禁止返回,是否可以实现呢?这简直是要控制浏览器的行为,虽然有些邪...
- 对齐PyTorch,一文详解OneFlow的DataLoader实现
-
撰文|赵露阳在最新的OneFlowv0.5.0版本中,我们增加了许多新特性,比如:新增动态图特性:OneFlow默认以动态图模式(eager)运行,与静态图模式(graph)相比,更容易搭建网...
- Python计算机视觉编程 第一章 基本的图像操作和处理
-
以下是使用Python进行基本图像操作和处理的示例代码:使用PIL库加载图像:fromPILimportImageimage=Image.open("image.jpg"...
- PyTorch 深度学习实战(31):可解释性AI与特征可视化
-
在上一篇文章中,我们探讨了模型压缩与量化部署技术。本文将深入可解释性AI与特征可视化领域,揭示深度学习模型的决策机制,帮助开发者理解和解释模型的内部工作原理。一、可解释性AI基础1.核心概念特征重要...
- 学习编程第177天 python编程 富文本框text控件的使用
-
今天学习的是刘金玉老师零基础Python教程第72期,主要内容是python编程富文本框text控件。一、知识点1.tag_config方法:利用某个别名作为标签,具体的对应标签的属性功能配置在后面参...
- 用Python讓電腦攝像頭實現掃二維碼
-
importsys#系統模組,用來存取命令列參數與系統功能importcv2#OpenCV,處理影像與相機操作importnumpyasnp#Numpy,用來處理數值與...
- 使用Transformer来做物体检测
-
作者:JacobBriones编译:ronghuaiyang导读这是一个Facebook的目标检测Transformer(DETR)的完整指南。介绍DEtectionTRansformer(D...
- 一周热门
- 最近发表
- 标签列表
-
- ps图案在哪里 (33)
- super().__init__ (33)
- python 获取日期 (34)
- 0xa (36)
- super().__init__()详解 (33)
- python安装包在哪里找 (33)
- linux查看python版本信息 (35)
- python怎么改成中文 (35)
- php文件怎么在浏览器运行 (33)
- eval在python中的意思 (33)
- python安装opencv库 (35)
- python div (34)
- sticky css (33)
- python中random.randint()函数 (34)
- python去掉字符串中的指定字符 (33)
- python入门经典100题 (34)
- anaconda安装路径 (34)
- yield和return的区别 (33)
- 1到10的阶乘之和是多少 (35)
- python安装sklearn库 (33)
- dom和bom区别 (33)
- js 替换指定位置的字符 (33)
- python判断元素是否存在 (33)
- sorted key (33)
- shutil.copy() (33)