什么是 ANR,如何避免?

发布于 2025-01-05 23:57:27 字数 6400 浏览 3 评论 0

以下是关于 ANR(Application Not Responding)的介绍以及避免方法:

ANR 的定义及产生原因

  • 定义
    • 在 Android 系统中,当应用程序的主线程在规定时间内没有响应特定的事件(如用户输入事件等)时,系统就会弹出 ANR 对话框,提示用户该应用程序无响应,这是一种系统对于应用程序响应性能的监控和保护机制。
  • 产生原因
    • 输入事件 5 秒内无响应
      • 例如,用户点击了屏幕上的按钮,但主线程由于某种原因(如正在执行一个耗时的计算或网络操作等)在 5 秒内未能处理该点击事件并作出响应,就会触发 ANR。
    • 广播接收器在 10 秒内未执行完毕
      • 当应用中的广播接收器接收到广播消息后,需要在 10 秒内完成相关的处理操作,如果超过这个时间限制,就会导致 ANR。例如,在广播接收器中进行复杂的数据库操作或者大量的文件读写等耗时操作,容易引发此问题。
    • 服务无法在 20 秒内执行完相应的方法
      • 对于像 Service 中的 onCreate()onStartCommand()onBind() 等重要方法,如果在 20 秒内不能执行完成,同样会引发 ANR。例如,在 onStartCommand() 方法中启动一个需要长时间初始化的任务且没有采用异步方式,就可能出现这种情况。

避免 ANR 的方法

  • 合理使用线程
    • 耗时操作异步化
      • 对于如网络请求、文件读写、复杂计算等耗时操作,应使用单独的线程(如通过 Thread 类或者 AsyncTask 等方式)来执行,避免在主线程中直接进行。例如,在点击按钮触发网络请求获取数据时,创建一个新线程来执行网络请求任务,使主线程可以继续响应用户的其他操作。
      • 以下是使用 AsyncTask 进行网络请求的简单示例:
public class MyAsyncTask extends AsyncTask<Void, Void, String> {

    @Override
    protected String doInBackground(Void... voids) {
        // 在这里执行网络请求等耗时操作
        try {
            URL url = new URL("http://example.com/api/data");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            // 处理网络连接和数据读取等操作
            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            StringBuilder result = new StringBuilder();
            String line;
            while ((line = reader.readLine())!= null) {
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    protected void onPostExecute(String result) {
        // 在主线程中处理获取到的数据并更新 UI 等
        super.onPostExecute(result);
        if (result!= null) {
            TextView textView = findViewById(R.id.my_text_view);
            textView.setText(result);
        }
    }
}

// 在主线程中启动 AsyncTask
new MyAsyncTask().execute();
  • 使用线程池
    • 对于大量的异步任务,可以考虑使用线程池来管理线程,以提高线程的创建和执行效率,并避免无限制地创建线程导致系统资源耗尽。例如,可以使用 ThreadPoolExecutor 或者 Executors 工厂类来创建线程池并执行任务。
// 创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    final int taskId = i;
    executorService.execute(new Runnable() {
        @Override
        public void run() {
            // 在这里执行具体的任务,如耗时计算等
            try {
                Thread.sleep(2000);
                Log.d("ThreadTask", "Task " + taskId + " completed");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
executorService.shutdown();
  • 优化广播接收器和服务中的代码
    • 广播接收器
      • 在广播接收器中,应尽量减少耗时操作,对于必须的耗时任务,可通过启动服务等方式在后台处理。例如,接收到开机广播后,如果需要进行大量的数据初始化工作,可以启动一个 IntentService 来在后台完成这些操作。
public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
            // 启动服务在后台进行耗时的数据初始化等操作
            Intent serviceIntent = new Intent(context, MyInitializationService.class);
            context.startService(serviceIntent);
        }
    }
}
  • 服务
    • Service 的关键方法(如 onCreate()onStartCommand() 等)中,应尽快启动其他线程或使用异步方式来执行耗时任务,确保这些方法能在规定时间内返回。例如,在 onStartCommand() 方法中使用 HandlerThread 来处理后台任务。
public class MyService extends Service {

    private HandlerThread handlerThread;
    private Handler handler;

    @Override
    public void onCreate() {
        super.onCreate();
        handlerThread = new HandlerThread("MyServiceThread");
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 通过 Handler 发送消息在后台线程处理任务
        handler.post(new Runnable() {
            @Override
            public void run() {
                // 执行耗时的服务任务,如长时间的文件操作等
                try {
                    Thread.sleep(5000);
                    Log.d("MyService", "Service task completed");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        handlerThread.quitSafely();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
  • 优化布局和 UI 操作
    • 简化布局结构
      • 避免在布局文件中使用过于复杂的视图嵌套结构,尽量采用扁平化的布局设计,以提高布局的加载和渲染速度。例如,使用 ConstraintLayout 等相对灵活且高效的布局容器来替代多层嵌套的 LinearLayout 布局。
    • 避免在 UI 线程进行繁重的绘制操作
      • 不要在主线程中进行大量的图形绘制或者频繁地更新复杂的 UI 组件,对于需要频繁更新的 UI 元素,可以采用 SurfaceView 或者 TextureView 等方式在单独的线程中进行绘制。
    • 懒加载和缓存数据
      • 对于一些需要加载的数据(如图片、列表数据等),可以采用懒加载的方式,在需要显示时再进行加载,并合理利用缓存机制(如内存缓存、磁盘缓存等),以提高数据获取和显示的速度,减少主线程的等待时间。例如,使用 LruCache 实现内存缓存图片,使用 DiskLruCache 实现磁盘缓存。

监控和分析工具的使用

  • 使用 Android Profiler 等工具
    • Android Profiler(在 Android Studio 中)可以对应用程序的 CPU、内存、网络等方面进行性能分析,通过它可以发现主线程中可能存在的耗时操作等导致 ANR 风险的问题点。例如,在分析过程中发现某个方法在主线程中执行时间过长,就可以针对性地进行优化。
  • ANR 日志分析
    • 当应用发生 ANR 时,系统会在 /data/anr/traces.txt 文件中记录相关的 ANR 信息,包括发生 ANR 的时间、进程信息、线程堆栈信息等,通过分析这些日志可以准确找出导致 ANR 的原因和具体代码位置,从而进行相应的修复。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

家住魔仙堡

暂无简介

文章
评论
25 人气
更多

推荐作者

微信用户

文章 0 评论 0

小情绪

文章 0 评论 0

ゞ记忆︶ㄣ

文章 0 评论 0

笨死的猪

文章 0 评论 0

彭明超

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文