Android如何理解和使用线程与进程(异步消息处理)
我们都知道,在操作系统中进程是OS分配资源的最小单位,而线程是执行任务的最小单位。一个进程可以拥有多个线程执行任务,这些线程可以共享该进程分配到的资源。当我们的app启动运行后,在该app没有其他组件正在运行的前提下,Android系统会启动一个新Linux进程来运行app,这个进程只包含了一个线程在运行。在默认情况下,app的组件都运行在该进程中,最初就包含的这个线程也被称为主线程或者UI线程。如果我们启动该app的时候,系统中已经有一个进程在运行该app的组件,那么该app也会在该进程中运行。当然,我们也可以让app中不同的组件运行在不同的进程中,也可以在任意进程中新开线程执行任务。
进程
默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不应改变这一点。但是,如果我们发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。
各类组件元素(< activity >、< service >、< receiver > 和 < provider >)的清单文件条目均支持 android:process 属性,此属性可指定该组件应在哪个进程中运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使某些组件共享一个进程,而其他组件则不共享。您也可设置 android:process,以便不同应用的组件在同一进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。
此外, 元素还支持 android:process 属性,用来设置适用于所有组件的默认值。
当内存不足,而其他更急于为用户提供服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某个进程。正因如此,系统会销毁在被终止进程中运行的应用组件。当这些组件需再次运行时,系统将为其重启进程。
决定终止哪个进程时,Android 系统会权衡其对用户的相对重要性。例如,相较于托管可见 Activity 的进程而言,系统更有可能关闭托管屏幕上不再可见的 Activity 的进程。因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。Android 会根据每个进程中运行的组件以及这些组件的状态,将它们放入“重要性层次结构”。这些进程类型包括(按重要性排序):
重要性层次结构
-
前台进程
正在与用户进行交互的进程,当以下任何一个条件成立时,该进程被认为是前台进程:
- 持有一个用户正在与之交互的 Activity(Activity 对象的 onResume() 方法已被调用)。
- 持有一个服务(Service),且该服务已被绑定到一个正在与用户交互的 Activity 上。
- 持有一个服务,且该服务在前台运行,即该服务 startForground() 调用。
- 持有一个服务,且该服务正在执行其生命周期的回调方法(onCreate()、onStart()、onDestroy())。
-
持有一个 BroadcastReceiver,且其正在执行 onReceive() 方法。
通常,在一个给定的时间内,只有很少的前台进程存在。当系统内存匮乏,以至于它们不能全部继续运行时,它们会依序被清除。通常,这时设备已经到了内存分页状态(memory paging state),来清除那些前台进程以确保用户响应。
-
可见进程
一个可视进程是没有前台组件的,但仍会影响用户在屏幕上所见内容的进程。当以下任何一个条件成立时,该进程被认为是可视进程:
- 它正在用户的互动屏幕上运行一个 Activity(其 onResume() 方法已被调用)。
- 它有一个 BroadcastReceiver 目前正在运行(其 BroadcastReceiver.onReceive() 方法正在执行)。
-
它有一个 Service 目前正在执行其某个回调(Service.onCreate()、Service.onStart() 或 Service.onDestroy())中的代码。
相比前台进程,系统中运行的这些进程数量较不受限制,但仍相对受控。这些进程被认为非常重要,除非系统为了使所有前台进程保持运行而需要终止它们,否则不会这么做
-
服务进程
包含一个已使用 startService() 方法启动的 Service。虽然用户无法直接看到这些进程,但它们通常正在执行用户关心的任务(例如后台网络数据上传或下载),因此系统会始终使此类进程保持运行,除非没有足够的内存来保留所有前台和可见进程。 -
缓存进程
目前不需要的进程,因此,如果其他地方需要内存,系统可以根据需要*地终止该进程。在正常运行的系统中,这些是内存管理中涉及的唯一进程:运行良好的系统将始终有多个缓存进程可用(为了更高效地切换应用),并根据需要定期终止最早的进程。只有在非常危急(且具有不良影响)的情况下,系统中的所有缓存进程才会被终止,此时系统必须开始终止服务进程。
这些进程通常包含用户当前不可见的一个或多个 Activity 实例(onStop() 方法已被调用并返回)。只要它们正确实现其 Activity 生命周期(详情请见 Activity),那么当系统终止此类流程时,就不会影响用户返回该应用时的体验,因为当关联的 Activity 在新的进程中重新创建时,它可以恢复之前保存的状态。
线程
当我们需要执行一些耗时操作,比如说发起网络请求时,考虑到网速等其他原因,服务器未必会立刻响应我们的请求,如果不将这类操作放在子线程里去运行,就会导致主线程被阻塞住,从而影响用户对软件的正常使用
系统不会为每个组件实例创建单独的线程。在同一进程中运行的所有组件均在界面线程中进行实例化,并且对每个组件的系统调用均由该线程进行分派。
线程的基本用法
-
新建线程类
新建一个类,继承于Thread,重写父类的run()方法
// 新建MyThread继承于Thread class MyThread extends Thread { @Override public void run(){ // 事务逻辑 } } // 在主线程里new出实例,调用start方法 new MyThread().start();
- 实现Runnable接口
// 创建一个实现了Runnable接口的类 class MyThread implements Runnable { @Override public void run(){ // 事务逻辑 } } // 在主线程里new出实现了Runnable接口的对象,传给Thread的构造函数中 MyThread myThread = new MyThread(); new Thread(myThread).start();
- 匿名类
new Thread(new Runnable(){ @Override public void run(){ // 事务逻辑 } })
更新UI
Android的UI是不安全的,我们只能在主线程中更新应用程序中的UI
Android 界面工具包并非线程安全工具包。所以您不得通过工作线程操纵界面,而只能通过界面线程操纵界面。因此,Android 的单线程模式必须遵守两条规则:
- 不要阻塞 UI 线程
- 不要在 UI 线程之外访问 Android UI 工具包
线程安全方法
在某些情况下,系统可能会从多个线程调用您实现的方法,因此编写这些方法时必须确保其满足线程安全的要求。
这一点主要适用于可以远程调用的方法,如绑定服务中的方法。如果对 IBinder 中所实现方法的调用源自运行 IBinder 的同一进程,则系统会在调用方的线程中执行该方法。但是,如果调用源自其他进程,则系统会选择线程池中的某个线程,并在此线程中(而不是在进程的界面线程中)执行该方法,线程池由系统在与 IBinder 相同的进程中进行维护。例如,即使服务的 onBind() 方法通过服务进程的界面线程调用,在 onBind() 所返回对象中实现的方法(例如,实现 RPC 方法的子类)仍会通过线程池中的线程调用。由于服务可以有多个客户端,因此多个池线程可同时使用相同的 IBinder 方法。因此,IBinder 方法必须实现为线程安全方法。
异步消息处理
Handler
-
Message
Message是在线程之间传递的消息,它可以携带少量的信息,用于在不同线程之间交换数据 -
Handler
Handler主要用于发送和处理数据
使用Handler的sendMessage()方法,通过处理之后,最终会传递到Handler的handleMessage()中 -
MessageQueue
MessageQueue消息队列,主要用于存放所有通过Handler发送的消息。这一部分消息会一直存放于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象 -
Looper
Looper是每个线程中MessageQueue的管家,调用Looper的loop()方法后,就会进入无限循环,每当发现MessageQueue中有消息时,就将它取出,传递到Handler的handleMessage()中
异步流程:
- 主线程中创建一个Handler对象,并重写handleMessage()方法
- 子线程创建Message对象,通过Handler将这条消息发送出去
- MessageQueue队列中收到该Message
-
Looper一直尝试从MessageQueue中取出数据并发送到handleMessage()方法中
大佬总结的Handler工作原理
Handler的例子:
MainActivity:
package com.example.handlertest; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final int CREATE_THREAD = 1; public static final int SEND_MESSAGE = 2; public static final int CALL_THREAD_TO_SEND_MESSAGE = 3; public static final int GET_MESSAGE = 4; private MyThread myThread; private Handler handler = new Handler() { public void handleMessage(Message msg){ Log.d(TAG, msg.what + ""); switch (msg.what){ case GET_MESSAGE: Log.d(TAG, "主线程接受消息成功"); break; default: break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button createButton = (Button) findViewById(R.id.create_thread); Button sendButton = (Button) findViewById(R.id.to_thread); Button getButton = (Button) findViewById(R.id.from_thread); createButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { createThread(); } }); sendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { sendMessageToThread(); } }); getButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { getMessageFromThread(); } }); } // 新建一个子线程 private void createThread(){ myThread = new MyThread(); myThread.start(); } // 向子线程发送一条消息 private void sendMessageToThread(){ Log.d(TAG, "向子线程发送一条消息"); Message message = new Message(); message.what = SEND_MESSAGE; myThread.handler.sendMessage(message); } // 子线程向父线程发送消息 private void getMessageFromThread(){ Message message = new Message(); message.what = CALL_THREAD_TO_SEND_MESSAGE; Bundle bundle = new Bundle(); ArrayList arr = new ArrayList(); arr.add(handler); bundle.putStringArrayList("data", arr); message.setData(bundle); myThread.handler.sendMessage(message); } } }
MyThread:
package com.example.handlertest; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; public class MyThread extends Thread { private static final String TAG = "MyThread"; public static final int CREATE_THREAD = 1; public static final int SEND_MESSAGE = 2; public static final int CALL_THREAD_TO_SEND_MESSAGE = 3; public static final int GET_MESSAGE = 4; public Handler handler = new Handler() { public void handleMessage(Message msg) { Log.d(TAG, msg.what + ""); switch (msg.what){ case CREATE_THREAD: Log.d(TAG, "创建线程成功"); break; case SEND_MESSAGE: Log.d(TAG, "主线程发送消息成功"); break; case CALL_THREAD_TO_SEND_MESSAGE: Log.d(TAG, "子线程发送消息成功"); // 获取数据 ArrayList data = msg.getData().getParcelableArrayList("data"); Handler mainHandler = (Handler) data.get(0); Message message = new Message(); message.what = GET_MESSAGE; mainHandler.sendMessage(message); break; default: break; } } }; @Override public void run() { } }
XML
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/create_thread" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="新建线程" /> <Button android:id="@+id/to_thread" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="向子线程发消息" /> <Button android:id="@+id/from_thread" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="子线程向主线程发送消息" /> </LinearLayout>
子线程向主线程发消息的方式:
- 创建子线程时,主线程Handler以参数形式在子线程构造函数中定义
public class MyThread extends Thread { public MyThread(Handler handler){ mainHandler = handler; } @Override public void run() { Message message = new Message(); message.what = GET_MESSAGE; mainHandler.sendMessage(message); } }
- 子线程获取主线程Looper,直接定义主线程需要完成的操作:
public class MyThread extends Thread { @Override public void run() { Handler Xhandler = new Handler(Looper.getMainLooper()){ @Override public void handleMessage(@NonNull Message msg) { Log.d(TAG, "11111"); } }; Message message = new Message(); mainHandler.sendMessage(message); }
-
子线程构造Handler,主线程通过Message传递主线程的Handler。也就是前面的例子,
第一次写的时候脑抽了,傻兮兮的整得很复杂,其实一个构造函数就行了
AsyncTask
实现原理也是基于异步消息处理机制
在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExcute()方法中执行一些任务的收尾工作
总的来说,AsyncTask是对Thread+Handler良好的封装
-
execute(Params… params)
执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行 -
onPreExecute()
后台任务开始执行前调用,用于进行一些界面上的初始化操作 -
doInBackground()
此方法中所有代码都会在子线程中运行,应在此方法中执行所有的耗时任务。如果要反馈当前任务的执行进度,可以调用publiProgress(Progress…)方法来完成 -
onProgressUpdate(Progress)
当后台任务调用publiProgress(Progress…)时,此方法很快被调用,在此方法中可以对UI进行更新 -
onPostExecute(Result)
后台任务完成并通过return语句进行返回时,此方法执行
在使用的时候,有几点需要格外注意:
- 异步任务的实例必须在UI线程中创建。
- execute(Params… params)方法必须在UI线程中调用。
- 不要手动调用onPreExecute(),doInBackground(Params…params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。
- 不能在doInBackground(Params… params)中更改UI组件的信息。
- 一个任务实例只能执行一次,如果执行第二次将会抛出异常。
让我们用一个实际例子来学习AsyncTask,该代码修改于另一个大佬的代码
package com.example.asynctasktest; import androidx.appcompat.app.AppCompatActivity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private static final String TAG = "ASYNC_TASK"; private Button execute; private Button cancel; private ProgressBar progressBar; private TextView textView; private MyTask mTask; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 组件绑定 execute = (Button) findViewById(R.id.execute); execute.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //注意每次需new一个实例,新建的任务只能执行一次,否则会出现异常 mTask = new MyTask(); mTask.execute(); execute.setEnabled(false); cancel.setEnabled(true); } }); cancel = (Button) findViewById(R.id.cancel); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //取消一个正在执行的任务,onCancelled方法将会被调用 mTask.cancel(true); } }); progressBar = (ProgressBar) findViewById(R.id.progress_bar); textView = (TextView) findViewById(R.id.text_view); } private class MyTask extends AsyncTask<String, Integer, String> { //onPreExecute方法用于在执行后台任务前做一些UI操作 @Override protected void onPreExecute() { Log.i(TAG, "onPreExecute() called"); textView.setText("loading..."); } //doInBackground方法内部执行后台任务,不可在此方法内修改UI @Override protected String doInBackground(String... params) { Log.i(TAG, "doInBackground(Params... params) called"); int time = 0; // 用一个循环去模拟下载的过程 try { while(time < 100){ publishProgress(time); time++; Thread.sleep(100); } } catch (InterruptedException e) { e.printStackTrace(); } return "下载完成"; } //onProgressUpdate方法用于更新进度信息 @Override protected void onProgressUpdate(Integer... progresses) { Log.i(TAG, "onProgressUpdate(Progress... progresses) called"); progressBar.setProgress(progresses[0]); textView.setText("loading..." + progresses[0] + "%"); } //onPostExecute方法用于在执行完后台任务后更新UI,显示结果 @Override protected void onPostExecute(String result) { Log.i(TAG, "onPostExecute(Result result) called"); textView.setText(result); execute.setEnabled(true); cancel.setEnabled(false); } //onCancelled方法用于在取消执行中的任务时更改UI @Override protected void onCancelled() { Log.i(TAG, "onCancelled() called"); textView.setText("cancelled"); progressBar.setProgress(0); execute.setEnabled(true); cancel.setEnabled(false); } } }
XML文件如下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <Button android:id="@+id/execute" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="execute"/> <Button android:id="@+id/cancel" android:layout_width="fill_parent" android:layout_height="wrap_content" android:enabled="false" android:text="cancel"/> <ProgressBar android:id="@+id/progress_bar" android:layout_width="fill_parent" android:layout_height="wrap_content" android:progress="0" android:max="100" style="?android:attr/progressBarStyleHorizontal"/> <ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/text_view" android:layout_width="fill_parent" android:layout_height="wrap_content"/> </ScrollView> </LinearLayout>
本文地址:https://blog.csdn.net/woshijuzizi/article/details/107730802