Android memory optimization, how to avoid OOM

1. Android's memory mechanism
2. Android's memory overflow
3. Evil static
4. It's all the thread's fault
5. Super fat Bitmap
6. Cursor with a strange
whereabouts
1. Android's memory mechanism
Android's program is written in Java language, so Android's memory management is similar to Java's memory management. Programmers allocate memory for objects through new, and all objects allocate space in the Java heap; however, the release of objects is done by the garbage collector. The memory mechanism in C/C++ is "whoever pollutes, whoever controls it", Java is more humanized, and we have hired a special cleaner (GC).

So how can the GC confirm whether an object has been abandoned? Java uses the principle of directed graphs. Java considers reference relationships as directed edges of a graph, directed edges point from the referrer to the referenced object. The thread object can be used as the starting vertex of a directed graph. The graph is a tree starting from the starting vertex. The objects that can be reached by the root vertex are valid objects, and GC will not recycle these objects. If an object (connected subgraph) is unreachable with this root vertex (note that the graph is a directed graph), then we consider that (these) objects are no longer referenced and can be recycled by GC.

2. Android
memory overflow How does Android memory overflow occur? The

Android virtual machine is a register-based Dalvik, and its maximum heap size is generally 16M, and some machines are 24M. Therefore, the memory space we can use is limited. If our memory usage exceeds a certain level, an OutOfMemory error occurs.

Why is there not enough memory? I think there are two main reasons:

Due to the mistakes of our program, the reference of some resources (such as Context) is kept for a long time, resulting in memory leaks and the resources cannot be released.
Multiple objects (such as Bitmap) that consume too much memory are saved, causing the memory to exceed the limit.

3. The evil static
static is a keyword in Java. When it is used to modify a member variable, then the variable belongs to the class, not an instance of the class. Therefore, a variable modified with static has a long life cycle. If you use it to refer to some instances that consume too much resources (the case of Context is the most), you must be careful at this time.

public class ClassName {
    private static Context mContext;
    //Omit
}
The above code is very dangerous, if you assign Activity to mContext. So even if the Activity has been onDestroy, the Activity will not be released because there are still objects holding its references.

Let's take an example from the Android documentation.

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);

    TextView label = new TextView(this);
    label.setText("Leaks are bad");

    if (sBackground == null) {
        sBackground = getDrawable(R.drawable.large_bitmap);
    }
    label.setBackgroundDrawable(sBackground);

    setContentView(label);
}
sBackground, is a static variable, but we found that we did not explicitly However, when the Drawable is connected to the View, the Drawable will set the View as a callback. Since the View contains the reference of the Context, in fact, we still save the reference of the Context. The reference chain is as follows:

Drawable->TextView->Context
Therefore, the Context is not released in the end, and a memory leak occurs.

How can we effectively avoid this kind of reference from happening?

First, you should try to avoid static member variables that refer to instances that consume too much resources, such as Context.

Second, Context should use Application Context as much as possible, because the life cycle of Application Context is relatively long, and there will be no memory leak problem when referring to it.

Third, use WeakReference instead of strong references. For example, you can use WeakReference mContextRef;

the details of this section can also refer to the Article section in the Android documentation.

Fourth, it is all the fault of the thread
Threads are also an important source of memory leaks. The main reason for thread memory leaks is the uncontrollable thread life cycle. Let's consider the following piece of code.

public class MyActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        new MyThread().start();
    }

    private class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            //do somthing
        }
    }
}
This code is very common and simple, and it is the form we often use. Let's think about a problem: Assuming that the run function of MyThread is a very time-consuming operation, when we open the thread, the horizontal screen of the device is changed to a vertical screen. Generally, the Activity will be re-created when the screen is converted, according to our ideas. , the old Activity should be destroyed, but it is not.

Since our thread is an inner class of Activity, a reference to Activity is saved in MyThread. When MyThread's run function does not end, MyThread will not be destroyed, so the old Activity it references will not be destroyed. , so there is a memory leak problem.

Some people like to use the AsyncTask provided by Android, but in fact, the problem of AsyncTask is more serious. Thread only has this memory leak problem when the run function does not end. However, the internal implementation mechanism of AsyncTask is to use ThreadPoolExcutor, the Thread generated by this class. The life cycle of the object is uncertain and cannot be controlled by the application, so if AsyncTask is used as an inner class of Activity, it is more prone to memory leaks.

How to solve the memory leak problem caused by this thread?

First, change the inner class of the thread to a static inner class.

Second, use a weak reference to save the Context reference inside the thread.

The resolved model is as follows:

