找回密码
 立即注册
首页 业界区 安全 Android 贯彻开发过程 之 对象生命周期

Android 贯彻开发过程 之 对象生命周期

匝抽 昨天 16:46
Android 贯彻开发过程 之 对象生命周期

我们在开发过程当中是否会关心对象的生命周期?开发者创建对象是否是随意创建?如何更好的管理对象?对象到底应该应该迎合哪个组件的生命周期?一个对象占用内存大约是多少?接下来请听本码农细心分解。
生命周期之Hello World

介绍两个命令:
  1. # 查看当前activity
  2. adb shell dumpsys activity activities | grep -E 'mResumedActivity|mCurrentFocus'
  3. # 查看栈内的activity
  4. adb shell dumpsys activity activities | grep -E "Running activities|Hist"
复制代码
执行下第一个命令
  1. $ dumpsys activity activities | grep -E 'mResumedActivity|mCurrentFocus'
  2.         mResumedActivity: ActivityRecord{a588abe u0 io.github.customview/.activity.HomeActivity t599}
复制代码
堆转储并拉到pc上,拖入Android Studio Memory Profiler打开
  1. adb shell am dumpheap io.github.customview /data/local/tmp/heapdump.hprof
  2. adb pull /data/local/tmp/heapdump.hprof .
复制代码
1.png

可以看到总共有758个类对象,19014个类实例对象。

  • Native Size代表这些对象在native堆占用的字节总和。
  • Shallow(浅的) Size(浅大小)表示对象本身在Java堆上占用的字节数,不包含它引用到的其他对象
  • Retained Size: 代表该对象被回收,可以释放的内存空间,即包含它引用的其他对象。例如A引用B,C对象,则 A的 Retained Size 等于A+B+C的Shallow Size之和。
  • Allocations: 表示在你做 Heap Dump 这一刻,当前还活着(未被 GC 回收)的该类对象的实例个数。
在内存优化的时候比较关心的是Retained Size,将他按照从大到小到小的顺序排列,依次排除。
现在进入主题:
回忆一下Activity生命周期
2.png

这便是入门Android的第一课,Activity的标准的生命周期流程,但是这并不只是入门那么简单,这个生命周期图应当贯彻Android开发的始终,使得对于生命周期拥有更加深刻的认识,包括Service, 协程,Compose副函数等等,每一个对象都有其生命周期,必须让每个对象的生命周期符合其所在的生命周期组件,不能超越生命周期的组件,否则会浪费内存,严重点内存泄漏、甚至OOM。对于Android移动应用来说内存是非常珍贵的,因为Android系统对于每个应用都是有内存限制的。并且在内存紧张的情况下JVM会进行非常频繁的GC,众所周知GC 的过程中会触发 Stop-The-World (STW) ,在 GC 的某些阶段会暂停所有 Java 线程,导致 UI 渲染和事件处理停顿,从而给用户感觉到“卡顿”,给用户带来非常糟糕的体验,有些用户吐槽android手机为什么越用越卡,这些可能就是原因之一。内存限制一般如下:

  • 低端机 / 早期设备:16 MB / 32 MB
  • 普通手机:128 MB / 256 MB
  • 高端机:512 MB 甚至更高
    一个啥也不干的类将会占用8字节。
    查看一个应用的内存限制
  1. val activityManager: ActivityManager = getSystemService(ActivityManager::class.java)
  2. Log.i(TAG, "initView -> memory: ${activityManager.memoryClass}MB")
  3. // or
  4. val maxMemory: Long = Runtime.getRuntime().maxMemory() / 1024 / 1024 // 最大可用内存
复制代码
我目前的这台机器的限制是258M,已经使用28M,当然仅仅是一个最简单的Hello World项目。
可以使用如下命令
  1. $ adb shell dumpsys meminfo  io.github.customview
  2. Applications Memory Usage (in Kilobytes):
  3. Uptime: 1805197131 Realtime: 1965534265
  4. ** MEMINFO in pid 1239 [io.github.customview] **
  5.                    Pss  Private  Private  SwapPss     Heap     Heap     Heap
  6.                  Total    Dirty    Clean    Dirty     Size    Alloc     Free
  7.                 ------   ------   ------   ------   ------   ------   ------
  8.   Native Heap     6933     6868       16       58    16384     9593     6790
  9.   Dalvik Heap     1251      928      252       34     3034     1514     1520
  10. Dalvik Other      888      888        0        0                           
  11.         Stack       92       92        0        0                           
  12.        Ashmem        2        0        0        0                           
  13.     Other dev       14        0        8        0                           
  14.      .so mmap     2661      128      188      145                           
  15.     .apk mmap      756        0      120        0                           
  16.     .ttf mmap       66        0        0        0                           
  17.     .dex mmap    10451        8     7728        0                           
  18.     .oat mmap      382        0        0        0                           
  19.     .art mmap     4434     3904      112       14                           
  20.    Other mmap       13        4        0        0                           
  21.       Unknown      830      728       92        4                           
  22.         TOTAL    29028    13548     8516      255    19418    11107     8310
  23. App Summary
  24.                        Pss(KB)
  25.                         ------
  26.            Java Heap:     4944
  27.          Native Heap:     6868
  28.                 Code:     8172
  29.                Stack:       92
  30.             Graphics:        0
  31.        Private Other:     1988
  32.               System:     6964
  33.                TOTAL:    29028       TOTAL SWAP PSS:      255
  34. Objects
  35.                Views:       11         ViewRootImpl:        1
  36.          AppContexts:        3           Activities:        1
  37.               Assets:        4        AssetManagers:        3
  38.        Local Binders:        9        Proxy Binders:       16
  39.        Parcel memory:        3         Parcel count:       14
  40.     Death Recipients:        0      OpenSSL Sockets:        0
  41.             WebViews:        0
  42. SQL
  43.          MEMORY_USED:        0
  44.   PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0
