Android内存泄漏

1 什么是内存泄漏(Memory Leak)

leak [liːk] 渗入,漏出;漏,渗漏;泄露,透露

每个应用程序都需要内存来完成工作,为了确保 Android 系统的每个应用都有足够的内存,Android 系统需要有效地管理内存分配。当内存不足时,Android 运行时都会触发 GC,GC 采用的垃圾标记算法为根搜索算法。如下所示:

根搜索算法

从上图中可以看出,Obj4 是可达的对象,表示它正在被引用,因此不会标记为可回收的对象。Obj5、Obj6 和 Obj7 都是不可达的对象,其中,Obj5 和 Obj6 虽然相互引用,但是因为它们到 GC Roots 是不可达的,所以它们仍被标记为可回收的对象。

内存泄露就是指没有用的对象到 GC Roots 是可达的(对象被引用),导致 GC 无法回收该对象。此时,如果 Obj4 是一个没有用的对象,但它仍与 GC Roots 是可达的,那么 Obj4 就会发生内存泄漏。

Java 是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(Heap)挤爆的可能,所以写出来的代码更为安全。

segmentation [ˌseɡmenˈteɪʃn] 分割;割断;细胞分裂

但是,在 Java 中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,Android 应用很容易浪费掉未释放的内存,最终导致内存溢出(out-of-memory,OOM)。

  • 一般内存泄漏(traditional memory leak)的原因是:当该对象的所有引用都已经释放了,对象仍未被释放;
  • 逻辑内存泄漏(logical memory leak)的原因是:当应用不再需要这个对象,当仍未释放该对象的所有引用;

logical [ˈlɑːdʒɪkl] 合情合理的;合乎逻辑的;逻辑(上)的

内存泄漏(Memory Leak):当应用不需要某个对象时,本该被回收,但是持有该对象的引用仍旧没有被释放,从而导致了对象不能被 GC 回收。

内存溢出(OOM Out Of Memory):当应用程序的堆(Heap)资源超过了 Dalvik 虚拟机分配的内存就会内存溢出。

对于 C++ 来说,内存泄漏就是 new 出来的对象没有 delete;对于 Java 来说,内存泄漏就是 new 出来的对象在 Heap 上无法被 GC 回收。

内存泄漏带来的影响:影响内存分配,造成应用程序执行效率降低;可分配的内存越少,更加容易出现内存溢出。

总结:内存泄漏的发生原因本质上是因为生命周期较长的对象去引用生命周期较短的对象,导致生命周期短的对象无法被 GC 及时回收掉,从而导致被占用的内存无法被释放,如果程序长期运行最终可能会导致 OOM 内存溢出。

Java 中的内存分配:

  1. 静态储存区:编译时就分配好,在程序整个运行期间都存在,它主要存放静态数据和常量;
  2. 栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存;
  3. 堆区:通常存放 new 出来的对象,由 Java 垃圾回收器回收;

成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体),因为它们属于类,类对象最终还是要被 new 出来的。局部变量的基本数据类型和引用存在栈中,应用的对象实体存储在堆中。因为它们属于方法当中的变量,生命周期会随着方法一起结束。

2 四种引用类型

  • 强引用(StrongReference):虚拟机宁可抛出 OOM,也不会让 GC 回收具有强引用的对象;(生命周期:进程终止)
  • 软引用(SoftReference):只有在内存空间不足时,才会被回的对象;(生命周期:内存不足,进行 GC 后)
  • 弱引用(WeakReference):在 GC 时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存;(GC 后)
  • 虚引用(PhantomReference):如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被 GC 回收;

phantom [ˈfæntəm] 虚幻的,幻觉的;

如果持有对象的强引用,垃圾回收器是无法在内存中回收这个对象。

在开发时,为了防止内存溢出,处理一些比较占用内存并且生命周期长的对象时,可以尽量使用软引用和弱引用。

弱引用和引用队列:

fun main() {
    val referenceQueue = ReferenceQueue<Pair<String, Int>?>()
    var pair: Pair<String, Int>? = Pair("小黄", 24)
    val weakReference = WeakReference(pair, referenceQueue)

    println(referenceQueue.poll()) // null

    pair = null

    System.gc()
    Thread.sleep(5000)
    println(referenceQueue.poll()) // java.lang.ref.WeakReference@4f023edb
}

