热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android线程的正确使用姿势

进程优先级(ProcessPriority)线程寄宿在进程当中,线程的生命周期直接被进程所影响,而进程的存活又和其优先级直接

进程优先级(Process Priority)

线程寄宿在进程当中,线程的生命周期直接被进程所影响,而进程的存活又和其优先级直接相关。在处理进程优先级的时候,大部分人靠直觉都能知道前台进程(Foreground Process)优先级要高于后台进程(Background Process)。但这种粗糙的划分无法满足操作系统高精度调度的需求。无论Android还是iOS,系统对于Foreground,Background进程有进一步的细化。

Foreground Process

Foreground一般意味着用户双眼可见,可见却不一定是active。在Android的世界里,一个Activity处于前台之时,如果能采集用户的input事件,就可以判定为active,如果中途弹出一个Dialog,Dialog变成新的active实体,直接面对用户的操作。被部分遮挡的activity尽管依然可见,但状态却变为inactive。不能正确的区分visible和active是很多初级程序员会犯的错误。

 

Background Process

后台进程同样有更细的划分。所谓的Background可以理解为不可见(invisible)。对于不可见的任务,Android也有重要性的区分。重要的后台任务定义为Service,如果一个进程包含Service(称为Service Process),那么在“重要性”上就会被系统区别对待,其优先级自然会高于不包含Service的进程(称为Background Process),最后还剩一类空进程(Empty Process)。Empty Process初看有些费解,一个Process如果什么都不做,还有什么存在的必要。其实Empty Process并不Empty,还存在不少的内存占用。

在iOS的世界里,Memory被分为Clean Memory和Dirty Memory,Clean Memory是App启动被加载到内存之后原始占用的那一部分内存,一般包括初始的stack, heap, text, data等segment,Dirty Memory是由于用户操作所改变的那部分内存,也就是App的状态值。系统在出现Low Memory Warning的时候会首先清掉Dirty Memory,对于用户来说,操作的进度就全部丢失了,即使再次点击App图标,也是一切从头开始。但由于Clean Memory没有被清除,避免了从磁盘重新读取app数据的io损耗,启动会变快。这也是为什么很多人会感觉手机重启后,app打开的速度都比较慢。

同理Android世界当中的Empty Process还保存有App相关的Clean Memory,这部分Memory对于提升App的启动速度大有帮助。显而易见Empty Process的优先级是最低的。

综上所述,我们可以把Android世界的Process按优先级分为如下几类:

进程的优先级从高到低依次分为五类,越往下,在内存紧张的时候越有可能被系统杀掉。简而言之,越是容易被用户感知到的进程,其优先级必定更高。

 

线程调度(Thread Scheduling)

Android系统基于精简过后的linux内核,其线程的调度受时间片轮转和优先级控制等诸多因素影响。不少初学者会认为某个线程分配到的time slice多少是按照其优先级与其它线程优先级对比所决定的,这并不完全正确。

Linux系统的调度器在分配time slice的时候,采用的CFS(completely fair scheduler)策略。这种策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量,如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。显然使用这种调度策略的话,优先级高的线程并不一定能在争取time slice上有绝对的优势,所以Android系统在线程调度上使用了cgroups的概念,cgroups能更好的凸显某些线程的重要性,使得优先级更高的线程明确的获取到更多的time slice。

Android将线程分为多个group,其中两类group尤其重要。一类是default group,UI线程属于这一类。另一类是background group,工作线程应该归属到这一类。background group当中所有的线程加起来总共也只能分配到5~10%的time slice,剩下的全部分配给default group,这样设计显然能保证UI线程绘制UI的流畅性。

有不少人吐槽Android系统之所以不如iOS流畅,是因为UI线程的优先级和普通工作线程一致导致的。这其实是个误会,Android的设计者实际上提供了background group的概念来降低工作线程的CPU资源消耗,只不过与iOS不同的是,Android开发者需要显式的将工作线程归于background group。

所以在我们决定新启一个线程执行任务的时候,首先要问自己这个任务在完成时间上是否重要到要和UI线程争夺CPU资源。如果不是,降低线程优先级将其归于background group,如果是,则需要进一步的profile看这个线程是否造成UI线程的卡顿。

