<include layout = "@layout/titlebar">
ll_include
则是当前layout/include_main_test
布局所对应的根元素的id。 <ViewStub
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/include_main_test"
android:inflatedId="@id/ll_include"
android:id="@+id/stud_import"
/>
内存泄漏:程序申请的内存,在使用后无法释放,是导致OOM的主要原因。
内存泄漏的实质:无意识地持有对象引用,使得 持有引用者的生命周期 > 被引用者的生命周期
内存溢出:程序在申请内存时,没有足够的内存够其使用。
进程的内存管理:(内存分配与内存回收)
通过AMS对进程进行内存分配;由Framework决定回收的进程类型,当内存紧缺时,会按照优先级的顺序对进程进行回收。Linux 内核真正回收具体进程。
对象的内存管理与Java虚拟机类似。
存储包括:
方法区中存放已被加载的类信息,以及常量与静态变量。
栈帧存放方法执行时的局部变量,包括数据类型与对象引用。
而对象的实例存于堆中。实例包括对象以及对象所在类的成员变量等等。
释放是对堆中进行GC操作。
被 Static 关键字修饰的成员变量的生命周期 = 应用程序的生命周期。
当前情况下context(成员变量)的生命周期为应用程序的生命周期大于Activity(引用实例)的生命周期,因此Activity在回收时,由于Context对Activity的持有,因此Activity无法回收。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//内存泄漏
private static Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
}
}
解决方法:
尽量避免静态成员变量引用资源耗费过多的实例(如 Context)如果需要,可以使用Applaction的Context
context.getApplicationContext()
我们需要通过context来创建一个单例的对象(静态对象,因为是单例模式),但我们不能直接使用context,而应该通过context.getApplicationContext()
来获取。(不能让单例的对象一直引用Activity,这样当Activity准备销毁时,也会因为被单例对象引用而无法销毁,导致内存泄漏)
public class SingleInstanceClass {
private static SingleInstanceClass instance;
private Context mContext;
private SingleInstanceClass(Context context) {
this.mContext = context.getApplicationContext(); // 传递的是Application 的context
}
public static SingleInstanceClass getInstance(Context context) {
if (instance == null) {
instance = new SingleInstanceClass(context);
}
return instance;
}
}
集合类添加元素后,会对 对象具有引用,即使已经将对象置为null,也无法回收。
// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 虽释放了集合元素引用的本身:o=null)
// 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
解决方法:在不使用时,必须使用clear
方法将所有对象删除,并将集合类对象置为null。
// 释放objectList
objectList.clear();
objectList=null;
非静态内部类默认持有外部类的引用,而静态内部类则不会。
而造成内存泄漏的原因是创建了一个非静态内部类的静态对象。则该对象的生命周期=应用的生命周期,因此会在外部类对象销毁的时候,仍然保留着对外部类的引用,则外部类的实例无法被GC。
public class TestActivity extends AppCompatActivity {
// 非静态内部类的实例的引用
// 注:设置为静态
public static InnerClass innerClass = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 保证非静态内部类的实例只有1个
if (innerClass == null)
innerClass = new InnerClass();
}
// 非静态内部类的定义
private class InnerClass {
//...
}
}
解决方法:将内部类改成静态内部类,这样就不会有外部类的引用。
以Thread为例,我们需要创建一个匿名内部类Thread并实现Runnable接口。
当工作线程正在处理任务且外部类需销毁时, 由于工作线程的实例持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成内存泄露。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
}
}).start();
group.setVisibility(View.GONE);
}
}
解决方法是可以使用静态内部类(Thread)
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyThread().start();
}
private static class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
或者在外部类的回收时,强制关闭线程
@Override
protected void onDestroy() {
super.onDestroy();
Thread.stop();
// 外部类Activity生命周期结束时,强制结束线程
}
在匿名内部类时,会默认持有外部的引用,也就是说Handler会持有外部类Activity的引用。
当Handler消息队列 还有未处理的消息 / 正在处理消息时,存在引用关系: “未被处理 / 正处理的消息 -> Handler实例 -> 外部类”(Looper的生命周期与Application相同)
若出现 Handler的生命周期 > 外部类的生命周期 时(即 Handler消息队列 还有未处理的消息 / 正在处理消息 而 外部类需销毁时),将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露。
解决方法:静态内部类+弱引用
静态内部类不持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 的引用关系 不复存在。而弱引用不论内存是否够用,在GC时都会被回收。
实例如下:
// 分析1:自定义Handler子类
// 设置为:静态内部类
private static class FHandler extends Handler{
// 定义 弱引用实例
private WeakReference<Activity> reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity); }
@Override
public void handleMessage(Message msg) {
...
}
}
}
未及时注销资源导致内存泄漏,如BraodcastReceiver、File、Cursor、Stream、Bitmap等。
解决办法:在Activity销毁的时候要及时关闭或者注销。
BraodcastReceiver:调用unregisterReceiver()
注销;
Cursor,Stream、File:调用close()
关闭;
Bitmap:调用recycle()
释放内存(2.3版本后无需手动)
属性动画:需要在onDestory中调用Animator.cancel
方法来停止无限循环动画。
不使用缓存,而一直在getView中重新实例化Item。
解决方法:使用ContertView和ViewHolder,防止多次实例化Item与Item中的子控件。
主要体现在getView的优化:
Integer) v.getTag()
知道当前点击的是哪个子项。 @Override
public View getView(int position, View convertView, ViewGroup parent){
ViewHolder viewHolder;
if (convertView==null) {
//convertView=View.inflate(mContext, R.layout.series_of_courses_item,null);
convertView= LayoutInflater.from(mContext).inflate(R.layout.series_of_courses_item,parent,false);
ViewHolder holder=new ViewHolder();
holder.course_title=convertView.findViewById(R.id.soc_course_title);
holder.course_time=convertView.findViewById(R.id.soc_course_time);
holder.enter_class=convertView.findViewById(R.id.enter_class);
holder.btn_son_course_delete = convertView.findViewById(R.id.btn_son_course_delete);
convertView.setTag(holder);
}
viewHolder=(ViewHolder)convertView.getTag();
final SeriesCourseModel seriesCourses=data.get(position);
//Log.d("SeriesCoursesAdapter", "getView: " + courses.getCourseName());
viewHolder.course_title.setText(seriesCourses.getName());
//int minutes= (int) LangUtils.parseLong(seriesCourses.getCourseTime(),0)/60;
viewHolder.course_time.setText(seriesCourses.getCourseTime());
//注册Item控件点击事件,首先在Activity中要设置监听器
if (onClickListener != null){
viewHolder.enter_class.setOnClickListener(onClickListener);
viewHolder.enter_class.setTag(position);
viewHolder.btn_son_course_delete.setOnClickListener(onClickListener);
viewHolder.btn_son_course_delete.setTag(position);
}
//type=1就显示那个button
if (type==1) {
viewHolder.enter_class.setVisibility(View.VISIBLE);
}
else viewHolder.enter_class.setVisibility(View.INVISIBLE);
return convertView;
}
class ViewHolder{
TextView course_title;
TextView course_time;
Button enter_class;
ImageButton btn_son_course_delete;
}
//向XListView添加数据项
public void addItems(SeriesCourseModel seriesCourseModel){
this.data.add(seriesCourseModel);
notifyDataSetChanged();
}
//向XListView删除数据项 注意添加了headView
public void deleteItems(int position){
this.data.remove(position);
notifyDataSetChanged();
}
decodeResource(res,resId,options)
,对应outWidth和outHeight参数。decodeResource(res,resId,options)
。 /**
* 对一个Resources的资源文件进行指定长宽来加载进内存, 并把这个bitmap对象返回
*
* @param res 资源文件对象
* @param resId 要操作的图片id
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样之后的bitmap对象
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight){
BitmapFactory.Options options = new BitmapFactory.Options();
//1.设置inJustDecodeBounds=true获取图片尺寸
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res,resId,options);
//3.计算缩放比
options.inSampleSize = calculateInSampleSize(options,reqHeight,reqWidth);
//4.再设为false,重新从资源文件中加载图片
options.inJustDecodeBounds =false;
return BitmapFactory.decodeResource(res,resId,options);
}
/**
* 一个计算工具类的方法, 传入图片的属性对象和想要实现的目标宽高. 通过计算得到采样值
* @param options 要操作的原始图片属性
* @param reqWidth 最终想要得到bitmap的宽度
* @param reqHeight 最终想要得到bitmap的高度
* @return 返回采样率
*/
private static int calculateInSampleSize(BitmapFactory.Options options, int reqHeight, int reqWidth) {
//2.height、width为图片的原始宽高
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if(height>reqHeight||width>reqWidth){
int halfHeight = height/2;
int halfWidth = width/2;
//计算缩放比,是2的指数
while((halfHeight/inSampleSize)>=reqHeight&&(halfWidth/inSampleSize)>=reqWidth){
inSampleSize*=2;
}
}
return inSampleSize;
}
当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。
LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
常用属性accessOrder:决定LinkedHashMap的链表顺序。值为true:以访问顺序维护链表。值为false:以插入的顺序维护链表。
而LruCache利用是accessOrder=true
时的LinkedHashMap实现LRU算法,使得最近访问的数据会在链表尾部,在容量溢出时,将链表头部的数据移除。
使用方法:
计算当前可用的内存大小;
分配LruCache缓存容量;
创建LruCache对象并传入最大缓存大小的参数、重写sizeOf()用于计算每个缓存对象的大小;
通过put()、get()和remove()实现数据的添加、获取和删除
lruCache = new LruCache<String, Bitmap>(maxSize)
{
@Override
protected int sizeOf(String key, Bitmap value) {
// TODO Auto-generated method stub
return value.getWidth() * value.getHeight() / 1024;
}
};
使用线程池,避免创建大量的Thread。Android线程与线程池《开发艺术》探索
避免在主线程中做耗时操作。Activity规定如果在5s之内无法响应点击事件或键盘输入事件就会ANR,如果10s内BroadCastReciver还没有完成执行逻辑就会ANR。当发生ANR后可以通过traces文件定位。
List>list = new ArrayList<>();