1.Android内存优化

在实践操作当中,可以从三个方面着手减小内存使用,首先是减小对象的内存占用,其次是内存对象的重复利用,最后是避免对象的内存泄露。
也可以从从设备分级、Bitmap 优化和内存泄露这三个方面入手。

减小内存占用

Bitmap 优化

[[4.图片、glide优化]]

  1. 资源和图片压缩,对于低端机用户可以关闭复杂的动画、或者某些功能;使用 565 格式的图片
  2. 一个空进程也会占用 10M 的内存,减少应用启动的进程数,减少常驻进程、有节操的保活,对低端机内存优化非常重要。
  3. Serializable全部改成 Parcelable(/ˈpɑːrsl/)[[Serializable 与 Parcelable]]
  4. arraymap,sarparray 代替 hashmap [[SparseArray 和 ArrayMap]]

内存对象的重复利用

  1. 使用线程池(对象池)
  2. 避免创建不必要的对象,单例
  3. 合理的使用缓存,使用lrucache,比如图片是很耗内存
  4. 内存抖动

内存抖动

内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,内存抖动出现原因主要是频繁(在循环里)创建对象(短时间内产生大量对象,需要大量内存,就可能会需要回收内存以用于产生对象,垃圾回收机制就自然会频繁运行了)。频繁内存抖动会导致垃圾回收频繁运行。解决的方法:

  1. 尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
  2. 注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
  3. 当需要大量使用Bitmap的时候,把它们缓存在数组中实现复用。
  4. 对于能够复用的对象,可以使用对象池将它们缓存起来。
  5. 允许复用的情况下,使用对象池进行缓存,如:Handler的Message单链表(obtain);
  • RecyclerView滚动优化

    • 问题:onBindViewHolder中创建Bitmap/Paint
    • 现象:快速滚动时明显卡顿
    • 方案:ViewHolder复用+对象池
  • 动画资源泄漏

    • 问题:未复用ValueAnimator/ObjectAnimator
    • 现象:连续触发动画时内存锯齿状波动
    • 方案:使用AnimatorSet复用动画对象
      1
      2
      3
      4
      5
      6
      7
      8
      if (mShowSearchContainerAnimatorSet == null) {  
      AnimatorSetBuilder builder = new AnimatorSetBuilder();
      PropertySetter.AnimatedPropertySetter setter = new PropertySetter.AnimatedPropertySetter(300, builder);
      setter.setViewAlpha(mCategoryContainer, 0, Interpolators.LINEAR);
      setter.setViewAlpha(mSearchContainer, 1, Interpolators.LINEAR);
      mShowSearchContainerAnimatorSet = builder.build();
      }
      mShowSearchContainerAnimatorSet.start();

根本原因

[[3.内存泄漏简单问]]

Handler 都不能算是罪魁祸首,罪魁祸首(根本原因)都是他们的头头——线程。内部类引用就引用吧,无所谓,但是这个内部类是长期存在的就不行

unknown_filename.2

handle. post
view 中使用的 Context 就是当前的 Activity,而这个 runnable 一旦被 post,就会一直存在于队列里面,直到时间到了,被执行。
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler->view —> Activity

常见内存泄漏

非静态内部类持有外部类的引用

内部类的构造函数中会传入外部类的实例,然后就可以通过 this$0访问外部类的成员
this$0的意思就是所说的隐式持有外部 Activity 引用, 内部类可以访问外部类的成员变量, 靠的就是 this$0, 这个东西是编译器自动加上的, 不需要手动定义, 在反编译的 smali 文件中很容易看到~(当然如果有多层内部类的嵌套, 会有 this$1, this$2)

1
2
3
4
5
6
7
8
class SdkDialogFragment extends DialogFragment {  

private Dialog mDialog;

public SdkDialogFragment(Dialog dialog) {
mDialog = dialog;
}
}

非静态内部类改为静态内部类,如使用Application Context

匿名内部类持有外部类的引用

在 Activity 中使用非静态的内部类,并开启一个长时间运行的线程,因为内部类持有 Activity 的引用,会导致 Activity 长期得不到回收,例如 handler(使用静态内部类加上弱引用的方式实现),或者 mHandler.removeCallbacksAndMessages(null);

匿名内部类,例如:AsyncTask 和 Runnable ,那么它们将持有其所在 Activity 的隐式引用。如果任务在 Activity 销毁之前还未完成,那么将导致 Activity 的内存资源无法被回收,从而造成内存泄漏。(将 AsyncTask 和 Runnable 类独立出来或者使用静态内部类)

解决方法:静态+弱引用

  • 内部类 Handler 对象会隐式地持有一个外部类对象(通常是一个 Activity)的引用(不然你怎么可能通过 Handler 来操作 Activity 中的 View?)
  • PS: 在 Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。静态类不持有外部类的对象,所以你的 Activity 可以随意被回收。
  • 由于 Handler 不再持有外部类对象的引用,导致程序不允许你在 Handler 中操作 Activity 中的对象了。所以你需要在 Handler 中增加一个对 Activity 的弱引用(WeakReference)。
  • 使用弱引用注意判空

