例如,假设某个应用在工作线程上直接引用了界面对象。工作线程上的该对象可能包含对 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%。
系统还会使用 Process
类为每个线程分配系统自己的优先级值。
默认情况下,系统会将线程的优先级设置为与生成它的线程具有相同的优先级和组成员资格。但是,您的应用可以使用 setThreadPriority()
明确调整线程优先级。
Process
类提供了一组可供您的应用设置线程优先级的常量,以帮助简化优先级值的分配。例如,THREAD_PRIORITY_DEFAULT
代表线程的默认值。如果线程执行的工作不太紧急,应用应将线程的优先级设为 THREAD_PRIORITY_BACKGROUND
。
应用可以使用 THREAD_PRIORITY_LESS_FAVORABLE
和 THREAD_PRIORITY_MORE_FAVORABLE
常量作为增量器来设置相对优先级。如需线程优先级的列表,请参阅 Process类中的 THREAD_PRIORITY 常量。
如需详细了解如何管理线程,请参阅有关 Thread 和 Process类的参考文档。
线程处理的辅助类
框架提供了相同的 Java 类和基元来方便线程处理,例如 Thread
、Runnable
和 Executors
类。为了帮助减轻与开发适用于 Android 的线程处理应用相关的认知负荷,框架提供了一组可协助开发的辅助程序,例如 AsyncTaskLoader
和 AsyncTask
。每个辅助类都有一组特定的性能细微差别,专用于解决一小部分特定的线程处理问题。将错误的类用在错误的场合可能会导致性能问题。
AsyncTask 类
对于需要快速将工作从主线程移动到工作线程的应用来说,AsyncTask
类是一个简单实用的基元。例如,输入事件可能会触发使用加载的位图更新界面的需求。AsyncTask
对象可以将位图加载和解码分流到备用线程;处理完成后,AsyncTask
对象可以设法回到主线程上接收工作以更新界面。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
总结
现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。
我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。
Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
附
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
的点。
附
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
[外链图片转存中…(img-25VrhtES-1710575206606)]