当前位置: 首页 > news >正文

Android 字节飞书面经

Android 字节飞书面经

文章目录

  • Android 字节飞书面经
    • 一面
    • 二面

一面

1. 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
2. 根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
3. 在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
4. 所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
5. 内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
6. 操作系统管理了系统的有限资源,当有多个进程(或多个进程发出的请求)要使用这些资源时,因为资源的有限性,必须按照一定的原则选择进程(请求)来占用资源。**这就是调度。**
7. CPU的核和线程执行关系:通常情况下,一个核心对应一个物理线程,但通过超线程技术,一个核心可以对应两个线程,也就是说它可以同时运行两个线程。**在待执行线程数很多时,CPU核心数越高,多线程执行效率也就越高,但当执行线程数低于CPU核心数时,多线程执行效率其实与CPU核心数关系不大,而是更多的取决于线程访问其他资源的速度。**
8. **推荐阅读:**[**深度好文|面试官:进程和线程的所有知识点**](https://zhuanlan.zhihu.com/p/317144739)

扩展:

  1. 为什么要设计线程?

调度与执行的基本单位是线程,资源所有权(包括程序、数据、文件等资源。一个进程拥有对这些资源的所有权)是进程

线程的设计就是为了提高CPU、内存、I/O 设备的利用率,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异

  1. 执行与调度的基本单位是线程,这样设计有什么好处?

为了并发和隔离,并发是为了尽量让硬件利用率提高,线程是为了系统层面做到并发,线程上下文切换效率比进程上下文切换会高很多,这样可以提高并发效率。

隔离也是并发之后要解决的重要问题,计算机的资源一般是共享的,隔离要能保障崩溃了这些资源能够被回收,不影响其他代码的使用。所以说一个操作系统只有线程没有进程也是可以的,只是这样的系统会经常崩溃而已,操作系统刚开始发展的时候和这种情形很像。
程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

  1. 一个线程有哪些数据段?
    1. 个人觉得这个问题有点不对,我没有查到相应的答案,我感觉面试官是想问内存的分段
    2. 内存分段情况:
      1. **代码段(正文段):**代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
      2. 数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
      3. BSS段:BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
      4. 堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
      5. 栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
  2. 死锁的形式有其他的几种吗?(除了多个线程同时挣抢资源)
    1. 资源死锁:每个进程都在等待其他进程释放资源,而其他资源也在等待每个进程释放资源,这样没有进程抢先释放自己的资源,这种情况会产生死锁,所有进程都会无限的等待下去。
    2. 通信死锁:两个或多个进程在发送消息时出现的死锁。进程 A 给进程 B 发了一条消息,然后进程 A 阻塞直到进程 B 返回响应。假设请求消息丢失了,那么进程 A 在一直等着回复,进程 B 也会阻塞等待请求消息到来,这时候就产生死锁。通信中一个非常重要的概念来避免:超时(timeout) 如果超时时间到了但是消息还没有返回,就会认为消息已经丢失并重新发送,通过这种方式,可以避免通信死锁。
  3. 怎么在程序上解决死锁
    1. 这个问题只需要破解四种死锁形成的条件之一就OK了,互斥,循环等待,保持和等待,非抢占式
    2. 比如给每个线程设置一个占有资源的时间,时间一到必须释放锁
    3. 给线程设置优先级,允许高优先级的抢占低优先级的线程
  4. Java栈和堆区别
  5. 什么是原子变量,原子变量的代码实现
    1. 原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题。
    2. 源码实现:
public class AtomicInteger extends Number implements java.io.Serializable {private static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;static {try {//用于获取value字段相对当前对象的“起始地址”的偏移量valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;//返回当前值public final int get() {return value;}//递增加detlapublic final int getAndAdd(int delta) {//三个参数,1、当前的实例 2、value实例变量的偏移量 3、当前value要加上的数(value+delta)。return unsafe.getAndAddInt(this, valueOffset, delta);}//递增加1public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}
...
}

我们可以看到 AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。

  • volatile保证线程的可见性,多线程并发时,一个线程修改数据,可以保证其它线程立马看到修改后的值
  • CAS 保证数据更新的原子性。
  1. TCP和UDP的区别吗
  2. Java四种引用
  3. 注解是干嘛的?几种常用的注解
    1. 注解是用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解,主要作用
      1. 生成文档,通过代码里标识的元数据生成javadoc文档。
      2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
      3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
      4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
    2. Java自带的标准注解,包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
    3. 元注解,元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
    4. 推荐阅读:JAVA 注解的基本原理
  4. 线程池有什么用,几种线程池说一下
    1. 线程池的作用:
      1. 重用存在的线程池,减少对象的创建,消亡的开销,性能佳
      2. 可有效控制最大的并发线程数,提高系统资源的使用率,同时避免过多的资源竞争,避免堵塞
      3. 提供定时执行,定期执行,单线程,并发数控制等功能
    2. 线程池的优先级:核心线程池>阻塞队列>非核心线程池
    3. 各任务队列:
      1. ArrayBlockingQueue:内部维护了一个由固定大小的数组构成的数据缓冲队列,数组大小在队列初始化的时候就需要制定,可以制定访问策略ArrayBlockingQueue(int capacity, boolean fair) fair==true 则按照FIFO的顺序访问阻塞队列,若为false ,则访问顺序是不确定的
      2. LinkedBlockingQueue:基于单向链表的阻塞队列,内部维护了一个由单向链表构成的数据缓冲队列,区别于ArrayBlockingQueue,LinkedBlockingQueue在初始化的时候可以不设置容量大小,其默认大小为**Ineger.MAX_VALUE **
      3. PriorityBlockingQueue:基于优先级的阻塞队列,我们可以实现Comparable接口,我们实现Comparable接口,我们重写CompareTo就可以定义优先级顺序。队列容量是无限的,当线程数达到最大值时,会对任务队列扩容。
      4. DelayQueue:基于延迟时间优先级的阻塞队列,DelayQueue必须实现Delayed接口,(Delayed继承自Comparable接口),我们需重写Delayed.getDelay方法为元素的释放(执行任务)设置延迟(getDelay方法的返回值是队列元素被释放前的保持时间,如果返回0或一个负值,就意味着该元素已经到期需要被释放,因此我们一般用完成时间和当前系统时间作比较)。
      5. SynchronousQueue:基于同步的阻塞队列,他内部并没有数据缓存空间,元素只是会在试图取走的时候才有可能存在,也就是说,如果没有在插入元素时没有后续没有执行取出的操作,那么插入的行为就会被阻塞。
    4. 系统预设的线程池:
      1. CachedTheadPool:
        1. CachedThreadPool只有非核心线程池,当任务提交后,都会开辟一个非核心线程池来执行。
        2. 闲置线程超过60s后都会被回收
        3. 所有提交的任务都会被立即执行,(使用SynchronousQueue)
        4. CachedTreadPool在执行大量短生命周期异步任务事,可以显著提高程序性能。
      2. FixedThreadPool:
        1. 线程数固定,核心线程数==最大线程数,线程闲置时间是0,任务队列是LinkedBlockingQueue
        2. 核心线程池没有超时限制,任务队列容量没有大小限制
        3. FixedTreadPool使用于需要快速响应的场景
      3. SingleTreadExecutor:
        1. 核心线程池只有一个,最大线程数也是1,线程闲置时间为0,任务队列为LinkedBlockingQueue,任务数量没有大小限制
        2. 如果单个线程在执行过程中因为某些错误而终止,会创建新的线程代替他执行后续的任务
    5. 推荐阅读:
      1. 大话Android多线程(五) 线程池ThreadPoolExecutor详解
  5. 说说反射
  6. Android事件分发机制
  7. 讲一下Android的handler

二面

二面忘记录音了,有些问题只记得一半

  • RecycleView的局部刷新机制,我们往RecycleView进行差异化更新应该注意什么?
  • 线程死锁后怎么解决,当时我回答的是破坏死锁形成的四个条件,比如设置时间片,但是下一次怎么恢复现场?
  • 操作系统的跨进程通信的方式你知道几种实现细节
  • ANR的触发原理
    • 输入事件超时5s
    • 广播类型超时(前台15s,后台60s)
    • 服务超时(前台20s,后台200s)
    • Activity的生命周期超时不会ANR
  • 导致ANR的原因:
    • 应用层耗时导致的ANR
      • 函数阻塞:如死循环,主线程IO,处理大量数据
      • 锁出错:线程对锁的挣抢
      • 内存紧张:系统分配给一个应用的内存是用上限的,内存紧张会导致一些应用操作导致耗时
    • 系统层ANR
      • CPU被抢占
      • 内存紧张
    • 分析堆栈信息trace文件,查看CPU负载,内存信息,看主线程执行耗时操作,线程被锁争抢
  • 卡顿优化方案
    • 布局优化
      • 减少冗余或者嵌套的布局来降低视图的层级,比如使用约束布局代替线性布局和相对布局
      • 使用ViewStub替代在启动过程中不需要显示的UI控件
      • 使用自定义View代替复杂的View叠加
    • 减少主线程耗时操作
      • 主线程不要直接操作数据库,数据库的操作应该放在子线程中进行
      • Sharedpreferences尽量使用apply操作,少使用commit
      • 网络请求放在子线程,
      • 不要在activity的OnResume和OnCreate,View的OnDraw中进行耗时操作,比如大量的计算
    • 减少过度绘制
    • 列表优化
      • RecyclerView使用优化,使用DiffUtil和notifyItemDataSetChanged进行局部更新
    • 减少小对象的分配和回收
  • Android四大组件如何实现跨进程通信?跨进程通信过程中如何实现数据共享?除了mmap还有吗?(当时自己想到了socket但是socket不太熟),给自己挖坑了
    • **使用Bundle **,由于bundle实现了Parcelable接口,可以在不同进程间传输。
    • **使用文件共享,**两个进程通过读一个文件来交换数据,Android是基于Linux,可以并发读写,并发读的话可能导致读出的数据不是最新的,并发写的话会把问题写错。文件共享适用于对数据同步要求不高的进程通信。多进程下不建议使用sharedpreferences,会有很大概率丢失数据,sharedpreferences是在一个个进程会有自己的缓存。
    • **使用Messenger,是一种轻量级的IPC方案,底层是AIDL **,服务端需要创建一个Service,创建一个handler,并通过他来创建一个Messenger对象,然后在Service的OnBind的方法返回,客户端需要绑定Service,绑定成功后使用服务端返回的IBinder创建一个Messenger,这样客户端就可以和服务端发送消息了,消息类型是Messenger,如果希望服务端也能发送消息给客户端,在客户端创建一个handler用来处理服务端发送的消息,使用这个handler创建一个Messenger,将Messenger通过replyTo返回给服务器,服务器拿到客户端的Messenger就可以进行通信了。
    • **使用AIDL,**服务端创建一个Service用来监听客户端的请求连接,创建一个AIDL文件,将暴露给客户端的接口在这个文件中声明,最后在Service中,声明实现AIDL的接口,客户端绑定服务端的Service,将服务端成功返回的Binder对象转为AIDL接口所属于的类型,接着可以调用AIDL的方法
    • **ContentProvider,**可以理解为受约束的AIDL,主要提供数据源的CRUD
    • **Socket,**服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了
  • 自定义view,一定得重写measure layout draw 方法吗?你觉得那个过程最耗时?关于MeasureSpec你觉得我们可以自己手动去更改值吗?我回答可以,面试官反问:如何可以手动更改值,那岂不是MeasureSpec没什么用了吗?
    1. 不一定
    2. 自定义组合控件类型不需要重写这三个方法,只需要初始化UI,添加一些监听,对外暴露一些方法
    3. 继承系统的控件(textview),我们希望复用系统的onMeaseure 和 onLayout ,所以我们只要重写onDraw
    4. 继承View,我们得重写**onMeasure 和 onDraw **,在View的源码当中并没有对AT_MOST和EXACTLY两个模式做出区分,也就是说View在wrap_content和match_parent两个模式下是完全相同的,都会是match_parent,显然这与我们平时用的View不同,所以我们要重写onMeasure方法。
    5. 继承ViewGroup,我们得重写onMeasure和onLayout
    6. 推荐阅读:Android自定义View全解
  • 在子线程中除了handler机制回到主线程更新数据,还有其他吗?
    • Handler:
      • 子线程(非UI线程)调用handler对象sendMessage(msg)方法,将消息发送给关联Looper,Looper将消息存储在MessageQueue消息队列里面。
      • 然后轮巡取出MessageQueue中的消息给UI线程中handler处理,handler得到消息调用handleMessage方法处理消息,从而可以更新Ui。
    • Activity.runOnUiThread
#  Activity.runOnUiThread
/*** Runs the specified action on the UI thread. If the current thread is the UI* thread, then the action is executed immediately. If the current thread is* not the UI thread, the action is posted to the event queue of the UI thread.** @param action the action to run on the UI thread*/public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}}
    * 看源码很明显知道当前线程是主线程的时候,立马会执行run() 方法,当前线程非主线程会把action入队列MessageQueue
- **View.post(Runable r)**
public boolean post(Runnable action) {final AttachInfo attachInfo = mAttachInfo;if (attachInfo != null) {return attachInfo.mHandler.post(action);}// Postpone the runnable until we know on which thread it needs to run.// Assume that the runnable will be successfully placed after attach.getRunQueue().post(action);return true;
}

先看第一种情况,AttachInfo !=null

    * <font style="color:#52C41A;">attachInfo.mHandler.post(action);  </font>由此便知Runablle是交给attachInfo的handler去处理了* 我们得知道**attchInfo是什么**,AttchInfo是View的内部类,其内部持有一个 Handler 对象,从注释可知它是由 ViewRootImpl 提供的
final static class AttachInfo {/*** A Handler supplied by a view's {@link android.view.ViewRootImpl}. This* handler can be used to pump events in the UI events queue.*/@UnsupportedAppUsagefinal Handler mHandler;AttachInfo(IWindowSession session, IWindow window, Display display,ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,Context context) {···mHandler = handler;···}···
}
    * 看看**attchInfo**初始化时机:
# ViewRootImpl   
final ViewRootHandler mHandler = new ViewRootHandler();public ViewRootImpl(Context context, Display display, IWindowSession session,boolean useSfChoreographer) {···mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);···}private void performTraversals() {···if (mFirst) {···host.dispatchAttachedToWindow(mAttachInfo, 0);···}···performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);performLayout(lp, mWidth, mHeight);performDraw();···}