复制代码
可以看到App Summary的TOTAL的值为28M.
综上移动端的资源是非常珍贵的,稍不注意就会OOM。当然可以在发生oom的时候进行亡羊补牢、为时不晚。但是为什么不一开始就进行预防呢?
单例 or 非单例

对于移动应用来说,一个对象是否需要单例需要谨慎考虑,我们先创建一个单例类来简单看下。
创建单例类之前
  1. val dumpFile = File(
  2.         getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
  3.         "home_activity.hprof"
  4. )
  5. Debug.dumpHprofData(dumpFile.absolutePath)
复制代码
将文件导入Android Studio
  1. adb pull /storage/emulated/0/Android/data/io.github.customview/files/Download/home_activity.hprof .
复制代码
截图如下:
3.png

可以看到这个HomeActivity实例被销毁可以释放大约7.3kb的内存.
再来关注下Kotlin的伴生对象
  1. internal class HomeActivity internal constructor(): AppCompatActivity() {
  2.     internal companion object {
  3.         internal fun startActivity(context: Context){
  4.             context.startActivity(Intent(context, HomeActivity::class.java))
  5.         }
  6.     }
  7.         //...
  8. }
复制代码
它类对象占用232字节内存,类实例仅占8字节
我们删除它之后,其就不占用内存了。
kotlin 伴生类加载时机:
只要当前类被引用那么其就会初始化伴生类,或者很简单,使用如下方式即可初始化伴生类。
  1. import android.util.Log
  2. private const val TAG: String = "Utils"
  3. internal class Utils internal constructor(){
  4.     internal companion object {
  5.         init {
  6.             Log.i(TAG, "Utils companion object load...")
  7.         }
  8.     }
  9.     internal fun hello(){
  10.         Log.i(TAG, "Utils hello...")
  11.     }
  12. }
复制代码
在HomeActivity中
  1. internal companion object {
  2.         init {
  3.                 Log.i(TAG, "HomeActivity companion object load... ")
  4.                 Utils // 初始化伴生对象
  5.         }
  6.         @JvmStatic
  7.         internal fun startActivity(context: Context){
  8.                 context.startActivity(Intent(context, HomeActivity::class.java))
  9.         }
  10. }
复制代码
干掉当前activity去另一个Activity会发生什么,我们对比一个两个堆转储的区别
HomeActivity
  1. UserActivity.startActivity(context = this)
  2. finish()
  3. System.gc() // 手动触发
复制代码
4.png

UserActivity
  1. private fun initView(){
  2.         binding.button.setOnClickListener {
  3.                 //  /storage/emulated/0/Android/data/io.github.customview/files/Download
  4.                 val dumpFile = File(
  5.                         getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
  6.                         "user_activity.hprof"
  7.                 )
  8.                 Debug.dumpHprofData(dumpFile.absolutePath)
  9.         }
  10.         binding.gc.setOnClickListener {
  11.                 System.gc()
  12.         }
  13. }
复制代码
手动多GC几次
5.png

可以看到伴生类无法被垃圾所回收,他的生命周期伴随整个APP,它会延长其他对象的生命周期,比如Acitivity、Service.
经过上面分析,在伴生类里使用context会有如下警告
  1. internal companion object {
  2.         private var context1: Context? = null
  3. }
  4. // Do not place Android context classes in static fields; this is a memory leak
复制代码
有些开发者忽略警告就是干!!!
建议

  • 禁止伴生类引用Acitivity、Service
  • 在伴生类当中仅使用常量、方法,尽量避免引用实例对象。
  • 尽量少用单例对象,因为它的生命周期是自引用起到整个app被关闭
context 使用注意事项
  1. Log.i(TAG, "initView -> this: $this baseContext: $baseContext, application: ${application}, applicationContext: $applicationContext")
复制代码

  • this:是组件本身,使用context的对象的生命周期即组件本身
  • baseContext:和this一致
  • applicationContext: 即Application,使用applicationContext对象的生命周期是app生命周期。
    创建依赖context的对象的生命周期取决于传递的context实例的生命周期。
    有些开发者在Application在中会创建一个静态的context变量在Application在onCreate的时候将Application实例赋值给context,这样做会有很多问题:
  • Android Studio 内存泄漏警告,强迫症患者难受,但是老项目当前相当多地方在使用,你也不好修改。
  • 增加内存泄漏的风险,这样的做法使得开发者肆无忌惮的使用静态的context创建对象,在没有生命周期思维的开发者身上,会造成滥用,使得很多对象的生命周期被放大到app生命周期,严重的会造成内存泄漏,甚至OOM
结尾

在Android 移动 应用开发当中,应当提高生命周期思维,仔细分析自己手动创建的对象存活的生命周期,将生命周期的思维贯彻app开发的整个过程,而不仅仅是activity、Service这些组件当中。感谢大家,祝大家升职加薪。
有生命不足,还请大家批评指正,我会立即修改!感谢大家!

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册