泄漏例子:在 ondestory 里 ref1没问题,ref2就内存泄漏, ref2是匿名内部类
unknown_filename.1

在反编译的时候经常会看到 xxxxx$1. class, xxxxxx$2. class, 这些就是匿名内部类, 经常的书写格式一般是 new xxxxxx () { 类的成员变量, 成员方法 }. xxxxx ();
其实我们经常用

1
2
3
4
5
6
7
leak_single.setOnClickListener (new View.OnClickListener () {
@Override
public void onClick (View view) {
Intent intent = new Intent (MainActivity. this, LeakSingleTestActivity. class);
startActivity (intent);
}
});

HandlerThread 泄露

1
2
3
4
5
6
7
8
9
10
11
HandlerThread thread = new HandlerThread("xxx");
thread.start();
Handler handler = new Handler(thread.getLooper());
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
view.setVisibility(View.GONE);
    }
}, 10000);

thread.quit();

在 HandlerThread 中还有一个方法: quitSafely, 简单提一下, 实际执行的是 removeAllFutureMessageLocked 方法, 只会清空 MessageQueue 消息池中所有的延迟消息, 并将所有非延迟消息派发出去让 Handler 处理, 相比于 quit, 更安全一些, 这个看需求吧, 一般 Activity 都退出了, 消息派不派发都没有实际意义了

单例

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。(使用 Application 的 context)

资源未关闭造成

BraodcastReceiver,ContentObserver,数据库Cursor,IO,Bitmap(close 或取消注册)、线程池 shutdown、handlerThread quick. 属性动画当设置成无限循环时,需要 cancel

listener 也要置为 null(getViewTreeObserver)

注册和取消注册成对出现,在对象合适的生命周期进行监听的注销

WebView 造成的泄露(当我们不要使用 WebView 对象时,应该调用它的 destory ()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露)。在客户端刚启动时,就初始化一个全局的 WebView 待用,并隐藏;当用户访问了 WebView 时,直接使用这个 WebView 加载对应网页,并展示。这种方法可以比较有效的减少初始化 WebView 的时间。

不要多余的成员变量或临时变量。成员变量(全局变量)。activity 持有成员变量的引用,比如说 arraylist,集合容器中的内存泄露(在退出程序之前,将集合里的东西 clear,然后置为 null,再退出程序。或者创建成局部变量 (短内存泄漏)在堆上,多了也占内存

生命周期

尽量不要用一个生命周期长于 Activity 的对象来持有 Activity 的引用

三种静态
  1. 静态内部类:尽量不要用一个生命周期长于Activity的对象来持有Activity的引用。声明handler为static,这样内部类就不再持有外部类的引用了,就不会阻塞Activity的释放。在Activity中尽量避免使用生命周期不受控制的非静态类型的内部类,可以使用静态内部类加上弱引用的方式实现。
  2. 静态变量:不要直接或者间接引用Activity、Service等。这会使用Activity以及它所引用的所有对象无法释放,然后,用户操作时间一长,内存就会狂升。
  3. 静态引用:应该避免 static 成员变量引用资源耗费过多的实例,比如 Context。尽量使用 getApplicationContext,因为 Application 的 Context 的生命周期比较长,引用它不会出现内存泄露的问题,而不是用 activity 的 context。可以通过调用 Context.getApplicationContext () or Activity.getApplication ()来获得

当某个 View 初始化时耗费大量资源, 而且要求 Activity 生命周期内保持不变, 这个时候很多业务组可能会吧 view 变成 static,加载到视图树上 (View Hierachy),像这样, 当 Activity 被销毁时,应当释放资源。但很可能会带来泄露问题, View 是跟 Context 紧密关联, 使用不当就会出现泄露问题, 需要特别注意.
我在使用 View 的时候没操作 Context? 怎么会有 Activity 的引用呢?

其实 View 的代码中是默认有 Context
Context 是什么时候给到 View 的呢? View 创建的时候, new View, 有的时候是我们写代码时自己 new, 更多的时候是 setContentView 时将 Activity 作为 context 传给 View

1
2
3
4
@Override  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

解决办法:
1: 通过设计改变 static 为普通变量, 不要在 Android 中使用 static 修饰 View, 完全避免此种可能, 推荐
2: 在 onDestroy 时将 static view 置为 null

静态方法

静态对象实例存在于堆中
静态方法在 stack,运行完就出栈了
静态成员变量在方法区
局部变量是不能静态的

类的静态成员变量(static成员变量)存储在方法区(Method Area)中,而非堆内存。静态成员变量独立于对象而存在,它们属于类本身而不是对象的一部分。

为什么 Java 静态方法引用的属性也必须是静态的

  • 静态方法不需要 new 对象,只要 class 文件被 ClassLoader   load 进入JVM 的 stack,该静态方法即可被调用。当然此时静态方法是存取不到 heap 中的对象属性的。  
  • 非静态方法执行前,要先new对象,在heap中分配数据,并把stack中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到heap数据区了。

和静态方法没关系,主要是静态变量持有activity的引用

1
2
3
4
5
6
7
public static Fragment newInstance(ArrayList<SpeakKey> data, int parentIndex) {
LearnSpeakScoreFragment fragment = new LearnSpeakScoreFragment();
Bundle bundle = new Bundle();
bundle.putInt("parentIndex", parentIndex);
fragment.setArguments(bundle);
return fragment;
}

不会内存泄漏,因为LearnSpeakScoreFragment又没持有activity的引用。方法运行完了就出栈了

标准的 handler 写法

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
// activity.mTextView.setText("");
}
}
}

