Android高级面试题之SDK源码分析:通过线程提升性能(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

  • 线程和界面对象引用

  • 显示引用

  • 隐式引用

  • 线程和应用Activity生命周期

  • 保留线程

  • 线程优先级

  • 线程处理的辅助类

  • AsyncTask 类

  • HandlerThread 类

  • ThreadPoolExecutor 类

善于在 Android 上利用线程可以帮助您提升应用的性能。本页从以下几个方面讨论线程的使用:使用界面线程(即主线程);应用生命周期与线程优先级之间的关系;以及平台为帮助管理线程复杂性所提供的方法。对于每个方面,本文都介绍了潜在的陷阱和相应的规避策略。

主线程


当用户启动您的应用时,Android 会创建新的 Linux 进程以及执行线程。这个主线程也称为界面线程,负责屏幕上发生的一切活动。了解其工作原理有助于您通过设计让应用利用主线程实现最佳性能。

内部原理

主线程的设计非常简单:它的唯一工作就是从线程安全工作队列获取工作块并执行,直到应用被终止。框架会从多个位置生成部分工作块。这些位置包括与生命周期信息、用户事件(例如输入)或来自其他应用和进程的事件相关的回调。此外,应用也可以不使用框架而自行对块进行明确排队。

应用执行的任何代码块几乎都与事件回调(例如输入、布局扩充或绘制)相关联。当某个操作触发事件时,发生了事件的线程会将事件从线程本身里推送到主线程的消息队列中。然后,主线程可以为事件提供服务。

当有动画或屏幕更新正在进行时,系统会每隔 16ms 左右尝试执行一个工作块(负责绘制屏幕),从而以每秒 60 帧的流畅速度进行渲染。要使系统达到此目标,界面/视图层次结构必须在主线程上更新。但是,如果主线程的消息队列中的任务太多或太长,导致主线程无法足够快地完成更新,那么应用应将此工作移至工作线程。如果主线程无法在 16ms 内执行完工作块,则用户可能会察觉到卡顿、延迟或界面对输入无响应。 如果主线程阻塞大约 5 秒,系统会显示“应用无响应”(ANR) 对话框,允许用户直接关闭应用。

将大量或冗长的任务从主线程中移出,使其不影响流畅渲染和快速响应用户输入,这是您在应用中采用线程处理的最大原因。

线程和界面对象引用


根据设计,Android 视图对象不是线程安全的。无论是创建、使用还是销毁界面对象,应用都应在主线程上完成。如果您尝试在主线程以外的其他线程中修改甚至引用界面对象,则可能导致异常、无提示故障、崩溃以及其他未定义的异常行为。

引用方面的问题分为两类:显式引用和隐式引用。

显式引用

非主线程上的许多任务的最终目标是更新界面对象。但是,如果其中一个线程访问视图层次结构中的某个对象,则可能导致应用不稳定:如果工作线程更改该对象的属性,与此同时有任何其他线程正在引用该对象,则结果无法确定。

例如,假设某个应用在工作线程上直接引用了界面对象。工作线程上的该对象可能包含对 View 的引用;但在工作完成之前,View 已从视图层次结构中移除。当这两个操作同时发生时,该引用会将 View 对象保留在内存中,并对其设置属性。 但是,用户从不会看到此对象,而且应用会在对象引用消失后删除该对象。

再举一个例子,假设 View 对象包含对其所属的 Activity 的引用。如果该 Activity 被销毁,但仍有直接或间接引用它的线程处理工作块,则垃圾回收器会等到该工作块执行完毕再收集该 Activity。

如果在线程处理工作的执行过程中发生某个 Activity 生命周期事件(例如屏幕旋转),这种情况可能会导致问题。在执行中的工作完成之前,系统将无法执行垃圾回收。因此,等到可以进行垃圾回收时,内存中可能有两个 Activity 对象。

在这类情况下,我们建议您不要在应用的线程处理工作任务中包含对界面对象的显式引用。避免此类引用有助于防止这些类型的内存泄漏,同时避开线程处理争用。

在任何情况下,应用都只应在主线程上更新界面对象。这意味着您应制定允许多个线程将工作传回主线程的协商政策,让最顶层的 Activity 或 Fragment 负责更新实际界面对象。

隐式引用

以下代码段演示了线程处理对象的常见代码设计缺陷:

public class MainActivity extends Activity {

// …

public class MyAsyncTask extends AsyncTask<Void, Void, String> {

@Override protected String doInBackground(Void… params) {…}

@Override protected void onPostExecute(String result) {…}

}

}

此代码段的缺陷在于,代码会将线程处理对象 MyAsyncTask 声明为某个 Activity 的非静态内部类(或 Kotlin 中的内部类)。此声明会创建对封装 Activity 实例的隐式引用。因此,在线程处理工作完成之前,该对象一直包含对相应 Activity 的引用,导致所引用 Activity 的销毁出现延迟。 这种延迟进而会给内存带来更多压力。

此问题的直接解决方法是将过载的类实例定义为静态类,或在其自己的文件中定义,从而移除隐式引用。

另一个解决方法是将 AsyncTask 对象声明为静态嵌套类(或在 Kotlin 中移除内部限定符)。这样做可以消除隐式引用问题,因为静态嵌套类与内部类有所不同:内部类的实例要求对外部类的实例进行实例化,并且可直接访问封装实例的方法和字段。相反,静态嵌套类不需要引用封装类的实例,因此它不包含对外部类成员的引用。

public class MainActivity extends Activity {

// …

static public class MyAsyncTask extends AsyncTask<Void, Void, String> {

@Override protected String doInBackground(Void… params) {…}

@Override protected void onPostExecute(String result) {…}

}

}

线程和应用 Activity 生命周期


应用生命周期会影响线程处理在应用中的工作方式。您可能需要确定线程在 Activity 销毁后应不应该保留。您还应注意线程优先级与 Activity 是在前台运行还是在后台运行之间的关系。

保留线程

线程会在生成这些线程的 Activity 的生命周期过后继续保留。无论是否发生 Activity 创建或销毁事件,线程都会继续不间断地执行。在某些情况下,这种持久性是可取的。

假设某个 Activity 生成了一组线程处理工作块,然后在工作线程能执行工作块之前被销毁。应用应如何处理正在执行的工作块?

如果工作块将要更新不再存在的界面,则该工作不必再继续。例如,如果该工作是从数据库加载用户信息,然后更新视图,则不再需要该线程。

相比之下,工作数据包可能具有某种不完全与界面相关的优势。在这种情况下,您应该保留该线程。例如,数据包可能正在等待下载图片,将其缓存到磁盘并更新关联的 View 对象。虽然该对象已不存在,但是下载和缓存该图片可能仍然有用,以防用户返回到已销毁的 Activity。

为所有线程处理对象手动管理生命周期响应可能极其复杂。如果管理不当,应用可能会遇到内存争用和性能问题。您可以结合使用 ViewModel 和 LiveData加载数据并在数据发生更改时收到通知,而不用关心生命周期。ViewModel 对象是此问题的一种解决方案。ViewModel 会在配置更改后保持不变,便于您保留视图数据。要详细了解 ViewModel 和 LiveData,请分别参阅 ViewModel 指南和 LiveData 指南。如果您还想详细了解应用架构,请参阅应用架构指南

线程优先级

应用线程的优先级一定程度上取决于应用处于生命周期的哪个阶段。在应用中创建和管理线程时,请务必设置线程的优先级,以便正确的线程适时获得正确的优先级。如果设置得过高,您的线程可能会干扰界面线程和 RenderThread,导致应用掉帧。如果设置得过低,可能会导致异步任务(例如图片加载)达不到所需的速度。

每次创建线程时,都应调用 setThreadPriority()。系统的线程调度程序会优先考虑优先级较高的线程,在这些优先级与最终将所有工作都完成的需求之间做出权衡。一般来说,前台组约占设备总执行时间的 95%,而后台组约占 5%。

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-ffaJh3Ph-1713466009804)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 28
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值