解决Android应用程序中OutOfMemoryError的实用指南

OutOfMemoryError or simply OOM is something that every Android developer must have encountered with. If you haven’t seen any OOM in your Android application, then you are going to have one in future. The OutOfMemoryError comes in Android due to memory leaks. So, in order to remove the OutOfMemoryError, you need to remove memory leaks from your Android application.

每个Android开发人员都必须遇到OutOfMemoryError或仅仅是OOM。 如果您在Android应用程序中没有看到任何OOM,那么将来您将拥有一个。 由于内存泄漏,Android中出现OutOfMemoryError。 因此,为了删除OutOfMemoryError,您需要从Android应用程序中删除内存泄漏。

Originally published at blog.mindorks.com

最初发布在 blog.mindorks.com

In this blog, we will solve the OutOfMemoryError in Android with the help of some practical examples. So, let’s get started with the basics of Memory Leak in Android.

在此博客中,我们将通过一些实际示例解决Android中的OutOfMemoryError。 因此,让我们开始了解Android中内存泄漏的基本知识。

什么是内存泄漏? (What is a Memory Leak?)

When you run some applications on the Android device, then the Android system will provide some memory to your application for its working. All the variable creation, function creation, activity creation, etc takes place in that memory only.

当您在Android设备上运行某些应用程序时,Android系统将为您的应用程序提供一些内存以使其正常工作。 所有变量创建,函数创建,活动创建等仅在该内存中进行。

For example, if the Android System allocates 100MB for your application, then your application can use 100MB max at one particular time. With due course of time, if space allocated to the application gets reduced or very few amounts of space is left, then the Garbage Collector(GC) will release the memory that is being held by those variables and activities that are of no use. In this way, the application will again gain some space.

例如 ,如果Android系统为您的应用程序分配了100MB,则您的应用程序在某一特定时间最多可以使用100MB。 在适当的时间间隔内,如果分配给应用程序的空间减少了,或者只剩下了很少的空间,那么垃圾收集器(GC)将释放那些没有用的变量和活动所持有的内存。 这样,应用程序将再次获得一些空间。

NOTE: The Garbage Collector performs the task of releasing the memory automatically and you don’t have to do anything for this.

注意:垃圾收集器执行自动释放内存的任务,因此您无需执行任何操作。

But sometimes, when you write the code in a bad manner such that your code holds the references of objects that are not required anymore, then in this situation, the Garbage Collector is unable to release the unused space and no space will be left for the application for its further working. This is called a Memory Leak.

但是有时候,当您以不良的方式编写代码,使您的代码保存不再需要的对象的引用时,在这种情况下,垃圾收集器将无法释放未使用的空间,并且将没有剩余空间用于申请进一步工作。 这称为内存泄漏

Due to the phenomenon of Memory Leak in Android, we encounter with the OutOfMemoryError in Android because your code is holding the references of the objects that are not required anymore and the Garbage Collector is unable to perform its job and your application uses all the space allocated to it by the Android System and is demanding for more.

由于Android中的内存泄漏现象,我们在Android中遇到OutOfMemoryError,因为您的代码持有不再需要的对象的引用,并且垃圾回收器无法执行其工作,并且您的应用程序使用了分配的所有空间Android系统对此有更高的要求。

发生OutOfMemoryError的原因可能是什么? (What may be the reasons for OutOfMemoryError?)

There are various reasons that can lead to OutOfMemoryError in Android. Some of the common reason for Memory Leaks that results in OutOfMemoryError are:

有多种原因会导致Android中出现OutOfMemoryError。 导致OutOfMemoryError的内存泄漏的一些常见原因是:

  • Use of Static view/context/activity

    使用静态视图/上下文/活动
  • Register and unregister listeners

    注册和取消注册监听器
  • Non-Static inner class

    非静态内部类
  • Wrong use of getContext() and getApplicationContext()

    错误使用了getContext()和getApplicationContext()

Let’s learn one by one in detail.

让我们详细地逐一学习。

使用静态视图/上下文/活动 (Use of static views/context/activity)

If you are using some static views/context/activity then you are going to have OutOfMemoryError(if your activities are dealing with lots of space). This is because the view or the context or the activity will be held by the application until the application is alive and due to this the memory taken by these will not be released by the Garbage Collector.