可以看到,在 GC 过后 referenceQueue.poll() 的返回值变成了非 null,这是由于 WeakReference 和 ReferenceQueue 的一个组合特性导致的:在声明一个 WeakReference 对象时如果同时传入了 ReferenceQueue 作为构造参数的话,那么当 WeakReference 持有的对象被 GC 回收时,JVM 就会把这个弱引用存入到与之关联的引用队列中,依靠这个特性,就可以实现内存泄漏的检测了。

例如,当用户按返回键退出 Activity 时,正常情况下该 Activity 对象应该在不久后被系统回收,我们可以监听 Activity.onDestroy() 方法,在毁掉时把 Activity 对象保存到和 ReferenceQueuw 关联的 WeakReference 中,在一段时间后(可以主动触发几次)检测 ReferenceQueue 中是否有值,如果一直为 null,则说明发生了内存泄漏。

3 发生内存泄漏的情况

Activity 是重量级对象,应该让 Android 系统来处理它。然而,逻辑内存泄漏总是在不经意间发生。在 Android 中,导致潜在内存泄漏的陷阱不外乎两种:

  • 全局进程(process-global)的 static 变量,这个无视应用的状态,持有 Activity 的强引用的怪物;
  • 活在 Activity 生命周期之外的线程,没有清空对 Activity 的强引用。
3.1 Activity Context 不正确使用造成的内存泄漏

在 Android 开发中,最容易引发的内存泄漏问题的是 Context。比如 Activity 的 Context,就包含大量的内存引用,例如 View Hierarchies 和其他资源。一旦泄漏了 Context,也意味泄漏它指向的所有对象。

在 Android 应用程序中通常可以使用两种 Context 对象:Activity 和 Application。

比如,对于自定义 View 通常需要传入 Context 对象,这样就意味着 View 对象对整个 Activity 保持引用,因此也就保持对 Activty 的所有的引用。

再比如,单例模式,当调用 getInstance 时,如果传入的 context 是 Activity 的 context,只要这个单例没有被释放,那么这个 Activity 也不会被释放一直到进程退出才会释放。

解决方案:能使用 Application 的 Context 就不要使用 Activity 的 Content,Application 的生命周期伴随着整个进程的周期。

检测逻辑内存泄漏需要主观判断,特别是对象的生命周期并不清晰。幸运的是,Activity 有着明确的生命周期,很容易发现泄漏的原因。Activity.onDestroy() 被视为 Activity 生命的结束,程序上来看,它应该被销毁了,或者 Android 系统需要回收这些内存。如果这个方法执行完,在堆栈中仍存在持有该 Activity 的强引用,垃圾回收器就无法把它标记成已回收的内存,结果就是 Activity 存活在它的生命周期之外。

3.2 非静态内部类引起的内存泄漏

在 Java 中,非静态内部类和匿名类都会潜在的引用它们所属的外部类(静态内部类却不会)。如果这个非静态内部类实例做了一些耗时的操作,就会造成外部类对象不会被回收,从而导致内存泄漏。

解决方案:将非静态内部类修改为静态内部类,静态内部类不会隐式持有外部类,或者在内部类销毁的时候取消相关的的任务。

3.3 Handler 引起的内存泄漏

在 Activity 中使用非静态内部类或者匿名内部类创建 Handler,这样 Handler 就默认持有外部类 Activity 的引用,如果 Activity 在需要销毁时,Handler 还有未执行完或者正在执行的 Message,Message 持有 Handler,因为 message.target == handler,而 Handler 又持有 Activity 的引用,导致 GC 无法回收 Activity,导致内存泄漏。

可以使用 内部类 + 弱引用 来解决,代码如下所示:

private class MyHandler extends Handler {
  private WeakReference<MainActivity> mWeakReference;
  private Activity activity;

  public MyHandler(Activity activity) {
    this.mWeakReference = new WeakReference(activity);
  }

  @Override
  public void handleMessage(@NonNull Message msg) {
    super.handleMessage(msg);
    activity = mWeakReference.get();
    if (activity != null) {
      
    }
  }
}
3.4 资源未关闭造成的内存泄漏

对于使用了 BraodcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap 等资源的使用,应该在 Activity 销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

解决方案:在 Activity 销毁时及时关闭或者注销。

Bitmap 对象在确认不使用时,应该先调用 recycle() 释放内存,然后在设置为 null,否则会导致内存泄漏。