虽说Android系统在任务调度上是以线程为基础单位,设置单个thread的优先级也可以改变其所属的control groups,从而影响CPU time slice的分配。但进程的属性变化也会影响到线程的调度,当一个App进入后台的时候,该App所属的整个进程都将进入background group,以确保处于foreground,用户可见的新进程能获取到尽可能多的CPU资源。用adb可以查看不同进程的当前调度策略。

当你的App重新被用户切换到前台的时候,进程当中所属的线程又会回归的原来的group。在这些用户频繁切换的过程当中,thread的优先级并不会发生变化,但系统在time slice的分配上却在不停的调整。

是否真的需要新线程?

开线程并不是提升App性能,解决UI卡顿的万金油。每一个新启的线程会消耗至少64KB的内存,系统在不同的线程之间switch context也会带来额外的开销。如果随意开启新线程,随着业务的膨胀,很容易在App运行的某个时间点发现几十个线程同时在运行。后果是原本想解决UI流畅性,却反而导致了偶现的不可控的卡顿。

移动端App新启线程一般都是为了保证UI的流畅性,增加App用户操作的响应度。但是否需要将任务放入工作线程需要先了解任务的瓶颈在哪,是i/o,gpu还是cpu?UI出现卡顿并不一定是UI线程出现了费时的计算,有可能是其它原因,比如layout层级太深。

尽量重用已有的工作线程(使用线程池)可以避免出现大量同时活跃的线程,比如对HTTP请求设置最大并发数。或者将任务放入某个串行的队列(HandlerThread)按顺序执行,工作线程任务队列适合处理大量耗时较短的任务,避免出现单个任务阻塞整个队列的情况。

用什么姿势开线程?

new Thread()

这是Android系统里开线程最简单的方式,也只能应用于最简单的场景,简单的好处却伴随不少的隐患。

1
2
3
4
5
6
new Thread(new Runnable() {
            @Override
            public void run() {
            }
        }).start();

这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消。

Runnable作为匿名内部类还持有了外部类的引用,在线程退出之前,该引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。

没有线程切换的接口,要传递处理结果到UI线程的话,需要写额外的线程切换代码。

如果从UI线程启动,则该线程优先级默认为Default,归于default cgroup,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得


转:https://www.cnblogs.com/wutianlong/p/5455334.html



推荐阅读
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 在分析Android的Audio系统时,我们对mpAudioPolicy->get_input进行了详细探讨,发现其背后涉及的机制相当复杂。本文将详细介绍这一过程及其背后的实现细节。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 开机自启动的几种方式
    0x01快速自启动目录快速启动目录自启动方式源于Windows中的一个目录,这个目录一般叫启动或者Startup。位于该目录下的PE文件会在开机后进行自启动 ... [详细]
  • Flowable 流程图路径与节点展示:已执行节点高亮红色标记,增强可视化效果
    在Flowable流程图中,通常仅显示当前节点,而路径则需自行获取。特别是在多次驳回的情况下,节点可能会出现混乱。本文重点探讨了如何准确地展示流程图效果,包括已结束的流程和正在执行的流程。具体实现方法包括生成带有高亮红色标记的图片,以增强可视化效果,确保用户能够清晰地了解每个节点的状态。 ... [详细]
  • 为了确保iOS应用能够安全地访问网站数据,本文介绍了如何在Nginx服务器上轻松配置CertBot以实现SSL证书的自动化管理。通过这一过程,可以确保应用始终使用HTTPS协议,从而提升数据传输的安全性和可靠性。文章详细阐述了配置步骤和常见问题的解决方法,帮助读者快速上手并成功部署SSL证书。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 尽管我们尽最大努力,任何软件开发过程中都难免会出现缺陷。为了更有效地提升对支持部门的协助与支撑,本文探讨了多种策略和最佳实践,旨在通过改进沟通、增强培训和支持流程来减少这些缺陷的影响,并提高整体服务质量和客户满意度。 ... [详细]
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • php更新数据库字段的函数是,php更新数据库字段的函数是 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
author-avatar
卢嘉怡i
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有