如果您使用的是静态视图/上下文/活动,则将出现OutOfMemoryError(如果您的活动处理大量空间)。 这是因为视图,上下文或活动将由应用程序保留,直到应用程序处于活动状态为止,因此,垃圾收集器不会释放这些视图,上下文或活动所占用的内存。

For example, In your application, you are having 4 activities i.e. A, B, C, and D. Activity “A” is your main activity and you can open the activity B, C, and D from A. Now, suppose the activities B, C, and D are holding static reference to its context and each activity is using 2MB and total memory allocated to the application by the Android System is 4MB. So, when activity “B” will be started then the memory used by the application will be 2MB. Now, come back to the activity “A” and start activity “C”. Now, the memory used by the application will be 4MB(2MB for Activity B and 2MB for Activity C). Again come back to activity “A” and start activity “D”. Now, your app will require 6MB(2MB for B, 2MB for C and 2MB for D), but the allocated memory is 4MB. So, here while opening the activity “D”, you will get OutOfMemoryError because the allotted memory is 4MB and you are demanding for 6MB.

例如 ,在您的应用程序中,您有4个活动,即ABCD。 活动“ A ”是您的主要活动,您可以从A打开活动BCD。 现在,假设活动BCD对其上下文保持静态引用,并且每个活动使用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。

Let’s look at one practical implementation. Here in this example, I am using Bitmap. By using more than 5 images of size 1MB each, my application will encounter OutOfMemoryError on my mobile device. In your case, the number of images that can result in OutOfMemoryError can be more than or less than 5. You can find the limit by adding images to the BitmapArray one by one and whenever you get the OOM, that is the limit of your device.

让我们看一个实际的实现。 在此示例中,我正在使用位图。 通过使用5张以上大小均为1MB的图像,我的应用程序将在移动设备上遇到OutOfMemoryError。 在您的情况下,可能导致OutOfMemoryError的图像数量可能大于或小于5。您可以通过将图像一张一张地添加到BitmapArray中来找到限制,并且只要获得OOM,这就是设备的限制。 。

So, my application is having one MainActivity and from this activity, I can go to “ActivityB” and “ActivityC”. Both these activities i.e. “ActivityB” and “ActivityC” are having static reference of their context. So, space used by these activities will not be deleted by Garbage Collector.

因此,我的应用程序具有一个MainActivity ,从此活动中,我可以转到“ ActivityB ”和“ ActivityC ”。 这两个活动,即“ ActivityB ”和“ ActivityC ”都对其上下文具有静态引用。 因此,这些活动使用的空间不会被垃圾收集器删除。

Following is the code for ActivityB:

以下是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()
}
}

Following is the code for the ActivityC:

以下是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()
}
}

So, if you launch the ActivityB from MainActivity, then there will no OutOfMemoryError because the limit for my mobile device is 5 images and in the ActivityB, I am using only 3 images.

因此,如果从MainActivity启动ActivityB ,则不会出现OutOfMemoryError,因为我的移动设备的限制为5张图像,而在ActivityB中 ,我仅使用3张图像。

Now, come back to the MainActivity and launch ActivityC and you will get OutOfMemoryError because here in the ActivityC, we are using 3 images and since we are using the static context in both ActivityB and ActivityC, the reference of ActivityB is still with us and overall we have 6 images(3 from ActivityB and 3 from ActivityC) and the limit for my mobile device is 5 images. So, OutOfMemoryError will occur.

现在,回过头来在MainActivity和发射ActivityC,你会得到OutOfMemoryError异常,因为这里的ActivityC,我们使用3个图像,因为我们使用的是在这两个ActivityBActivityC静态背景下,ActivityB的基准仍是与我们的整体我们有6幅图像(3从ActivityB和3从ActivityC)和我的移动设备的限制为5个图像。 因此,将发生OutOfMemoryError。

To remove this error, make the context or view or activity, Non-Static.

要消除此错误,请将上下文或视图或活动设为非静态。

class ActivityB : AppCompatActivity() {

lateinit var context: Context
val bitmapArray = ArrayList<Bitmap>() override fun onCreate(savedInstanceState: Bundle?) {
...
}
}