资源性对象比如(Cursor,File 文件等)往往都用了一些缓冲,在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待 GC 来处理。

3.5 注册监听器造成的内存泄漏

系统服务可以通过 Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果 Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有 Activity 的引用,如果在 Activity onDestory 时没有释放掉引用就会内存泄漏。

解决方法:

  • 使用 Application Context 代替 Activity Context;
  • 在 Activity 执行 onDestory 时,调用反注册;
3.6 集合中对象没有清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合容器(比如 ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是 static 的话,那情况就更严重了。

解决方案:当我们不需要该对象时,并没有把它的引用从集合中清理掉,然后置为 null。

3.7 WebView 造成的内存泄漏

当我们不使用 WebView 对象时,应该调用它的 destory() 函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。

// 创建
WebView mWebView = new WebView(getApplicationContext());

// 销毁
if (mWebView != null) {
  mWebView.setVisibility(View.GONE);
  mWebView.removeAllViews();
  mWebView.destroy();
  mWebView = null;
}

解决方案:为 WebView 开启另外一个进程,通过 AIDL 与主线程进行通信,WebView 所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

3.8 构造 Adapter 时,没有使用缓存的 ConvertView

初始时 ListView 会从 Adapter 中根据当前的屏幕布局实例化一定数量的 View 对象,同时 ListView 会将这些 View 对象缓存起来。当向上滚动 ListView 时,原先位于最上面的 List Item 的 View 对象会被回收,然后被用来构造新出现的最下面的 List Item。

这个构造过程就是由 getView() 方法完成的,getView() 的第二个形参 View ConvertView 就是被缓存起来的 List Item 的 View 对象(初始化时缓存中没有 View 对象则 ConvertView 是 null)。

3.9 静态的 Activity 和静态的 View

在类中定义了静态 Activity 变量,把当前运行的 Activity 实例赋值于这个静态变量。如果这个静态变量在 Activity 生命周期结束后没有清空,就导致内存泄漏,因为 static 变量是贯穿这个应用的生命周期的,所以被泄漏的 Activity 就会一直存在于应用的进程中,不会被垃圾回收器回收。

public class TestActivity extends AppCompatActivity {
    static Activity activity;

    void setActivity() {
        activity = this;
    }
}

如果一个 View 初始化耗费大量资源,而且在一个 Activity 生命周期内保持不变,那可以把它变成 static,加载到视图树上(View Hierachy),像这样,当 Activity 被销毁时,应当释放资源。

public class TestActivity extends AppCompatActivity {
    static View view;

    void setView() {
        view = findViewById(R.id.button);
    }
}

如果想要释放内存,把 static activity、static view 置 null 即可,但是还是不建议用 static activity、static view。

4 内存检测工具

4.1 LeakCanary 工具

官方地址如下:https://github.com/square/leakcanary/LeakCanary 需要在项目代码中集成。当内存泄漏发生时,LeakCanary 会弹窗提示并生成对应的堆存储信息记录。

LeakCanary2 的引入使用非常简单,在 build.gradle 添加以下依赖即可:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'

如果 LeakCanary2 被顺利初始化,Logcat 输出:

LeakCanary

为什么只用添加 build.gradle 依赖就可以初始化 LeakCanary 呢?原因在于在leakVanary-object-watcher-android:2.9.1@aar/AndroidManifest.xml 中,声明了 ContentProvider:

MainProcessAppWatcherInstaller

由此可知,LeakCanary 将初始化过程交由 MainProcessAppWatcherInstaller 这个 ContentProvider 来自动完成的。以下是 MainProcessAppWatcherInstaller 的相关源码:

internal class MainProcessAppWatcherInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
  
}

由于 ContentProvider 会在 Application 被创建之前就由系统调用其 onCreate() 方法来完成初始化,所以 LeakCanary 通过 AppWatcherInstaller 就可以拿到 Context 来完成初始化并随应用一起启动,通过这种方式就简化了使用者的引入成本。MainProcessAppWatcherInstaller 最终会将 Application 对象传给 AppWatcher 的 manualInstall(Application) 方法:

