[Android] Multi-threaded programming, asynchronous message processing mechanism and new Handler() are marked as obsolete solutions to solve the Handler memory leak problem and the basic usage of AsyncTask

1. Android multi-thread programming

1. Asynchronous message processing mechanism

1.1 Weak quotation

WeakReference (weak reference) is a special reference type used in Java to manage references to objects. Its role is to allow the object to be recycled during the garbage collection process when there are no strong references pointing to it (When an object has only weak references pointing to it and no strong references pointing to it, garbage collection The server may recycle the object during the next garbage collection, even if the system memory is not tight.), thereby avoiding potential memory leaks.

In some cases, we may want an object to be garbage collected when there is no strong reference pointing to it to avoid potential memory leaks. **For example, consider the following situation:

  1. Caching: We may use cache to store certain objects, but when these objects are no longer used, we want them to be garbage collected and free up memory.
  2. Image loading: In Android development, we often load a large number of images. In order to avoid memory overflow, we may hope to release related resources in time when the images are no longer displayed.

WeakReferenceIs a special reference type that allows an object to be garbage collected when there is no strong reference pointing to it.

How to create a weak reference:

Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);

When we no longer need the object referenced by the weak reference, we can set the weak reference to null so that the object becomes no longer reachable, thus being recycled during the next garbage collection.

obj = null;

At this time, if there are no other strong references pointing toobj, it will become the object referenced by the weak reference. Because there is no strong reference pointing to it, the garbage collector may time to recycle this object.

Commonly used methods:

  • weakRef.get()

Weak reference objects are created through the WeakReference class, which provides a get() method to obtain the object referenced by the weak reference.

If the object has not been collected by the garbage collector, thenget() method will return a reference to the object; but if the object has been collected by the garbage collector, or when calling. method will return get() is recycled during the process, then the get()null

1.2 Strong references

When we create objects in Java and use variables to refer to these objects, we usually use strong references. Strong references allow an object to remain alive as long as there is at least one strong reference pointing to it. Even if the system memory is tight, the garbage collector will not reclaim these objects.

When we create an object and assign it to a variable, the variable becomes a strong reference.

As long as a strong reference exists, the garbage collector will not recycle the referenced object, even if the system memory is tight. Only when there is no strong reference to an object, the garbage collector will recycle the object and release the memory at the appropriate time.

Object obj = new Object(); // 这里obj是一个强引用

Even if obj is not used in the code, as long asobj the variable is in scope and has not been explicitly released (for example, set to null or out of scope), the object will still not be garbage collected< a i=7>. Only when there are no strong references pointing to this object can the garbage collector possibly reclaim it.

1.3 Use Handler to build instances

Before Android 10 we usually used this method to build Handler

Handler handler = new Handler(){
    
    
    public void handlerMessage(Messafe msg){
    
    
        
    }
}

But after it has been marked as obsolete, this code will get a warning when compiling: "Warning: Handler() in [deprecation] Handler is deprecated". Because the above code has the risk of memory leakage, how to leak memory and what is the principle?

When the Activity is destroyed, if the Handler used in it is held and remains active outside the life cycle of the object that externally refers to it, this may cause a memory leak. In this case, the external class where the Handler is located may not be recycled because it has a mutual reference relationship with the Handler.

To avoid memory leaks, you can take the following measures:

  • Use static inner classes or independent classes to implement Handler to avoid strong reference relationships with external classes.
  • Try to avoid holding references to external classes (Activity, etc.) in Handler, or cancel the sending of Message and Runnable in time when Handler is not needed.

In order to prevent memory leaks, the official requirement is to declare the handler as a static class. However, the memory leak problem has been solved, but it has caused another very difficult problem. Static classes cannot access external class members.

How to solve it? This problem is very simple. Just pass the external class object through the constructor.

What if the message is not processed yetactivity is destroyed, then what should we do?

At this time we need to use the **.get()** method of weak reference to sense whetheractivity still exists.

public class MainActivity extends AppCompatActivity {
    
    