可以跟踪到 ViewRootImpl 类。ViewRootImpl 内就包含一个 Handler 对象 mHandler,并在构造函数中以 mHandler 作为构造参数之一来初始化 mAttachInfo。ViewRootImpl 的performTraversals()方法就会调用 DecorView 的 dispatchAttachedToWindow 方法并传入 mAttachInfo,从而层层调用整个视图树中所有 View 的 dispatchAttachedToWindow 方法,使得所有 childView 都能获取到 mAttachInfo 对象

    * **继续看dispatchAttachedToWindow(mAttachInfo, 0)方法**
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)void dispatchAttachedToWindow(AttachInfo info, int visibility) {mAttachInfo = info;···}

mAttachInfo 的赋值时机可以追踪到 View 的 dispatchAttachedToWindow 方法,至此,我们可以很清楚知道mAttachInfo的初始化流程

:::success

  1. ViewRootImpl的构造方法里面开始初始化 mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);这里传了mhandler,说mAttachInfol的引用是指向ViewRoot Impl的handler的。
  2. #ViewRootImpl.performTraversals()会调用#View. dispatchAttachedToWindow(mAttachInfo, 0);再看#View. dispatchAttachedToWindow方法,我们才发现真正的View.mAttachInfo赋值是在这里。
  3. 此外,performTraversals()方法也负责启动整个视图树的 Measure、Layout、Draw 流程,只有当 performLayout 被调用后 View 才能确定自己的宽高信息。而 performTraversals()本身也是交由 ViewRootHandler 来调用的,即整个视图树的绘制任务也是先插入到 MessageQueue 中,后续再由主线程取出任务进行执行。由于插入到 MessageQueue 中的消息是交由主线程来顺序执行的,所以 attachInfo.mHandler.post(action)就保证了 action 一定是在 performTraversals 执行完毕后才会被调用,因此我们就可以在 Runnable 中获取到 View 的真实宽高了

