对Android App UI线程的一点理解

1. ANR

ANRs (“Application Not Responding”),意思是”应用没有响应“

发生ANR最常见的一种情况: UI线程中执行长时间的任务,如IO或网络操作, 导致UI线程5秒内没有响应输入事件

 

参考 http://blog.youkuaiyun.com/leilu2008/article/details/6689405

 

2. 如何避免ANR

防止UI线程中执行长时间的任务,避免UI线程阻塞。 以Activity为例,我们应该避免在Activity的生命周期方法中执行耗时操作。

3. 问题

Android系统创建UI线程时,同时在这个线程中启动了一个消息循环(Looper), Looper.loop()源码注释指出该方法可能阻塞。 这里的loop()显然是在UI线程中执行,不会阻塞UI线程吗?为什么没有引起ANR?

 

ActivityThread.java

 

    public static final void main(String[] args) {
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        ...
        Looper.loop();
        ...
   }

 

Looper.java

 

    public static final void loop() {
        Looper me = myLooper();
        MessageQueue queue = me.mQueue;
        
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
        while (true) {
            Message msg = queue.next(); // might block
            //if (!me.mRun) {
            //    break;
            //}
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }
                if (me.mLogging!= null) me.mLogging.println(
                        ">>>>> Dispatching to " + msg.target + " "
                        + msg.callback + ": " + msg.what
                        );
                msg.target.dispatchMessage(msg);
                if (me.mLogging!= null) me.mLogging.println(
                        "<<<<< Finished to    " + msg.target + " "
                        + msg.callback);
                
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf("Looper", "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
                
                msg.recycle();
            }
        }
    }

 

4. Looper不会阻塞UI线程

Looper不会阻塞UI线程。 原因是,UI线程消息循环开始后, UI线程内发生的所有操作(以Activity为例,比如Activity的生命周期的管理)都是通过这个消息循环来完成。即,Activity的生命回调方法(onCreate, onResume)最终都会以一种间接的方式由这个消息循环来调用。 

 

参考  http://stackoverflow.com/questions/6984263/android-looper-and-call-stack 

http://stackoverflow.com/questions/5193913/handlers-messagequeue-looper-do-they-all-run-on-the-ui-thread

 

5. 分析

 

Activity启动时相关的几个类

 

注意图中的ActivityManagerProxy和ActivityManagerService,后者为服务端, 前者可以看作它的远程代理。 ActivityThread通过这个代理进行IPC调用。另外, ActivityThread的一个类型为ApplicationThread(可以看出, 它间接地继承自Binder, 并且是ActivityThread的内部类)成员变量作为远程调用的参数被传递给了ActivityManagerService, 这里的ApplicationThread是ActivityManagerService能够调用Activity的回调方法的关键。

 

Activity启动的时序图

 

由上图可以看出,一系列调用过程最终结果是一个相应Message进入MessageQueue, 由ActivityThread的一个Handler成员变量进行消息的处理。其处理流程基本可归纳为


handleXXXActivity() ----> performXXXActivity()---->Instrument.callActivityOnXXX()--->Activity.onXXX()


这里的Activit.onXXX()就是我们编写的Activity生命周期方法。

 

所以说Activity的生命周期方法是通过UI线程的消息循环来完成, 并不存在Looper阻塞UI线程的问题。我们需要注意的就是避免在这些回调方法中执行耗时操作。

 

