解决Android应用程序中OutOfMemoryError的实
OutOfMemoryError 或简单的 OOM 是每个 Android 开发人员都必须遇到的问题。如果您在 Android 应用程序中没有看到任何 OOM,那么您将来会有一个。由于内存泄漏,OutOfMemoryError 出现在 Android 中。因此,为了消除 OutOfMemoryError,您需要从 Android 应用程序中消除内存泄漏。
在本篇博客中,我们将通过一些实际示例来解决 Android 中的 OutOfMemoryError。那么,让我们开始了解 Android 中内存泄漏的基础知识。
什么是内存泄漏?
当您在 Android 设备上运行某个应用程序时,Android 系统会为您的应用程序提供一些内存供其工作。所有的变量创建、函数创建、活动创建等都只发生在那个内存中。
例如,如果 Android 系统为您的应用程序分配了 100MB,那么您的应用程序在某一特定时间最多可以使用 100MB。随着时间的推移,如果分配给应用程序的空间减少或只剩下很少量的空间,那么垃圾收集器(GC)将释放那些无用的变量和活动所持有的内存。这样,应用程序将再次获得一些空间。
注意:垃圾收集器执行自动释放内存的任务,您无需为此做任何事情。
但是有时,当您以一种糟糕的方式编写代码时,您的代码会保存不再需要的对象的引用,那么在这种情况下,垃圾收集器无法释放未使用的空间,并且没有空间留给申请其进一步工作。这称为内存泄漏。
由于 Android 中的 Memory Leak 现象,我们在 Android 中遇到 OutOfMemoryError 是因为您的代码持有不再需要的对象的引用,并且垃圾收集器无法执行其工作并且您的应用程序使用了所有分配的空间Android 系统对它的要求更高。
OutOfMemoryError 的原因可能是什么?
有多种原因可能导致 Android 中的 OutOfMemoryError。导致 OutOfMemoryError 的内存泄漏的一些常见原因是:
- 使用静态视图/上下文/活动
- 注册和注销监听器
- 非静态内部类
- 错误使用 getContext() 和 getApplicationContext()
让我们一一详细了解。
使用静态视图/上下文/活动
如果您正在使用一些静态视图/上下文/活动,那么您将遇到 OutOfMemoryError(如果您的活动处理大量空间)。这是因为视图或上下文或活动将由应用程序持有,直到应用程序处于活动状态,因此垃圾收集器不会释放这些占用的内存。
例如,在您的应用程序中,您有 4 个活动,即A、 B、 C和D。活动“ A ”是您的主要活动,您可以从A打开活动B、 C和D。现在,假设活动B、 C和D持有对其上下文的静态引用,并且每个活动使用 2MB 并且 Android 系统分配给应用程序的总内存为 4MB。因此,当活动“ B ”启动时,应用程序使用的内存将是 2MB。现在,回到活动“ A“并启动活动“ C ”。现在,应用程序使用的内存将是 4MB(活动B为 2MB,活动C为 2MB )。再次回到活动“ A ”并启动活动“ D ”。现在,您的应用程序将需要 6MB(B为 2MB,C为 2MB,D为 2MB ),但分配的内存为 4MB。因此,在打开活动“ D ”时,您将收到 OutOfMemoryError,因为分配的内存为 4MB,并且您要求6MB。
让我们看一个实际的实现。在此示例中,我使用的是位图。通过使用超过 5 个每个大小为 1MB 的图像,我的应用程序将在我的移动设备上遇到 OutOfMemoryError。在您的情况下,可能导致 OutOfMemoryError 的图像数量可能大于或小于 5。您可以通过将图像逐个添加到 BitmapArray 来找到限制,每当您获得 OOM 时,这就是您设备的限制.
所以,我的应用程序有一个MainActivity,从这个活动中,我可以转到“ ActivityB ”和“ ActivityC ”。这两个活动,即“ ActivityB ”和“ ActivityC ”都具有其上下文的静态引用。因此,垃圾收集器不会删除这些活动使用的空间。
以下是ActivityB的代码:
class ActivityB : AppCompatActivity() {
// static context
companion object {
lateinit var context: Context
}
val bitmapArray = ArrayList<Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
context = this
Thread(Runnable {
try {
// adding 3 images to bitmapArray
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)
bitmapArray.add(bitmap1)
bitmapArray.add(bitmap2)
bitmapArray.add(bitmap3)
} catch (e: Exception){
Logger.getLogger(ActivityB::class.java.name).warning(e.toString())
}
}).start()
}
}
以下是ActivityC的代码:
class ActivityC : AppCompatActivity() {
companion object {
lateinit var context: Context
}
val bitmapArray = ArrayList<Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_c)
context = 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.image3)
bitmapArray.add(bitmap1)
bitmapArray.add(bitmap2)
bitmapArray.add(bitmap3)
} catch (e: Exception){
Logger.getLogger(ActivityC::class.java.name).warning(e.toString())
}
}).start()
}
}
因此,如果您从MainActivity启动ActivityB,则不会出现 OutOfMemoryError ,因为我的移动设备的限制是 5 张图像,而在ActivityB中,我只使用 3 张图像。
现在,回到MainActivity并启动ActivityC,你会得到 OutOfMemoryError 因为在ActivityC中,我们使用了 3 张图像,并且由于我们在ActivityB和ActivityC中都使用静态上下文,所以ActivityB的引用仍然存在于我们和整体我们有 6 张图片(来自ActivityB的 3 张和来自ActivityC的3张),我的移动设备的限制是 5 张图片。所以,OutOfMemoryError 会发生。
要消除此错误,请将上下文或视图或活动设为非静态。
class ActivityB : AppCompatActivity() {
lateinit var context: Context
val bitmapArray = ArrayList<Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
注册和注销监听器
在 Android 中,我们使用各种监听器来监听位置变化等变化。因此,当您完成使用该侦听器时,永远不要忘记取消注册您的侦听器。如果您没有取消注册侦听器,那么侦听器将出现在后台,并且即使侦听器没有用,垃圾收集器也不会删除侦听器占用的空间。因此,如果不使用,最好取消注册侦听器。让我们看一个实际的例子。
在这个例子中,我们有一个名为“ SomeListener ”的单例类,它将有两个函数,即register()和unregister()。register()函数用于在活动数组中添加活动,而unregister()函数用于从活动数组中删除活动。以下是 Singleton 类的代码:
object SomeListener {
private val activityArray = ArrayList<Activity>()
fun register(activity: Activity) {
activityArray.add(activity)
}
fun unregister(activity: Activity) {
activityArray.remove(activity)
}
}
在我的移动设备中,如果我添加超过 5 个图像,那么应用程序将给出 OutOfMemoryError。因此,在本例中,我们将拥有三个活动,即MainActivity、ActivityB和ActivityC。ActivityB和ActivityC将从MainActivity中调用,在这两个活动的onCreate()函数中,我们将调用 Singleton 类的register()方法将活动添加到活动数组中。
以下是ActivityB的代码:
class ActivityB : AppCompatActivity() {
val bitmapArray = ArrayList<Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_b)
Thread(Runnable {
try {
// adding 3 images to bitmapArray
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)
bitmapArray.add(bitmap1)
bitmapArray.add(bitmap2)
bitmapArray.add(bitmap3)
} catch (e: Exception){
Logger.getLogger(ActivityB::class.java.name).warning(e.toString())
}
}).start()
// calling the register() function
SomeListener.register(this)
}
}
以下是ActivityC的代码:
class ActivityC : AppCompatActivity() {
val bitmapArray = ArrayList<Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_c)
Thread(Runnable {
try {
// adding 3 images to bitmapArray
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)
bitmapArray.add(bitmap1)
bitmapArray.add(bitmap2)
bitmapArray.add(bitmap3)
} catch (e: Exception){
Logger.getLogger(ActivityC::class.java.name).warning(e.toString())
}
}).start()
// calling the register() function
SomeListener.register(this)
}
}
如果我们从MainActivity调用ActivityB,则将存储 3 个图像的引用(就像我们存储在 Singleton 类中一样)。在这里,不会显示 OutOfMemoryError,因为我们的限制是 5 张图像。现在,如果我们回到MainActivity并调用ActivityC,那么我们将得到 OutOfMemoryError 因为最初,我们引用了 3 个图像,当ActivityC启动时,又出现了 3 个图像,总体而言我们引用了 6 个图像(如我们使用相同的 Singleton 类)并且我们的限制是 5 个图像。所以,我们会遇到 MemoryOutOfBoundError。
要消除此错误,您必须在活动关闭或无用时取消注册侦听器。因此,添加以下代码行以在活动停止时取消注册侦听器:
override fun onStop() {
SomeListener.unregister(this)
super.onStop()
}
嵌套类必须是静态的:
如果您在应用程序中有一些嵌套类,则将其设为静态类,因为静态类不需要外部类隐式引用。因此,如果您将内部类用作非静态类,那么它将使外部类处于活动状态,直到应用程序处于活动状态。因此,如果您的类使用大量内存,这可能会导致 OutOfMemoryError。因此,最好将内部类设为静态。
在 Java 中,您必须自己将内部类设为静态,但在 Kotlin 中,内部类在本质上是默认静态的。因此,您不必担心 Kotlin 中的静态内部类。
在第三方库中错误使用 getContext() 和 getApplicationContext()
我们在应用程序中使用了许多第三方库,其中大多数使用 Singleton 类。因此,如果您将某些上下文传递给第三方库并且该第三方库超出了当前活动的范围,则使用getApplicationContext()而不是getContext()。
通常,我们在应用程序中执行以下操作:
SomeThirdPartyLibrary.initialize(this)
在这里,initialize 是该库中的一些静态函数,它使用如下上下文:
SomeThirdPartyLibrary {
object companion {
initialize(Content context) {
this.context = context.getApplicationContext()
}
}
}
但有些库不使用上述表示法。因此,在这种情况下,它将使用当前活动的上下文,因此,当前活动的引用将一直保持到应用程序处于活动状态,这可能导致 OutOfMemoryError(因为初始化函数是静态的)。
因此,最好在代码中显式使用getApplicationContext()并且不要依赖第三方库来执行此操作。
这些是可用于从我们的应用程序中删除 OutOfMemoryError 的一些技术。最好编写不给出任何 OutOfMemoryError 的代码。尽管如此,如果您的项目非常大并且您越来越难以找到导致 OutOfMemoryError 的类,那么您可以使用 Android Studio 的内存分析器来查找导致 OutOfMemoryError 的类。
结论
在这篇文章中,我们学习了如何在 Android 中移除 OutOfMemoryError。这可以通过使用视图/上下文/活动的非静态引用,通过注册和注销侦听器,通过使用静态内部类来删除。因此,将来,如果您在 Android 中编写一些代码,那么我确信您将使用这些代码来避免 OutOfMemoryError。
作者:Sumit Mishra
链接:Practical Guide To Solve OutOfMemoryError in Android Application