    public static final int UPDATE_TEXT = 1;
    static ActivityMainBinding binding;
    private Handler handler = new MyHandler(Looper.myLooper(),this);

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        //触发按钮修改文本文字
        binding.button.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View view) {
    
    
                new Thread(new Runnable() {
    
    
                    @Override
                    public void run() {
    
    
                        Message message = new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });
    }

    static class MyHandler extends Handler{
    
    
        WeakReference<Activity> mainActivityWeakReference;

        //构造函数传入外部的this
        public MyHandler(@NonNull Looper looper, Activity mainActivity) {
    
    
            super(looper);
            //构建一个弱引用对象
            mainActivityWeakReference = new WeakReference<Activity>(mainActivity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
    
    
            super.handleMessage(msg);

            //判断活动是否还存在,如果不存在则结束
            Activity activity = mainActivityWeakReference.get();
            if (activity == null){
    
    
                return;
            }

            switch (msg.what){
    
    
                case UPDATE_TEXT:
                    binding.Text.setText("Nice to meet too");
            }
        }
    }
}

2. Analyze the asynchronous message processing mechanism

The asynchronous message processing mechanism in Android mainly consists of four parts: Message, Handler, MessageQueue, Looper

2.1 Message

transmits messages between threads and is used to exchange data between different threads, such as the usage of what in the above code.

2.2 Handler

Handler is also the handler, mainly used to send messages and process messages. Generally, the method of Handler is used to send a message. The sent message will eventually be passed to the Handler's method. We usually need to repeat Writemethod. setMessage()HanleMessage()HanlerMessage

2.3 MessageQueue

The message queue usually stores all messages sent through Handler. There is only one MessageQueue object in each thread.

2.4 Looper

Looper is the housekeeper of MessageQueue in each thread. Calling Looper's loop() method can enter an infinite loop. Whenever a message is found in MessageQueue, the message will be taken out and sent to the Handler's HandleMessage() method. Similarly There is also only one Looper object in each thread.

Looper.myLooper()Is a static method used to obtain the Looper object of the current thread.

2.5 Asynchronous message processing process

First, you need to create an internal class that statically inheritsHandler in the main thread and rewrite ithandleMessage() method. Then create an object of this inner classMyHandler.

Then when UI operations need to be performed in the child thread, a Message object is created and the message is sent through MyHandler.

Then the message will be added to the queue ofMessageQueue to wait for processing, and Looper will always try to take out the pending messages from MessageQueue, and finally distribute them back to the inner class < a i=7> in method. handleMessage()

SinceHandler is created in the main thread, the code in the handleMessage() method is also It will run in the main thread, so we can safely perform UI operations here.

image-20230801114843890

3. Use AsyncTask (use with caution)

With AsyncTask, even if you don't understand the asynchronous message processing mechanism, you can easily switch from the child thread to the main thread.

AsyncTask is an abstract method. We need to inherit it with a subclass, and when using it, we need to specify three generic parameters:

  • **Params:** Parameters that need to be passed in when executing AsyncTask
  • **Progress:** When a background task is executed, if you need to display the current progress on the interface, use the generic type here as the progress unit.
  • **Result:**The return value when the task ends.

For example:

class DownloadTask extends AsyncTask<Void,Integer,Boolean>{
    
    
}
  • The first generic parameter is specified as Void, which means that there is no need to pass parameters to the background task when executing AsyncTask.
  • The second generic parameter is specified as Integer, which means using integer data as the progress display unit
    .
  • The third generic parameter is specified as Boolean, which means using Boolean data to feedback the execution results.

Next you need to rewrite a few methods:

3.1 onPreExecute

This method will be called before the background task starts executing and is used to perform some initialization operations on the interface, such as displaying a progress bar dialog box, etc.

3.2 doInBackground()

**All code in this method will run in the child thread, where we should handle all time-consuming tasks. **Once the task is completed, the task execution result can be returned through the return statement. If the third generic parameter of AsyncTask specifies void, the task execution result does not need to be returned. Note that cannot perform UI operations in this method.

For example, to provide feedback on the execution progress of the current task, you can call the publishProgress(Progress...) method to complete.

3.3 OnProgressUpdate(Progress…)

When the publishProgress method is called in the background task, the onProgressUpdate method will be called soon.

The parameters carried in this method are passed in the background task. In this method, you can operate the UI, and use the values ​​in the parameters to update the interface elements accordingly.

3.4 onPostExecute(Result)

This method is called when the background task ends and returns through the retrun statement. The returned data will be passed to this method as a parameter, and UI operations can be performed based on the data.

3.5 Implement custom AsyncTask

image-20230801162447918

First use AlertDialog and ProgressBar to implement a suspended loading box.

class MyAskncTask extends AsyncTask<Void,Integer,Boolean> {
    
    

    private AlertDialog alertDialog;
    int downloadPercent = 0;

    @Override
    protected void onPreExecute() {
    
    
        super.onPreExecute();
        MainActivity.this.runOnUiThread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setView(R.layout.progressbar);
                alertDialog = builder.create();
                alertDialog.setMessage("Downloaded: "+0+"%");
                alertDialog.show();
            }
        });
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
    
    
        try {
    
    
            while (true){
    
    
                Thread.currentThread().join(100);
                downloadPercent+= 1;
                //传递下载进度
                publishProgress(downloadPercent);
                if(downloadPercent>100){
    
    
                    break;
                }
            }
        }catch (Exception e){
    
    
            return false;
        }
        return true;
    }


    @Override
    protected void onPostExecute(Boolean aBoolean) {
    
    
        alertDialog.dismiss();
    }

    @Override
    protected void onProgressUpdate(Integer... values) {
    
    
        alertDialog.setMessage("Downloaded"+values[0]+"%");
    }
}

Implement background running loading. When the button is clicked again, the page loaded in the background will be entered:

binding.button.setOnClickListener(new View.OnClickListener() {
    
    
    @Override
    public void onClick(View view) {
    
    
        runOnUiThread(new Runnable() {
    
    
            final MyAskncTask myAskncTask = new MyAskncTask(); // 在这里赋值
            @Override
            public void run() {
    
    
                //判断是否正在运行
                if ( myAskncTask.getStatus() == AsyncTask.Status.RUNNING) {
    
    
                    if (alertDialog != null) {
    
    
                        //如果正在运行重新显示弹窗
                        alertDialog.show();
                    }
                } else {
    
    
                    myAskncTask.execute();
                }
            }
        });
    }
});
  • PENDING: The task has been created but has not been executed yet.
  • RUNNING: The task is running and its doInBackground() method is executing.
  • FINISHED: The task has been executed. Once a task has finished running, it cannot be executed again.

Guess you like

Origin blog.csdn.net/m0_72983118/article/details/132062336