📌  相关文章
📜  如何解决 Android 应用程序中的 OutOfMemoryError?

📅  最后修改于: 2022-05-13 01:58:43.980000             🧑  作者: Mango

如何解决 Android 应用程序中的 OutOfMemoryError?

OutOfMemoryError,或者简称OOM,是每个Android开发者都遇到过的问题。如果您还没有在您的 Android 应用程序中看到 OOM,那么您将在不久的将来看到。内存泄漏会导致在 Android 中发生 OutOfMemoryError。因此,为了消除 OutOfMemoryError,您需要做的就是处理内存泄漏!在这篇 Geeks for Geeks 文章中,我们将借助一些实际示例来解决 Android 中的 OutOfMemoryError。那么,让我们开始了解 Android 内存泄漏的基础知识。

究竟什么是内存泄漏?

当您在 Android 设备上运行应用程序时,Android 系统会为该应用程序分配内存以使其函数。所有变量创建、函数创建、活动创建等都仅在该内存中进行。例如,如果 Android 系统为您的应用程序分配了 200MB,那么您的应用程序一次只能使用 200MB。如果分配给应用程序的空间随着时间的推移而减少,或者只剩下少量空间,垃圾收集器 (GC) 将释放不再需要的变量和活动所持有的内存。结果,应用程序将再次获得一些空间。

但是,如果您编写的代码包含对不再需要的对象的引用,垃圾收集器将无法释放未使用的空间,并且没有空间可供应用程序继续工作。这称为内存泄漏。由于 Android 中的 Memory Leak 现象,我们在 Android 中遇到 OutOfMemoryError,因为您的代码持有不再需要的对象的引用,并且垃圾收集器无法执行其工作并且您的应用程序使用了分配给它的所有空间由 Android 系统和要求更多。

OutOfMemoryError 的可能原因是什么?

Android 中的 OutOfMemoryError 可能由于多种原因而发生。导致 OutOfMemoryError 的内存泄漏是由多种因素引起的,包括:

  1. 静态视图/上下文/活动的应用
  2. 监听器可以注册和注销。
  3. 非静态的内部类
  4. getContext() 和 getApplicationContext() 的错误使用让我们一一讨论。

1. 静态视图/上下文/活动的利用

如果您使用一些静态视图/上下文/活动,您将收到 OutOfMemoryError(如果您的活动处理大量空间)。这是因为视图、上下文或活动将由应用程序持有,直到应用程序不再活动,因此垃圾收集器不会释放这些使用的内存。例如,假设您的应用程序中有四个活动:A、B、C 和 D。活动“A”是您的主要活动,您可以从中访问活动 B、C 和 D。假设活动 B、C 和 D 各自持有对其上下文的静态引用,每个活动消耗 2MB,Android 系统分配给应用程序的总内存为 4MB。因此,当启动活动“B”时,应用程序将消耗 2MB 的内存。现在返回活动“A”并开始活动“C”。该应用程序现在将使用 4MB 内存(Activity B 为 2MB,Activity C 为 2MB)。返回活动“A”并开始活动“D”。您的应用现在需要 6MB(B 为 2MB,C 为 2MB,D 为 2MB)。让我们看一个实际实现的例子。我在这个例子中使用了位图。如果我使用超过 5 个每个 1MB 的图像,我的应用程序将在我的移动设备上遇到 OutOfMemoryError。在您的情况下,可能导致 OutOfMemoryError 发生的图像数量可能大于或小于 5。您可以通过一次将图像添加到 BitmapArray 来找到限制,并且每当您获得 OOM 时,这就是您的设备的限制。

因此,我们的应用程序有一个 MainActivity,我可以从中访问“ActivityB”和“ActivityC”。这两个活动,“ActivityB”和“ActivityC”,都有一个对其上下文的固定引用。因此,垃圾收集器不会删除这些活动使用的空间。

Kotlin
class GfgAcivityB : AppCompatActivity() {
    companion object {
        lateinit var context: Context
    }
    val gfgBitmap = ArrayList()
      
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gfgActvity2)
        context = this
        Thread(Runnable {
            try {
                // adding 3 images to gfgBitmap
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image3)
                gfgBitmap.add(bitmap1)
                gfgBitmap.add(bitmap2)
                gfgBitmap.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(GfgAcivityB::class.java.name).warning(e.toString())
            }
        }).start()
    }
}


Kotlin
class GfgActivity2 : AppCompatActivity() {
    companion object {
        lateinit var gfgContext: GfgContext
    }
    val bitmapArray = ArrayList()
      
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gfgactivity2)
        gfgContext = this
        Thread(Runnable {
            try {
  
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.gfgImage)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(GfgActivity2::class.java.name).warning(e.toString())
            }
  
        }).start()
    }
}


Kotlin
object gfgListner {
    private val someActivity = ArrayList()
    fun someRegister(myActivity: Activity) {
        someActivity.add(activity)
    }
    fun doUnrgis(activity: Activity) {
        someActivity.remove(activity)
    }
}


Kotlin
override fun justStop() {
    AnotherListner.unregister(this)
    super.onStop()
}