:::

    * 看第二种情况:<font style="color:#F5222D;">getRunQueue().post(action);</font>* <font style="color:#F5222D;">getRunQueue() </font><font style="color:#000000;">方法返回的是</font><font style="color:rgba(46, 36, 36, 0.87);">HandlerActionQueue 我们先看一下代码</font>
public class HandlerActionQueue {private HandlerAction[] mActions;private int mCount;public void post(Runnable action) {postDelayed(action, 0);}public void postDelayed(Runnable action, long delayMillis) {final HandlerAction handlerAction = new HandlerAction(action, delayMillis);synchronized (this) {if (mActions == null) {mActions = new HandlerAction[4];}mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);mCount++;}}public void executeActions(Handler handler) {synchronized (this) {final HandlerAction[] actions = mActions;for (int i = 0, count = mCount; i < count; i++) {final HandlerAction handlerAction = actions[i];handler.postDelayed(handlerAction.action, handlerAction.delay);}mActions = null;mCount = 0;}}private static class HandlerAction {final Runnable action;final long delay;public HandlerAction(Runnable action, long delay) {this.action = action;this.delay = delay;}public boolean matches(Runnable otherAction) {return otherAction == null && action == null|| action != null && action.equals(otherAction);}}···}

很明显可以知道,HandlerActionQueue 可以看做是一个专门用于存储 Runnable 的任务队列,mActions 就存储了所有要执行的 Runnable 和相应的延时时间。两个post方法就用于将要执行的 Runnable 对象保存到 mActions中,executeActions就负责将mActions中的所有任务提交给 Handler 执行

    * <font style="color:#000000;">现在看一下</font><font style="color:#F5222D;">executeActions </font><font style="color:#000000;">被谁调用</font>