//标准的单例
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context.getApplicationContext();// 使用Application 的context
}
public static AppManager getInstance(Context context) {
if (instance == null) {
instance = new AppManager(context);
}
return instance;
}

(特别对于 Context,View,Fragmet,Activity 对象),如果要将其放进类内部的容器对象或者静态类中引用,请一直用 WeakReference 包装!比如在 TabLayout 的源码中,在 TabLayoutOnPageChangeListener 中,就为 TabLayout 做了 WeakReference wrap。

unknown_filename

引用

对象的引用分为四种级别,从而使程序更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

  1. 强引用是Java的默认引用实现, 它会尽可能长时间的存活于 JVM(虚拟机) 内, 当没有任何对象指向它时(显式地将引用赋值为nul)才会在合适的时间,进行垃圾回收。
  2. 软引用 如果内存空间不足了,就会回收这些对象的内存。
  3. 弱引用 WeakReference  弱引用的对象拥有更短的生命周期,只要垃圾回收器扫描到它,不管内存空间充足与否,都会回收它的内存。
  4. 虚引用 PhantomReference  虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

其他

Android 系统中 GC 内存泄漏的原因

主动回收内存System.gc();、getruntime.runtime.gc
导致内存泄漏主要的原因是,申请了内存空间而忘记了释放。如果程序中存在对象的引用,这个对象就被定义为”有效的活动(引用可达)”,无法让垃圾回收器GC验证这些对象是否不再需要,这些对象就会驻留内存,消耗内存。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。

什么是 GC

GC垃圾收集器,它让创建的对象不需要像c/c++那样delete、free掉,GC的时间系统自身决定,时间不可预测。 对超出作用域的对象或引用置为空的对象进行清理,删除不使用的对象,腾出内存空间。

内存溢出和内存泄漏

内存泄露 memory leak,是指程序在申请内存后,忘了释放,就出现了内存泄漏,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!

内存溢出是要求分配的内存超出了系统能给的,系统不能满足需求,于是产生溢出。

Java 带垃圾回收的机制,为什么还会内存泄露呢?

举个例子 当你堆里某个对象没有被引用时,然后再过一段时间,垃圾回收机制才会回收,那么

1
whiletrue){String str=new String("ni hao ni hao ");}

一直循环创建 String对象。。。你觉得堆不会溢出嘛。。。

内存泄露的根本原因就是保存了不可能再被访问的变量类型的引用和回收的不确定性

GC Roots

GC Roots算法判定一个对象需要被回收,GC Roots一般在JVM的栈区域里产生。
![[6.JVM#GC Roots]]

具体例子

不使用 application 作为 context。当 context 为 application 时,会把 imageView 是生命周期延长到整个运行过程中,imageView 不能被回收,从而造成 OOM 异常。

((App) getApplication ()). leakviews.add (view);

1
2
3
4
public class App extends Application {
public ArrayList<Object> leakviews = new ArrayList<>();
}

在子线程调了其他类的方法,而其他类里用到了 context,是 activity 的 context,从而导致内存泄露

eventbus、webview(h5接口)、多进程
[[多进程Binder#内存泄露]]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
protected void onDestroy() {
if( mWebView!=null) {

// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}

mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();

}
super.on Destroy();
}

postDelayed,几分钟之后打点统计、或者做其他操作,都是不合理的~或者在Message中处理非常耗时的操作,最后造成消息堆积,无法得到及时处理,最后造成内存泄露
Runnable(Thread)泄露相对更容易理解,主要是异步线程持有外部Activity 的引用,而回调Activity onDestroy方法时,线程没有执行完成,导致内存泄露

handler. removeCallbacksAndMessages (null);
removeCallbacks (Runnable r) 和 removeMessages (int what)

1:在Activity的onDestroy执行时,Handler泄露可手动调用Handler的removeCallbacksAndMessages,清除异步消息,Runnable(Thread)泄露则可通过终止线程(控制逻辑需要自己写),切断引用链~推荐
2:将Handler,Runnable(Thread)定义为静态内部类,推荐,通过此方式,不会隐式持有外部Activity的引用
3:如果确实需要使用Activity做相关操作,建议使用弱引用,或者使用ApplicationContext,推荐
4:如果确实有耗时操作,建议使用jobschedule去做,推荐


1.Android内存优化
http://peiniwan.github.io/2025/12/860d5e680c4f.html
作者
六月的雨
发布于
2025年12月16日
许可协议