object AppWatcher {
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 默认参数,5s
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    checkMainThread()
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // 初始化一些配置,配置泄漏Listener,GC触发器,Dump类
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    // 调用各种install,其实就是为各种InstallableWatcher注册一些监听时间,这里有四个InstallableWatcher
    watchersToInstall.forEach {
      it.install()
    }
    // Only install after we're fully done with init.
    installCause = RuntimeException("manualInstall() first called here")
  }

  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
}

在 AppWatcher.appDefaultWatchers 中有四种类型的 InstallableWatcher,实现大同小异。

以下是 ActivityWatcher 的源码:

class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
  object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
    override fun onActivityDestroyed(activity: Activity) {
      reachabilityWatcher.expectWeaklyReachable(
        activity, "${activity::class.java.name} received Activity#onDestroy() callback"
      )
    }
  }

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

ActivityWatcher.install() 是为了注册监听。在 lifecycleCallbacks 中可以看出,当 Acrivity 发生onDestory 的时候,会回调到 onActivityDestroyed 这里面来。而这里面调用的是 reachabilityWatcher.expectWeaklyReachable。reachabilityWatcher 是什么?AppWatcher.appDefaultWatchers函数可以知道,appDefaultWatchers 其实就是 objectWatcher。

以下是 objectWatcher 的初始化:

object AppWatcher {
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )
}

