第一行代码学习笔记第十章——探究服务
知識點目錄
- 10.1 服務是什么
- 10.2 Android多線程編程
* 10.2.1 線程的基本用法
* 10.2.2 在子線程中更新UI
* 10.2.3 解析異步消息處理機制
* 10.2.4 使用AsyncTask
- 10.3 服務的基本用法
* 10.3.1 定義一個服務
* 10.3.2 啟動和停止服務
* 10.3.3 活動和服務進行通信
- 10.4 服務的生命周期
- 10.5 服務的更多技巧
* 10.5.1 使用前臺服務
* 10.5.2 使用IntentService
- 10.6 服務的最佳實踐——完整版的下載示例
- 10.7 小結與點評
知識點回顧
10.1 服務是什么
服務(Service)是Android中實現程序后臺運行的解決方案。它非常適合去執行一些不需要和用戶交互而且還要求長期運行的任務。
-
服務并不是運行在獨立的進程中,而是依賴創建服務時所在的應用程序進程
-
服務并不會自動開啟線程,所有的代碼都是默認運行在主線程中。
正常情況下,我我們都需要手動創建子線程,并在子線程中執行具體的任務,否則就有可能出現主線程被阻塞住的情況。
10.2 Android多線程編程
當我們需要執行一些耗時操作(例如:網絡請求)時,都會將這些操作放在子線程中去運行,否則容易被阻塞住,從而影響用戶對軟件的正常使用。
10.2.1 線程的基本用法
創建線程的方式一般有如下兩種:
1. 繼承Thread
新建一個類繼承Thread,然后重寫父類的run()方法,在run()方法里面寫耗時邏輯。
public class MyThread extends Thread {@Overridepublic void run() {// 處理具體的邏輯} }然后new出一個MyThread實例,調用它的start()方法,這樣run()方法中的代碼就會在子線程中運行。
new MyThread().start();2. 實現Runnable接口
繼承的耦合性太高,我們可以通過實現Runnable接口的方式來定義一個線程。
public class MyThread implements Runnable {@Overridepublic void run() {// 處理具體的邏輯} }啟動線程的方式如下:
MyThread myThread = new MyThread();new Thread(myThread).start();3. 匿名類
如果不想去專門再定義一個類去實現Runnable接口,那么也可以使用匿名類的形式,這種方式更加常見。
new Thread(new Runnable() {@Overridepublic void run() {// 處理具體的邏輯} }).start();10.2.2 在子線程中更新UI
下面我們寫一個Demo在子線程中更新UI看看效果。
1.布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/change_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:textAllCaps="false"android:text="Change text"/><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:textAllCaps="false"android:text="Hello world"android:textSize="20sp"/></RelativeLayout>2.在子線程中更新UI
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mText = (TextView) findViewById(R.id.text);Button changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text://開一個子線程更新UInew Thread(new Runnable() {@Overridepublic void run() {mText.setText("Nice to meet you");}}).start();break;}} }運行程序,并點擊"Change text"按鈕。程序會直接崩潰:
觀察logcat中的錯誤日志如下:
為了解決這種情況,Android提供了一套異步消息處理機制,很好地解決了子線程中進行UI操作的問題。
異步消息機制的基本用法:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {public static final int UPDATE_TEXT = 1; // 定義個整型常量,用于表示Handler中某個動作private TextView mText;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_TEXT://在這里進行UI操作mText.setText("Nice to meet you");break;}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mText = (TextView) findViewById(R.id.text);Button changeText = (Button) findViewById(R.id.change_text);changeText.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text:new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();message.what = UPDATE_TEXT;mHandler.sendMessage(message); //將Message對象發出去}}).start();break;default:break;}} }10.2.3 解析異步消息處理機制
在了解了Android異步消息處理的基本用法后,我們來深入學習下異步消息機制的原理。
Android異步消息處理主要由4個部分組成:Message、Handler、MessageQueue和Looper。
Message
在線程之間傳遞消息,可以在內部攜帶少量的信息,主要用于在不同線程之間交換數據。
Handler
主要用于發送和處理消息。sendMessage()方法用于發送消息,handleMessage()方法用于處理消息。
MessageQueue
MessageQueue是消息隊列,主要用于存放所有通過Handler發送的消息,這部分消息會一直存在于消息隊列中,等待被處理。每一個線程中只會有一個MessageQueue對象。
Looper
Looper是每個線程中的MessageQueue的管家,調用Looper的loop()方法后,就會進入到一個無限循環中,每當發現MessageQueue中存在一條消息,就會將它取出,并傳遞到Handler的handleMessage()方法中。每一個線程中只會有一個Looper對象。
綜上所述:異步消息處理的整個流程如下:
-
主線程中創建一個Handler對象,并重寫handleMessage()方法
-
子線程中需要進行UI操作時,創建一個Message對象,并通過Handler的sendMessage()方法將這條消息發送出去
-
發送出去的消息被添加到MessageQueue中等待被處理
-
Looper會一直嘗試從MessageQueue中取出待處理的消息
-
分發到Handler的handleMessage()方法中進行處理
整個異步消息處理機制的流程示意圖如下所示:
前面使用的runOnUiThread()方法就是一個異步消息處理機制的接口封裝。
10.2.4 使用AsyncTask
AsyncTask是Android幫我們封裝好的對異步消息處理的工具,背后實現的原理也是異步消息處理機制。
AsyncTask是一個抽象類,需要創建一個類去繼承它。在繼承時可以為AsyncTask類指定3個泛型參數:
-
第一個參數Params??捎糜谠诤笈_任務中使用
-
第二個參數Progress。后臺任務執行時,如果需要在界面上顯示當前進度,則使用這里指定的泛型作為進度單位
-
第三個參數Result。當任務執行完畢,如果需要對結果進行返回,則使用這里指定的泛型作為返回值類型
基本使用方法:
class DownloadTask extends AsyncTask <Void,Integer,Boolean>{/*** 在后臺任務開始之前調用* 用于一些界面的初始化操作,例如顯示一個進度條對話框* 主線程中運行*/@Overrideprotected void onPreExecute() {super.onPreExecute();//顯示進度對話框progressDialog.show();}/***在這里處理所有的耗時操作* 如果需要更新UI元素,例如反饋當前任務的執行進度,可以調用publishProgress(Progress...)* 如果任務完成可以通過return語句來將任務執行的結果返回,返回類型是AsyncTask中的第三參數* 如果AsyncTask的第三個參數是Void,那么就可以不返回任務執行的結果* 子線程中運行* @param voids* @return*/@Overrideprotected Boolean doInBackground(Void... voids) {try {while (true) {int downloadPercent = doDownload(); //這是一個虛構的方法publishProgress(downloadPercent);if (downloadPercent >= 100) {break;}}} catch (Exception e) {return false;}return true;}/*** 當doInBackground()中調用了publishProgress(Progress...),則此方法會很快被調用* 該方法中攜帶的參數就是在后臺任務中傳遞多來的* 可以對UI進行操作,利用參數中的值可以對界面上的元素進行更新* 主線程中運行* @param values*/@Overrideprotected void onProgressUpdate(Integer... values) {//在這里更新下載進度progressDialog.setMessage();}/*** 當doInBackground()中調用了return語句時,這個方法會很快被調用* 返回的數據作為參數傳遞到此方法中* 可以利用返回的數據來進行一些UI操作,比如提醒任務執行的結果或關閉掉進度條對話框等* 主線程中運行* @param aBoolean*/@Overrideprotected void onPostExecute(Boolean aBoolean) {//關閉對話框progressDialog.dismiss();if (aBoolean) {Toast.makeText(MainActivity.this, "Download succeeded", Toast.LENGTH_SHORT).show();} else {Toast.makeText(MainActivity.this, "Download failed", Toast.LENGTH_SHORT).show();}} }AsyncTask的用法就是:
-
在onPreExecute()中做一些初始化工作
-
在doInBackground()中執行具體的耗時操作
-
在onProgressUpdate()中進行UI操作
-
在onPostExecute()中執行一些任務的收尾工作
10.3 服務的基本用法
10.3.1 定義一個服務
可以通過Android Studio的快捷鍵來創建:
和
通過這樣創建的Service,會自動在AndroidManifest.xml中注冊。
public class MyService extends Service {public MyService() {}/*** 在服務創建的時候調用*/@Overridepublic void onCreate() {super.onCreate();}/*** 在每次服務啟動的時候調用* @param intent* @param flags* @param startId* @return*/@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {return super.onStartCommand(intent, flags, startId);}@Overridepublic IBinder onBind(Intent intent) {// TODO: Return the communication channel to the service.throw new UnsupportedOperationException("Not yet implemented");}/*** 在服務銷毀的時候調用,一般用于回收那些不再使用的資源*/@Overridepublic void onDestroy() {super.onDestroy();} }10.3.2 啟動和停止服務
啟動和停止Service的方法都是借助Intent來實現的。
@Override public void onClick(View v) {switch (v.getId()) {case R.id.start_service:Intent startIntent = new Intent(this, MyService.class);startService(startIntent); // 啟動服務break;case R.id.stop_service:Intent stopIntent = new Intent(this, MyService.class);stopService(stopIntent); // 停止服務break;} }服務啟動后,可以在Settings—>Developer options—>Running servcies中找到它,如下圖所示:
onCreate()與onStartCommand()的區別:
-
onCreate()只在服務第一次創建的時候使用
-
onStartCommand()是在服務每次啟動的時候都會調用
10.3.3 活動和服務進行通信
如果在活動中想決定何時開始下載和隨時查看下載進度,我們可以創建一個專門的Binder對象來對下載功能進行管理。同時借助onBind()方法。
public class MyService extends Service {private static final String TAG = "MyService";public MyService() {}private DownloadBinder mDownloadBinder = new DownloadBinder();class DownloadBinder extends Binder {public void startDownload() {Log.d(TAG, "startDownload executed");}public int getProgress() {Log.d(TAG, "getProgress executed");return 0;}}/*** 在服務創建的時候調用*/@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "onCreate executed ");}@Overridepublic IBinder onBind(Intent intent) {return mDownloadBinder;}/*** 在服務銷毀的時候調用,一般用于回收那些不再使用的資源*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");} }在活動中綁定和解綁服務:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private MyService.DownloadBinder mDownloadBinder;private ServiceConnection mConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {mDownloadBinder = (MyService.DownloadBinder) service;mDownloadBinder.startDownload();mDownloadBinder.getProgress();}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button bindService = (Button) findViewById(R.id.bind_service);Button unbindService = (Button) findViewById(R.id.unbind_service);bindService.setOnClickListener(this);unbindService.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.bind_service:Intent bindIntent = new Intent(this, MyService.class);//BIND_AUTO_CREATE 表示在活動和服務進行綁定后自動創建服務bindService(bindIntent, mConnection, BIND_AUTO_CREATE); // 綁定服務break;case R.id.unbind_service:Intent unbindIntent = new Intent(this, MyService.class);unbindService(mConnection); // 解綁服務break;default:break;}} }備注:服務在整個應用程序范圍內都是通用的,即可以和不同的活動進行綁定,而且在綁定完成后它們都可以獲取到相同的DownloadBinder實例。
10.4 服務的生命周期
服務的生命周期主要有以下兩種路徑:
-
啟動服務
該服務在其他組件調用startService()時創建,并回調onStartCommand()方法。如果這個服務之前沒有創建過,onCreate()方法會先于onStartCommand()方法執行。直到調用stopService()或stopSelf()方法服務才會停止。注意:雖然每調用一次startService()方法onStartCommand()都會執行一次,但實際上每個服務都只會存在一個實例。
-
綁定服務
該服務在其他組件調用bindService()時創建,并回調服務中的onBind()方法。如果服務之前沒有創建過,onCreate()方法會優于onBind()方法執行??蛻舳双@取到onBind()方法里面返回的IBinder對象的實例,就能與服務端進行通信了。
當我們對服務同時進行了startService()和bindService()時,這時候我們需要同時調用stopService()和unbindService()方法,服務才會被銷毀,onDestory()方法才會執行。
Service兩種情況下生命周期圖如下:
10.5 服務的更多技巧
10.5.1 使用前臺服務
服務一直都在后臺運行,優先級比較低,當系統出現內存不足的情況時,有可能被系統回收掉。如果想服務一直保持運行,可以考慮使用前臺服務。
前臺服務與后臺服務的區別:
-
不會被系統回收掉
-
狀態欄會有一個正在運行的圖標,下拉狀態欄后可以看到更加詳細的信息。
讓服務變成前臺服務的方法如下:
public class MyService extends Service {....../*** 在服務創建的時候調用*/@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "onCreate executed ");Intent intent = new Intent(this, MainActivity.class);PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);Notification notification = new NotificationCompat.Builder(this).setContentTitle("This is content title").setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)).setContentIntent(pi).build();startForeground(1,notification);}......}具體做法就是創建一個Notification,然后調用startForeground()方法即可。
startForeground()有兩個參數:
參數一:通知的id
參數二:構建的Notification對象。
點擊StartService或BindService按鈕后,MyService就會以前臺服務的模式啟動,并且在系統狀態欄會顯示一個通知圖標,下拉狀態欄后可以看到該通知的詳細內容,如下圖所示:
10.5.2 使用IntentService
IntentService是一種異步的,會自動停止的服務。
基本用法如下:
public class MyIntentService extends IntentService {private static final String TAG = "MyIntentService";public MyIntentService() {super("MyIntentService"); //調用父類的有參構造函數}/*** 處理一些耗時操作* 運行在子線程中,不會出現ANR問題* @param intent*/@Overrideprotected void onHandleIntent(Intent intent) {Log.d(TAG, "Thread id is " + Thread.currentThread().getId());}/*** 服務運行結束后,會自動停止*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");} }點擊Start IntentServcie按鈕后,打印log如下:
可以看出IntentServcie集開啟線程和自動停止于一身。
10.6 服務的最佳實踐——完整版的下載示例
完整版的功能需求:結合AsyncTask和Service實現開始下載、暫停下載和取消下載的功能。
代碼已上傳到我的github上,需要的朋友可以點擊如下鏈接查看:
服務的最佳實踐——完整版的下載示例
10.7 小結與點評
本章主要學習了Android多線程編程、服務的基本用法、服務的生命周期、前臺服務和IntentService等。最后通過完整版下載示例對前面學習的知識進行了綜合運用。
非常感謝您的耐心閱讀,希望我的文章對您有幫助。歡迎點評、轉發或分享給您的朋友或技術群。
總結
以上是生活随笔為你收集整理的第一行代码学习笔记第十章——探究服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一行代码学习笔记第九章——使用网络技术
- 下一篇: 自定义控件——旋转菜单