注册和取消注册监听器 (Register and unregister listeners)

In Android, we use various kind of listeners to listen to the changes like the location change. So, never forgot to unregister your listener when you are done using that listener. If you are not unregistering the listeners, then the listener will be present in the background and the space taken by the listener will not be deleted by the Garbage Collector even when the listener is of no use. So, it is better to unregister the listener if not in use. Let’s look at one practical example.

在Android中,我们使用各种侦听器来侦听位置更改等更改。 因此,当您完成使用该侦听器的操作时,请不要忘记注销它。 如果您没有注销侦听器,则该侦听器将出现在后台,并且即使该侦听器无用,垃圾收集器也不会删除该侦听器占用的空间。 因此,如果不使用监听器,最好注销它。 让我们看一个实际的例子。

In this example, we are having one Singleton class named “SomeListener” that will be having two functions i.e. register() and unregister(). The register() function is used to add the activity in an activity array and the unregister() function is used to remove the activity from the activity array. Following is the code for the Singleton class:

在此示例中,我们有一个名为“ SomeListener ”的Singleton类,它将具有两个函数,即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)
}
}

In my mobile device, if I am adding more than 5 images, then the application will give OutOfMemoryError. So, in this example, we will be having three activities i.e. MainActivity, ActivityB, and ActivityC. The ActivityB and ActivityC will be called from MainActivity and in the onCreate() function of both these activities, we will call the register() method of the Singleton class to add the activities in the activity array.

在我的移动设备中,如果要添加5张以上的图像,则该应用程序将给出OutOfMemoryError。 因此,在此示例中,我们将拥有三个活动,即MainActivityActivityBActivityC 。 将从MainActivity调用ActivityBActivityC ,并在这两个活动的onCreate()函数中,我们将调用Singleton类的register()方法将活动添加到活动数组中。

Following is the code of ActivityB:

以下是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)
}
}

Following is the code of ActivityC:

以下是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)
}
}

If we call the ActivityB from MainActivity, then the reference of 3 images will be stored(as we are storing in the Singleton class). Here, no OutOfMemoryError will be shown because our limit is of 5 images. Now, if we come back to the MainActivity and call the ActivityC, then we will get OutOfMemoryError because initially, we are having reference to 3 images and when ActivityC is launched, then 3 more images came and overall we have reference to 6 images(as we are using the same Singleton class) and our limit is of 5 images. So, we will encounter MemoryOutOfBoundError.

如果我们从MainActivity调用ActivityB ,那么将存储3张图像的引用(就像我们存储在Singleton类中一样)。 在此,由于我们的限制为5张图片,因此不会显示OutOfMemoryError。 现在,如果返回MainActivity并调用ActivityC ,则将得到OutOfMemoryError,因为最初,我们引用了3个图像,当启动ActivityC时,又出现了3个图像,总体上我们引用了6个图像(如我们使用相同的Singleton类),并且限制为5张图片。 因此,我们将遇到MemoryOutOfBoundError。

To remove this error, you have to deregister the listener when the activity is closed or of no use. So, add the below lines of code to unregister the listener when the activity is stopped:

要消除此错误,必须在活动关闭或无用时注销注册侦听器。 因此,添加以下代码行以在活动停止时注销注册侦听器:

override fun onStop() {
SomeListener.unregister(this)
super.onStop()
}

嵌套类必须是静态的: (The nested class must be Static:)

If you are having some nested class in the application, then make it a static class because the static class does not need the outer class implicit reference. So, if you are using the inner class as non-static then it makes the outer class alive until the application is alive. So, this can lead to OutOfMemoryError if your class is using a lot of memory. So, it is better to make the inner class Static.

如果您在应用程序中有一些嵌套类,则将其设为静态类,因为静态类不需要外部类的隐式引用。 因此,如果将内部类用作非静态类,则它将使外部类处于活动状态,直到应用程序处于活动状态。 因此,如果您的类使用大量内存,则可能导致OutOfMemoryError。 因此,最好使内部类为静态。

In Java, you have to make the inner class static by your own but in Kotlin, the inner class is by default static in nature. So, you need not worry about static inner class in Kotlin.