class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
  
  private val queue = ReferenceQueue<Any>()
  
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 移除那些没有发生泄漏的对象
    removeWeaklyReachableObjects()
    // 生成key
    val key = UUID.randomUUID().toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 通过这个key,构建一个弱引用对象,同时传入一个引用队列
    val reference = 
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) // --> 1
    SharkLog.d {
      "Watching " +
      (if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
      (if (description.isNotEmpty()) " ($description)" else "") +
      " with key $key"
    }
		// 将弱引用对象存入watchedObjects
    watchedObjects[key] = reference // --> 2
    checkRetainedExecutor.execute {
      moveToRetained(key) // 这个方法会延迟5s --> 3
    }
  }

  @Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
    val weakRefsToRemove =
    watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
    weakRefsToRemove.values.forEach { it.clear() }
    watchedObjects.keys.removeAll(weakRefsToRemove.keys)
  }

  @Synchronized fun clearWatchedObjects() {
    watchedObjects.values.forEach { it.clear() }
    watchedObjects.clear()
  }

  @Synchronized private fun moveToRetained(key: String) {
    // 移除那些没有发生泄漏的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    // 执行了移除操作后,在watchedObjects中还能找到对应的弱引用的话,说明这个对象发生了泄漏
    if (retainedRef != null) {
      // 记录当前时间
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

  private fun removeWeaklyReachableObjects() {
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
}

LeakCanary 的原理:

  • 当一个 Activity.onDestory 方法被执行后,说明该 Activity 的生命也结束了,在发生 GC 的时候,该 Activity 应该被回收;
  • LeakCanary 通过 Application.registerActivityLifecycleCallbacks() 方法可以注册 Activity 生命周期的监听;当一个 Activity 需要被回收的时候会生成一个唯一的 key,把它封装在 KeyedWeakReference(弱引用)中,并传入到自定义的 ReferenceQueue(引用队列中)中(注释 1),并将 key 和 KeyedWeakReference 放到一个 map 中(注释 2);
    • 说明:在声明一个 WeakReference 对象时如果同时传入了 ReferenceQueue 作为构造参数的话,那么当 WeakReference 持有的对象被 GC 回收时,JVM 就会把这个弱引用存入到与之关联的引用队列中,依靠这个特性,就可以实现内存泄漏的检测了。
  • 一段时间(一般是 5s)后主动触发 GC(之前的 GC 已经把要回收的 KeydWeakReference 对象放在了 ReferenceQueue 中),将自定义的 ReferenceQueue 中的 KeyedWeakReference 的对象全部移除。此时如果 map 中还有 KeyedWeakReference 剩余,那么就是有些对象没有进入队列,那么这些 KeyedWeakReference 对应的对象还没被回收,这是不合理的,因此产生了内存泄漏;
  • 当被泄漏的对象达到一个阈值,LeakCanary 就会把 Java 的堆栈信息 dump 到 .hprof 文件中;
  • LeakCanary 用自带的 Shark 库来解析 .hprof 文件,找到无法被清理的引用的引用栈,然后再根据对 Android 系统的知识来判定是哪个实例导致的泄漏;
  • 通过泄漏信息,LeakCanary 会将一条完整的引用链缩减到一个小的引用链,其余的因为这个小的引用链导致的泄漏链都会被聚合再一起;

dump [dʌmp] 转存(计算机数据)

LeakCanary 是如何使用 ObjectWatcher 监控生命周期的?LeakCanary 使用了Application.ActivityLifecycleCallbacks 方法进行 Activity 和 Fragment 的生命周期监测,当 Activity 和 Fragment 被回调 onDestroy 以后就会生成 KeyedWeakReference 来监测,然后借助 HeapDumpTrigger 的轮询和触发 GC 的操作找到弹出提醒的时机。

LeakCanary 如何 dump 和分析 .hprof 文件的?使用 Android 平台自带的Debug.dumpHprofData 方法获取到 hprof 文件,使用自建的 Shark 库进行解析,获取到 LeakTrace。

LeakCanary 中的 Idle 机制:在 Activity.onDestroy 的时候,LeakCanary 并没有马上去执行检测任务,而是将任务添加到消息队列的一个 Idle 任务列表里,然后当 Handler 在消息队列中获取不到消息,也就是主线程空闲的时候,会去 Idle 任务列表里取任务出来执行。

idle [ˈaɪd(ə)l] 闲置的;空闲的

4.2 Android Profiler 分析器

通过 Android Studio 的 Profiler 分析器,该方式可以直观的观察在程序运行期间产生的内存波动并且可以明确列出所有产生内存消耗的对象以及内存分配情况,需要一些经验才能有效使用。

打开 Profiler:

Profiler
点击 “+” 选择自己的应用包名:

创建 SESSIONS

选择完后就会显示创建的 SESSIONS 页面:

SESSIONS 界面

如果想要删除这个 SESSIONS,右键删除:

删除 SESSIONS
点击 memory 这一行:

memory
点击后可以看到内存捕获类型页面:

内存捕获类型页面

  • 捕获堆转储(Capture heap dump):查看应用程序中在特定时间点使用内存的对象;
  • 记录 Native 分配(Record native allocations):查看每个 C/C++ 对象在一段时间内是如何分配的;
  • 记录 java/kotlin 分配(Record java/kotlin allocations):查看在一段时间内如何分配每个 java/kotlin 对象;

Record java/kotlin allocations

Allocation Records

下拉红框区域:

在这里插入图片描述

在这里插入图片描述

使用内存分析器查看应用的内存使用情况

参考

https://www.jianshu.com/p/4267a4992625
https://blog.csdn.net/weixin_35398720/article/details/117720897
https://zhuanlan.zhihu.com/p/25213586
https://developer.51cto.com/article/513681.html
https://www.jianshu.com/p/c364a1474c38
Android知识体系之异常处理-内存泄漏
欲言又止!面试官:说一下LeakCanary的原理!
LeakCanary 的原理是什么?
【Android 日常学习】LeakCanary——面试官最爱问的性能优化工具,你知道它是怎么工作的吗?(源码分析)
(面试必备-源码分析系列)LeakCanary看这篇就行了
偏僻又热门,引用与引用队列
用烂LeakCanary2,隔壁产品看不懂了

android studio内存分析之Memory profiler的使用

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android内存泄露是指应用程序在使用完对象后,没有及时将其释放,导致这些对象无法被垃圾回收器回收,最终导致应用程序的内存占用不断增加,直至崩溃。以下是一些可能导致Android内存泄露的情况及解决方法: 1. 静态变量:如果在应用程序中使用了静态变量,并且这些变量引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的静态变量设为null。 2. 匿名内部类:如果在应用程序中使用了匿名内部类,并且这些内部类引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的匿名内部类引用设为null。 3. Handler:如果在应用程序中使用了Handler,并且这些Handler引用了Activity或者Fragment等容器类,就可能导致内存泄露。解决方法是在Activity或者Fragment销毁时,将相关的Handler引用设为null。 4. Bitmap对象:如果在应用程序中使用了Bitmap对象,并且没有及时释放,就可能导致内存泄露。解决方法是在使用完Bitmap对象后,调用recycle()方法释放内存。 5. 资源对象:如果在应用程序中使用了资源对象,并且没有及时释放,就可能导致内存泄露。解决方法是在使用完资源对象后,调用其对应的释放方法,比如close()。 希望以上这些解决方法可以帮助您避免Android内存泄露的问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值