<think>我们正在解决Android UI线程卡住的问题。根据引用内容,UI线程卡住通常是由于在主线程执行耗时操作(如网络请求、数据库操作、复杂计算等)导致的。以下是一些有效的解决方法: ### 1. **识别并移除非UI操作到子线程** - **耗时操作类型**: - 网络请求(如HTTP请求) - 数据库操作(如SQLite读写) - 文件I/O(如读写SD卡文件) - 复杂计算(如图像处理、大量数据解析) - **解决方案**: - 使用`AsyncTask`(已过时,但适用于简单场景) - 使用`HandlerThread`或`IntentService` - 使用现代并发工具:`RxJava`、`Kotlin协程`或`java.util.concurrent`包(如`ExecutorService`) ### 2. 使用Handler进行线程间通信 - 在子线程执行耗时操作后,通过`Handler`将结果发送到主线程更新UI: ```java // 在主线程创建Handler Handler mainHandler = new Handler(Looper.getMainLooper()); new Thread(() -> { // 子线程执行耗时操作 final String result = doHeavyWork(); // 通过Handler将结果发送到主线程 mainHandler.post(() -> { textView.setText(result); // 更新UI }); }).start(); ``` ### 3. 优化数据库操作 - 避免在主线程执行数据库操作(引用[3]): - 使用`Room`持久化库配合协程或`RxJava` - 手动使用子线程: ```java ExecutorService executor = Executors.newSingleThreadExecutor(); executor.execute(() -> { database.userDao().insertAll(new User("John")); mainHandler.post(() -> toast("Saved!")); // 通知UI }); ``` ### 4. 避免过度频繁的UI更新 - 问题场景:循环中频繁触发UI刷新(如引用[4]每100ms刷新一次) - 优化方案: - 使用`View.postDelayed`控制刷新频率,避免堆积消息 - 合并多次更新为单次刷新(如使用`handler.removeCallbacks`移除未执行的任务) ### 5. 使用性能分析工具定位卡顿点 - **Systrace**:检测UI线程阻塞原因 ```bash python systrace.py -t 10 sched gfx view wm am app input ``` - 查找`Choreographer#doFrame`耗时超过16ms的帧 - **Android Profiler**: - 监控CPU使用率,定位耗时方法 - 检查主线程状态(运行/阻塞) ### 6. 其他关键优化 - **减少布局复杂度**:使用`ConstraintLayout`减少嵌套层级 - **视图复用**:`RecyclerView`替代`ListView` - **内存泄漏预防**:避免非静态内部类持有Activity引用 ### 示例:网络请求子线程处理(使用Retrofit + RxJava) ```java // Retrofit接口定义 interface ApiService { @GET("data") Observable<Data> fetchData(); } // 在Activity中 apiService.fetchData() .subscribeOn(Schedulers.io()) // 在IO线程执行请求 .observeOn(AndroidSchedulers.mainThread()) // 回到主线程更新UI .subscribe(data -> { updateUI(data); // 更新UI操作 }, error -> { handleError(error); }); ``` > **注意**:Android 4.0+强制要求网络请求必须在子线程执行,否则会触发`NetworkOnMainThreadException`[^1]。 ### 验证效果 - 使用`adb shell`监控ANR日志: ```bash adb shell cat /data/anr/traces.txt ``` - 检查卡顿是否消失,以及UI响应速度是否提升。 §§相关问题§§ 1. 如何在Android中使用协程处理耗时任务? 2. Systrace工具如何分析UI线程卡顿? 3. 如何避免Android中的内存泄漏导致UI卡顿? 4. RecyclerView优化UI性能的方法有哪些? 5. 主线程Handler机制的工作原理是什么? </think> ### 解决Android UI线程卡住问题的方法 #### 1. **识别耗时操作** - 检查UI线程中的以下操作: ```java // 典型耗时操作示例 button.setOnClickListener(v -> { // 避免在主线程执行这些操作: Thread.sleep(5000); // 阻塞线程 loadDataFromDatabase(); // 数据库操作[^3] downloadFileFromInternet(); // 网络请求[^5] processHeavyCalculation(); // 复杂计算[^2] }); ``` - 使用**Android Profiler**定位耗时方法: - CPU分析器检测方法执行时间 - 跟踪`Choreographer#doFrame`中的帧延迟 #### 2. **使用异步处理机制** - **Handler + Thread**(基础方案): ```java // 创建后台线程处理耗时任务 new Thread(() -> { Object result = doHeavyWork(); // 后台执行 // 通过Handler通知主线程更新UI new Handler(Looper.getMainLooper()).post(() -> { updateUI(result); // 主线程更新UI[^4] }); }).start(); ``` - 注意:避免在循环中频繁`post()`导致消息队列堆积[^4] - **Kotlin协程**(推荐方案): ```kotlin // 在ViewModel或LifecycleScope中启动 viewModelScope.launch { val data = withContext(Dispatchers.IO) { // 切换到IO线程 fetchDataFromNetwork() } updateUI(data) // 自动切回主线程 } ``` #### 3. **优化数据库操作** - 使用`Room`库的异步查询: ```java // 自动在后台线程执行查询 Completable.fromAction(() -> database.userDao().insert(user)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(); ``` - 避免在主线程执行`SQLite`操作(ANR阈值=5秒) #### 4. **控制UI刷新频率** - 针对高频刷新场景(如实时绘制): ```java private final Runnable updateTask = new Runnable() { @Override public void run() { updateCanvas(); // UI更新逻辑 // 控制刷新间隔≥16ms(60fps) handler.postDelayed(this, 16); } }; // 启动刷新循环 handler.post(updateTask); // 停止时移除回调 handler.removeCallbacks(updateTask);[^4] ``` #### 5. **使用现代化架构组件** ```mermaid graph LR A[UI层] -->|观察| B[ViewModel] B -->|调用| C[Repository] C -->|异步获取| D[数据库/网络] D -->|LiveData返回| B B -->|更新| A ``` - **LiveData**:自动管理线程切换 - **WorkManager**:后台任务调度 - **RxJava**:复杂的异步流处理 #### 6. **关键性能优化点** - **布局优化**: - 使用`ConstraintLayout`减少嵌套层级 - 避免`overscroll`等昂贵渲染效果 - **内存管理**: - 使用`LeakCanary`检测内存泄漏 - 避免在`onDraw()`中创建对象 - **IPC调用优化**: - 减少跨进程通信频率 - 使用`Bundle`代替多次调用[^1] > **验证方法**: > 在开发者选项中开启"显示界面更新"和"GPU呈现模式分析",红色帧率条标识卡顿位置[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值