public abstract class WeakAsyncTaskProgress, Result, WeakTarget> extends
AsyncTaskProgress, Result> {
    protected WeakReference mTarget;

    public WeakAsyncTask(WeakTarget target) {
        mTarget = new WeakReference(target);
    }

    @Override
    protected final void onPreExecute() {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPreExecute(target);
        }
    }

    @Override
    protected final Result doInBackground(Params… params) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            return this.doInBackground(target, params);
        } else {
            return null;
        }
    }

    @Override
    protected final void onPostExecute(Result result) {
        final WeakTarget target = mTarget.get();
        if (target != null) {
            this.onPostExecute(target, result);
        }
    }

    protected void onPreExecute(WeakTarget target) {
        // No default action
    }

    protected abstract Result doInBackground(WeakTarget target, Params… params);

    protected void onPostExecute(WeakTarget target, Result result) {
        / / No default action
    }
}
In fact, the problem with threads is not just memory leaks, but also some catastrophic problems. Since this article discusses memory issues, it will not be discussed here.

Since 51cto didn't let me pass it all at once, saying that I had too many words, I passed it separately.

5. Super Fatty Bitmap
It can be said that the vast majority of people who have OutOfMemory problems are because of Bitmap problems. Because Bitmap occupies too much memory, it is a "super fat man", especially for pictures with large resolution. If you want to display multiple pictures, the problem is even more significant.

How to solve the memory problem that Bitmap brings us?

First, timely destruction.

Although, the system can confirm that the memory allocated by Bitmap will eventually be destroyed, but because it occupies too much memory, it is likely to exceed the limit of the java heap. Therefore, when you run out of Bitmap, you should recycle it in time. Recycle does not guarantee that the Bitmap will be released immediately, but it will give the virtual machine a hint: "The picture can be released".

Second, set a certain sampling rate.

Sometimes, the area we want to display is very small, and it is not necessary to load the entire image, but only need to record a reduced image. At this time, a certain sampling rate can be set, which can greatly reduce the memory occupied. Such as the following code:

private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; //The width and height of the picture are half of the original, that is, the picture is a quarter of the original
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);
Third, clever use of soft references (SoftRefrence)

Sometimes, we do not keep a reference to Bitmap after using it , so the Recycle function cannot be called. At this time, the clever use of soft references can effectively release the Bitmap when the memory is running low. For example:

private class MyAdapter extends BaseAdapter {

    private ArrayList> mBitmapRefs = new ArrayList>();
    private ArrayList mValues;
    private Context mContext;
    private LayoutInflater mInflater;

    MyAdapter(Context context, ArrayList values) {
        mContext = context;
        mValues = values;
        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    public int getCount() {
        return mValues.size();
    }

    public Object getItem(int i) {
        return mValues.get(i);
    }

    public long getItemId(int i) {
        return i;
    }

    public View getView(int i, View view, ViewGroup viewGroup) {
        View newView = null;
        if(view != null) {
            newView = view;
        } else {
            newView =(View)mInflater.inflate(R.layout.image_view, false );
        }

        Bitmap bitmap = BitmapFactory.decodeFile(mValues.get(i).fileName);
        mBitmapRefs.add(new SoftReference(bitmap)); //Add ArrayList here
        ((ImageView)newView).setImageBitmap(bitmap);

        return newView;
    }
}
6. Cursor with weird whereabouts
Cursor is a class that manages data collections obtained by Android after querying data. Under normal circumstances, if the amount of data obtained from the query is small, there will be no memory problems, and the virtual machine can ensure that the Cusor will eventually will be released.

However, if the data volume of the Cursor is very large, especially if there is Blob information in it, it should be ensured that the memory occupied by the Cursor is released in time instead of waiting for the GC to process it. And Android obviously prefers programmers to close Cursor manually, because we found in the source code that if we wait for the garbage collector to collect, it will give the user an error prompt.

So the way we use Cursor is generally as follows:

Cursor cursor = null;
try {
    cursor = mContext.getContentResolver().query(uri,null, null,null,null);
    if(cursor != null) {
        cursor.moveToFirst() ;
        //do something
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    if (cursor != null) {
        cursor.close();
    }
}
There is a case where we cannot close Cursor directly , this is the case in the CursorAdapter, but note that the CursorAdapter does not automatically close the Cursor when the Activity ends, so you need to manually close it in the onDestroy function.

@Override
protected void onDestroy() {
    if (mAdapter != null && mAdapter.getCurosr() != null) {
        mAdapter.getCursor().close();
    }
    super.onDestroy();
}
The changeCursor function in CursorAdapter will change the original The Cursor is released and replaced with a new Cursor, so you don't have to worry about the original Cursor not being closed.
You may think of using the Activity's managedQuery to generate the Cursor, so that the Cursor will be consistent with the Acitivity's life cycle, what a perfect solution! However, in fact managedQuery also has great limitations.
The Cursor generated by managedQuery must be guaranteed not to be replaced, because many programs may actually query conditions are uncertain, so we often replace the original Cursor with the new query Cursor. Therefore, the scope of application of this method is also very small.

Seven, other things to say.
In fact, to reduce the use of memory, there are actually many methods and requirements. For example, instead of using the entire image, try to use 9path images. Adapter needs to use convertView, etc., many details can save memory. These all need us to dig, who said that Android's memory is not powerful.

Welcome to reprint: http://www.yinqisen.cn/blog-316.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326520679&siteId=291194637