Kotlin
aThirdLib {
  object someCopanion {
    initialize(Here context) {
       this.context = context.getApplicationContext()
    }
  }
}


以下代码用于活动 C

科特林

class GfgActivity2 : AppCompatActivity() {
    companion object {
        lateinit var gfgContext: GfgContext
    }
    val bitmapArray = ArrayList()
      
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gfgactivity2)
        gfgContext = this
        Thread(Runnable {
            try {
  
                val bitmap1: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image1)
                val bitmap2: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image2)
                val bitmap3: Bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.gfgImage)
                bitmapArray.add(bitmap1)
                bitmapArray.add(bitmap2)
                bitmapArray.add(bitmap3)
            } catch (e: Exception){
                Logger.getLogger(GfgActivity2::class.java.name).warning(e.toString())
            }
  
        }).start()
    }
}

因此,如果您从 MainActivity 启动 ActivityB,则不会出现 OutOfMemoryError,因为我的移动设备的限制是 5 张图片,而我在 ActivityB 中只使用了 3 张图片。返回MainActivity并启动ActivityC;您将收到 OutOfMemoryError 因为我们在 ActivityC 中使用 3 张图像,并且因为我们在 ActivityB 和 ActivityC 中都使用静态上下文,所以对 ActivityB 的引用仍然存在,所以我们总共有 6 张图像(3 张来自 ActivityB,3 张来自ActivityC),我的移动设备的限制是 5 张图片。因此,将发生 OutOfMemoryError。

2.监听器可以注册和注销

在 Android 中,我们使用各种类型的侦听器来检测位置变化等变化。因此,请记住在完成使用后取消注册您的侦听器。然后您需要注册该事件,即使不再需要该侦听器。因此,如果监听器没有被使用,最好取消注册它。让我们看一个真实世界的例子。我们将只使用一个注册器和一个注销器来为我们工作,就是这样。 register()函数将一个活动添加到活动数组中,然后 unregister()函数从活动数组中删除一个活动。 Singleton类的代码如下:

科特林

object gfgListner {
    private val someActivity = ArrayList()
    fun someRegister(myActivity: Activity) {
        someActivity.add(activity)
    }
    fun doUnrgis(activity: Activity) {
        someActivity.remove(activity)
    }
}

如果我向移动设备添加超过 5 张图像,应用程序将显示 OutOfMemoryError。因此,在本例中,将有三个活动:MainActivity、ActivityB 和 ActivityC。 ActivityB 和 ActivityC 将从 MainActivity 中调用,在这两个活动的 onCreate() 函数中,我们将调用 Singleton 类的 register() 方法将活动添加到活动数组中。如果我们从 MainActivity 调用 ActivityB,将保存对三个图像的引用(因为我们存储在 Singleton 类中)。因为我们的限制是 5 张图像,所以不会显示 OutOfMemoryError。现在,如果我们返回 MainActivity 并调用 ActivityC,我们会得到一个 OutOfMemoryError,因为我们最初引用了三张图片,当启动 ActivityC 时,又出现了三张图片,总共有六张图片(因为我们正在使用相同的 Singleton 类),我们的限制是五个。结果,我们将收到 MemoryOutOfBoundError。

科特林

override fun justStop() {
    AnotherListner.unregister(this)
    super.onStop()
}

3.嵌套类必须是静态的

如果您的应用程序中有嵌套类,请将其设为静态,因为静态类不需要外部类的隐式引用。因此,如果您使内部类成为非静态类,它会使外部类保持活动状态,直到应用程序启动。因此,如果您的类消耗大量内存,这可能会导致 OutOfMemoryError。因此,最好将内部类设为静态。在Java中,您必须自己将内部类设为静态,而在 Kotlin 中,内部类默认为静态。所以你不必担心 Kotlin 中的静态内部类。

4.第三方库中getContext()和getApplicationContext()函数使用错误

在我们的应用程序中,我们使用了很多第三方库,其中大部分使用了 Singleton 类。因此,如果您将某些上下文传递给当前活动范围之外的第三方库,请使用 getApplicationContext() 而不是 getContext()。通常,我们在应用程序中执行以下操作:

aThirdPartyLib.initialize(this)

在这种情况下,initialize 是该库中的一个静态函数,它以下列方式使用上下文:

科特林

aThirdLib {
  object someCopanion {
    initialize(Here context) {
       this.context = context.getApplicationContext()
    }
  }
}

但是,一些库不使用上述表示法。因此,它将使用当前活动的上下文,并且对当前活动的引用将一直保持到应用程序处于活动状态,这可能会导致 OutOfMemoryError(因为初始化函数是静态的)。

这些是从我们的应用程序中删除 OutOfMemoryError 的一些方法。最好编写不会引发 OutOfMemoryError 的代码。不过,如果您的项目很大并且您很难找到导致 OutOfMemoryError 的类,您可以使用 Android Studio 的内存分析器来定位导致 OutOfMemoryError 的类。您应该知道如何从这里实际使用 Android Studio Memory Profiler 的基本功能。