#view@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)void dispatchAttachedToWindow(AttachInfo info, int visibility) {mAttachInfo = info;···// Transfer all pending runnables.if (mRunQueue != null) {mRunQueue.executeActions(info.mHandler);mRunQueue = null;}···}

现在我们知道了,这个操做也是#View.dispatchAttachedToWindow ,从而使得 mActions 中的所有任务都会被插入到 mHandler 的 MessageQueue 中** 等到主线程执行完 performTraversals() 方法后就会来执行 mActions,所以此时我们依然可以获取到 View 的真实宽高**

    * **推荐阅读:**[**一文读懂 View.Post 的原理及缺陷**](https://juejin.cn/post/6939763855216082974#heading-3)
- **AsyncTask*** 内部就是一个Handler和线程池的封装。在线程池中执行后台任务,并在执行过程中将执行进度传递给主线程,当任务执行完毕后,将最终结果传递给主线程。
- **<font style="color:rgb(51, 51, 51);">Rxjava</font>**

:::success
通过操作符切换线程

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

:::

  • Android 什么时候会用到子线程?我当时回答的是可以考虑将耗时操作放在子线程的场景都可以,比如:网络请求,数据库的读写,热启动将耗时操作放在子线程进行,磁盘IO
  • ApplicationContext和ActivityContext有什么区别?ApplicationContext启动Activity有什么要注意的?我当时回答:必须要加FLAG_ACTIVITY_NEW_TASK标志位,面试官反问:你觉得还有什么要注意的吗?
    • Context是上下文环境,是应用程序环境信息的接口,使用Context的调用方法,比如启动Activity,访问资源,调用系统服务,传入Context,比如弹出Toast,创建Dialog
    • Activity,Service和Application都是间接的继承自Context的,因此一个应用程序中Context的数量=Activity+Service+Application ,这个类的继承关系如下:
    • ApplicationContext:生命周期与Application的生命周期一样长,全局唯一,与activity的生命周期无关
    • Activity:生命周期与Activity相关,每一个Activity都会有一个对应的ActivityContext
    • 凡事跟UI相关的,都应该使用Activity作为Context来处理,其他的一些操作,Service,Activity,Appliocation等实例都可以
    • 为了防止内存泄漏,使用context的时候特别注意:
      • 不要让生命周期很长的引用activity context,即保证引用activity的对象要与activity生命周期一样长
      • 生命长的对象引用使用ApplicationContext
  • 为什么子线程不可以更新UI
    • 其实子线程是可以更新UI的,只要在ViewRootImpl创建之前就行了,因为在更新UI操作的时候,我们会调用ViewRootImpl的checkThread方法,去判断mThread != Thread.currentThread() ,若为true,则会抛出异常
    • ViewRootImpl的创建时机是什么时候呢?Activity.makeVisible---->WindowManagerImpl.addView---->WindowManagerGlobal.addView---->new ViewRootImpl() ,这个时候我们知道了ViewRootImpl的创建调用过程,那我们更加关注的是谁调用了Activity.makeVisible,从Acitivity声明周期handleResumeActivity会被优先调用到,也就是说在handleResumeActivity启动后(OnResume),ViewRootImpl就被创建了,这个时候,就无法在在子线程中访问UI了。
    • 其实自线程更新UI,会导致UI显示的不确定性,线程共同访问更新操作同一个UI控件时容易发生不可控的错误但是若是我们加锁,耗时会比较多。
    • 推荐阅读:Android 子线程更新UI了解吗?

相关文章:

Android 字节飞书面经

Android 字节飞书面经 文章目录 Android 字节飞书面经一面二面 一面 1. 线程是进程的一部分&#xff0c;一个线程只能属于一个进程&#xff0c;而一个进程可以有多个线程&#xff0c;但至少有一个线程。 2. 根本区别&#xff1a;进程是操作系统资源分配的基本单位&#xff0c;…...

选择好友窗口(三)

选择好友窗口&#xff08;三&#xff09; 前言 在前两集我们就完成了整个左半部分的内容了&#xff0c;那么我们这一集开始就要完成我们的选择好友窗口的右侧部分。 需求分析 我们要做出的效果如下图 可以看到我们整个布局是一个网格的布局&#xff0c;当然也可以做成一个垂…...

【含文档】基于ssm+jsp的音乐播放系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: apache tomcat 主要技术: Java,Spring,SpringMvc,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定义了两个…...

【C语言】动态内存开辟

写在前面 C语言中有不少开辟空间的办法&#xff0c;但是在堆上开辟的方法也就只有动态内存开辟&#xff0c;其访问特性与数组相似&#xff0c;但最大区别是数组是开辟在栈上&#xff0c;而动态内存开辟是开辟在堆上的。这篇笔记就让不才娓娓道来。 PS:本篇没有目录实在抱歉CSD…...

Redis缓存在thinkPHP/fastAdmin框架中的应用

Redis缓存在thinkPHP/fastAdmin框架中的应用 引言 在现代Web开发中&#xff0c;性能优化是一个永恒的话题。随着用户数量的增加和业务逻辑的复杂化&#xff0c;如何提高应用的响应速度和处理能力成为了开发者们必须面对的挑战。缓存技术作为提升性能的有效手段之一&#xff0…...

Ceisum无人机巡检视频投放

公司投标内容有个视频投放的功能动画&#xff0c;原本想实现这么一个效果&#xff1a; 案例效果来自别人的展示作品&#xff0c;Leader一眼就相中了这个效果&#xff0c;可惜别人的终究是别人的&#xff0c;又不会白白给你&#xff0c;终究是要自己动手尝试。 动画方面的展示…...

分享几款开源好用的图片在线编辑,适合做快速应用嵌入

图片生成器是指一种工具或软件&#xff0c;用于自动生成图片或图像内容&#xff0c;通常依据用户设定的参数或模板进行操作。这种工具能够帮助用户快速创建视觉效果丰富的图像&#xff0c;而无需具备专业的设计技能。 在数字化时代&#xff0c;图片编辑已经成为日常工作和生活的…...

闪存学习_1:Flash-Aware Computing from Jihong Kim

闪存学习_1&#xff1a;Flash-Aware Computing from Jihong Kim 前言一、Storage Media&#xff1a;NAND Flash Memory1、概念2、编程和擦除操作3、读操作4、异地更新操作&#xff08;Out-Place Update&#xff09;5、数据可靠性6、闪存控制器&#xff08;SSD主控&#xff09;7…...

如何将本地项目上传至Gitee仓库(详细教程)

前提条件 1、本地电脑安装Git客户端 2、本地已有项目 3、Gitee注册好了账户 如果没有安装Gitee 可以区菜鸟查看一下安装教程 Git教程https://www.runoob.com/git/git-tutorial.html 操作示例 前提条件已经准备好的情况下登录gitee 码云 https://gitee.com 点解右侧加号 新…...

redis详细教程(3.hash和set类型)

hash Redis中的Hash是一种数据结构&#xff0c;用于存储键值对集合。在Redis中&#xff0c;Hash非常适合表示对象&#xff0c;其中对象的每个字段都对应一个键值对。以下是关于Redis中Hash的详细讲解&#xff1a; 特点&#xff1a; 1. 键值对集合&#xff1a;Hash是一个包含…...

性能测试详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、 性能测试术语解释 1. 响应时间 响应时间即从应用系统发出请求开始&#xff0c;到客户端接收到最后一个字节数据为止所消耗的时间。响应时间按软件的特点…...

修改ruoyi的logo和标题

1、修改网页标签上的图标和标题 静态修改 标签页上的 图标 直接替换public/favicon.ico文件为你自己的图标文件 如果你的图标文件名不是favicon.ico&#xff0c;你还需要在public/index.html中相应地修改这一行&#xff0c;将/favicon.ico替换为你的图标文件路径。 静态修改…...

vue系列==vue3新语法

vue系列vue3新语法 1、setup组合式API入口函数 2、利用ref函数定义响应式数据 3、利用reactive函数定义响应式数据 1、setup组合式API入口函数 1、Vue3为组合式API提供了一个setup函数&#xff0c;所有组合式API函数都是在此函数中调用的&#xff0c;它是组合式API的使用入口…...

001-Kotlin界面开发之Jetpack Compose Desktop学习路径

Compose Desktop学习之路 学习过程 理解Kotlin的基本语法 Compose Desktop采用Kotlin构建&#xff0c;因此对Kotlin的基本语法有很好的理解是必不可少的。你可以从官方的Kotlin文档开始。 用一句话概括&#xff0c;Kotlin是一种现代的、静态类型的编程语言&#xff0c;它结合…...

qt QStackedLayout详解

QStackedLayout类提供了一种布局方式&#xff0c;使得在同一时间内只有一个子部件&#xff08;或称为页面&#xff09;是可见的。这些子部件被维护在一个堆栈中&#xff0c;用户可以通过切换来显示不同的子部件&#xff0c;适合用在需要动态显示不同界面的场景&#xff0c;如向…...

python实现钉钉群机器人消息通知(消息卡片)

直接上代码 python """ 飞书群机器人发送通知 """ import time import urllib3 import datetimeurllib3.disable_warnings()class DingTalkRobotAlert():def __init__(self):self.webhook webhook_urlself.headers {Content-Type: applicatio…...

防火墙技术应用

目录 防火墙安全功能指标防火墙性能指标防火墙部署防火墙应用案例-Linux防火墙防火墙应用案例-华为包过滤防火墙ACL(基本)防火墙应用案例-工控防火墙部署 防火墙安全功能指标 我们防火墙也会提供各种各样的互联接口&#xff0c;它这种接口有以太网、快速以太网、千兆以太网&am…...

docker engine stopped

1&#xff09;环境&#xff1a;win 10 2&#xff09;docker安装时已经已经安装了虚拟机 3&#xff09;启用网络适配器 4&#xff09;启用docker服务&#xff08;依赖服务LanmanServer&#xff09; 5&#xff09;全都弄好了&#xff0c;docker还是打不开&#xff0c;没办法了&a…...

Redis- 内核的分配内存限制的警告“WARNING Memory overcommit must be enabled!”

文章目录 问题描述报错原因影响解决方案注意事项 问题描述 [rootredisxxx]# redis-sentinel ./sentinel.conf 19638:X 01 Nov 2024 16:57:27.180 # WARNING Memory overcommit must be enabled! Without it, background save or replication may fail under low memory condi…...

腾讯云在线扩容数据盘

文章目录 一、查询云硬盘使用的分区形式二、根据不同的云硬盘类型进行在线扩容分区2.1 安装 gdisk 工具。2.2 安装 growpart 工具使用 growpart 工具扩容分区 三、根据不同的云硬盘类型进行在线扩容文件系统3.1 扩容 EXT 文件系统3.2 扩容 XFS 文件系统 一、查询云硬盘使用的分…...

后进先出(LIFO)详解

LIFO 是 Last In, First Out 的缩写&#xff0c;中文译为后进先出。这是一种数据结构的工作原则&#xff0c;类似于一摞盘子或一叠书本&#xff1a; 最后放进去的元素最先出来 -想象往筒状容器里放盘子&#xff1a; &#xff08;1&#xff09;你放进的最后一个盘子&#xff08…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...