在Java中,必须自行使内部类静态化,但在Kotlin中,默认情况下,内部类本质上是静态的。 因此,您不必担心Kotlin中的静态内部类。

在第三方库中错误地使用了getContext()和getApplicationContext() (Wrong use of getContext() and getApplicationContext() in third party libraries)

We use many third-party libraries in our application and most of them use Singleton class. So, if you are passing some context to a third-party library and that third-party library is beyond the scope of the current activity, then use getApplicationContext() and not getContext().

我们在应用程序中使用了许多第三方库,其中大多数使用Singleton类。 因此,如果您要将某些上下文传递给第三方库,并且该第三方库超出了当前活动的范围,请使用getApplicationContext()而不是getContext()

Generally, we do the below thing in our application:

通常,我们在应用程序中执行以下操作:

SomeThirdPartyLibrary.initialize(this)

Here, initialize is some static function in that library and it uses the context something like this:

在这里,initialize是该库中的一些静态函数,它使用如下上下文:

SomeThirdPartyLibrary {
object companion {
initialize(Content context) {
this.context = context.getApplicationContext()
}
}
}

But some of the libraries don’t use the above notation. So, in that case, it will use the context of the current activity and due to this, the reference of the current activity will be held until the application is alive and this can result in OutOfMemoryError(as the initialize function is static).

但是某些库不使用上述符号。 因此,在这种情况下,它将使用当前活动的上下文,因此,将保留当前活动的引用,直到应用程序处于活动状态为止,这可能会导致OutOfMemoryError(因为initialize函数是静态的)。

So, it is better to use the getApplicationContext() in your code explicitly and don’t depend on third-party libraries to do that.

因此,最好在代码中显式使用getApplicationContext() ,并且不要依赖第三方库来执行此操作。

These are some of the techniques that can be used to remove OutOfMemoryError from our application. It is better to write the code that doesn’t give any OutOfMemoryError. Still, if your project is very big and you are getting it harder to find the class that is responsible for OutOfMemoryError, then you can use the memory profiler of Android Studio to find the classes responsible for OutOfMemoryError.

这些是可用于从我们的应用程序中删除OutOfMemoryError的一些技术。 最好编写不给出OutOfMemoryError的代码。 不过,如果您的项目很大,并且很难找到负责OutOfMemoryError的类,那么可以使用Android Studio的内存探查器来查找负责OutOfMemoryError的类。

Learn how to use Android Studio Memory Profiler

了解如何使用Android Studio Memory Profiler

Also, there is one library called LeakCanary, that tells you about the class that is responsible for memory leaks in your Android application(if any).

另外,有一个名为LeakCanary的库,它告诉您有关导致Android应用程序中内存泄漏的类(如果有)。

Learn more about LeakCanary

了解有关LeakCanary的更多信息

结论 (Conclusion)

In this blog, we learned how to remove OutOfMemoryError in Android. This can be removed by using a non-static reference of views/context/activity, by registering and unregistering the listeners, by using a static inner class. So, in the future, if you are writing some code in Android, then I am sure that you are going to use these to avoid OutOfMemoryError.

在此博客中,我们学习了如何在Android中删除OutOfMemoryError。 可以通过使用视图/上下文/活动的非静态引用,通过使用静态内部类注册和注销侦听器,来删除此属性。 因此,将来,如果您要在Android中编写一些代码,那么我确定您将使用这些代码来避免OutOfMemoryError。

That’s it for this blog. Hope you learned something new today.

本博客就是这样。 希望您今天学到了新东西。

If you are preparing for your next Android Interview, Join our Android Professional Course to learn the latest in Android and land job at top tech companies.

如果您正在为下一次Android面试做准备,请参加我们的Android专业课程,以学习最新的Android知识并在顶级科技公司找到工作。

Happy Learning :)

快乐学习:)

Team MindOrks!

MindOrks团队!

Also, Let’s connect on Twitter, Linkedin, Github, and Facebook

另外,让我们在 Twitter Linkedin Github Facebook上连接

翻译自: https://medium.com/mindorks/practical-guide-to-solve-outofmemoryerror-in-android-application-8cccfdc5c60c

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值