第一行代码学习笔记第十章——探究服务
知識點(diǎn)目錄
- 10.1 服務(wù)是什么
- 10.2 Android多線程編程
* 10.2.1 線程的基本用法
* 10.2.2 在子線程中更新UI
* 10.2.3 解析異步消息處理機(jī)制
* 10.2.4 使用AsyncTask
- 10.3 服務(wù)的基本用法
* 10.3.1 定義一個服務(wù)
* 10.3.2 啟動和停止服務(wù)
* 10.3.3 活動和服務(wù)進(jìn)行通信
- 10.4 服務(wù)的生命周期
- 10.5 服務(wù)的更多技巧
* 10.5.1 使用前臺服務(wù)
* 10.5.2 使用IntentService
- 10.6 服務(wù)的最佳實(shí)踐——完整版的下載示例
- 10.7 小結(jié)與點(diǎn)評
知識點(diǎn)回顧
10.1 服務(wù)是什么
服務(wù)(Service)是Android中實(shí)現(xiàn)程序后臺運(yùn)行的解決方案。它非常適合去執(zhí)行一些不需要和用戶交互而且還要求長期運(yùn)行的任務(wù)。
-
服務(wù)并不是運(yùn)行在獨(dú)立的進(jìn)程中,而是依賴創(chuàng)建服務(wù)時所在的應(yīng)用程序進(jìn)程
-
服務(wù)并不會自動開啟線程,所有的代碼都是默認(rèn)運(yùn)行在主線程中。
正常情況下,我我們都需要手動創(chuàng)建子線程,并在子線程中執(zhí)行具體的任務(wù),否則就有可能出現(xiàn)主線程被阻塞住的情況。
10.2 Android多線程編程
當(dāng)我們需要執(zhí)行一些耗時操作(例如:網(wǎng)絡(luò)請求)時,都會將這些操作放在子線程中去運(yùn)行,否則容易被阻塞住,從而影響用戶對軟件的正常使用。
10.2.1 線程的基本用法
創(chuàng)建線程的方式一般有如下兩種:
1. 繼承Thread
新建一個類繼承Thread,然后重寫父類的run()方法,在run()方法里面寫耗時邏輯。
public class MyThread extends Thread {@Overridepublic void run() {// 處理具體的邏輯} }然后new出一個MyThread實(shí)例,調(diào)用它的start()方法,這樣run()方法中的代碼就會在子線程中運(yùn)行。
new MyThread().start();2. 實(shí)現(xiàn)Runnable接口
繼承的耦合性太高,我們可以通過實(shí)現(xiàn)Runnable接口的方式來定義一個線程。
public class MyThread implements Runnable {@Overridepublic void run() {// 處理具體的邏輯} }啟動線程的方式如下:
MyThread myThread = new MyThread();new Thread(myThread).start();3. 匿名類
如果不想去專門再定義一個類去實(shí)現(xiàn)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;}} }運(yùn)行程序,并點(diǎn)擊"Change text"按鈕。程序會直接崩潰:
觀察logcat中的錯誤日志如下:
為了解決這種情況,Android提供了一套異步消息處理機(jī)制,很好地解決了子線程中進(jìn)行UI操作的問題。
異步消息機(jī)制的基本用法:
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://在這里進(jìn)行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對象發(fā)出去}}).start();break;default:break;}} }10.2.3 解析異步消息處理機(jī)制
在了解了Android異步消息處理的基本用法后,我們來深入學(xué)習(xí)下異步消息機(jī)制的原理。
Android異步消息處理主要由4個部分組成:Message、Handler、MessageQueue和Looper。
Message
在線程之間傳遞消息,可以在內(nèi)部攜帶少量的信息,主要用于在不同線程之間交換數(shù)據(jù)。
Handler
主要用于發(fā)送和處理消息。sendMessage()方法用于發(fā)送消息,handleMessage()方法用于處理消息。
MessageQueue
MessageQueue是消息隊(duì)列,主要用于存放所有通過Handler發(fā)送的消息,這部分消息會一直存在于消息隊(duì)列中,等待被處理。每一個線程中只會有一個MessageQueue對象。
Looper
Looper是每個線程中的MessageQueue的管家,調(diào)用Looper的loop()方法后,就會進(jìn)入到一個無限循環(huán)中,每當(dāng)發(fā)現(xiàn)MessageQueue中存在一條消息,就會將它取出,并傳遞到Handler的handleMessage()方法中。每一個線程中只會有一個Looper對象。
綜上所述:異步消息處理的整個流程如下:
-
主線程中創(chuàng)建一個Handler對象,并重寫handleMessage()方法
-
子線程中需要進(jìn)行UI操作時,創(chuàng)建一個Message對象,并通過Handler的sendMessage()方法將這條消息發(fā)送出去
-
發(fā)送出去的消息被添加到MessageQueue中等待被處理
-
Looper會一直嘗試從MessageQueue中取出待處理的消息
-
分發(fā)到Handler的handleMessage()方法中進(jìn)行處理
整個異步消息處理機(jī)制的流程示意圖如下所示:
前面使用的runOnUiThread()方法就是一個異步消息處理機(jī)制的接口封裝。
10.2.4 使用AsyncTask
AsyncTask是Android幫我們封裝好的對異步消息處理的工具,背后實(shí)現(xiàn)的原理也是異步消息處理機(jī)制。
AsyncTask是一個抽象類,需要創(chuàng)建一個類去繼承它。在繼承時可以為AsyncTask類指定3個泛型參數(shù):
-
第一個參數(shù)Params??捎糜谠诤笈_任務(wù)中使用
-
第二個參數(shù)Progress。后臺任務(wù)執(zhí)行時,如果需要在界面上顯示當(dāng)前進(jìn)度,則使用這里指定的泛型作為進(jìn)度單位
-
第三個參數(shù)Result。當(dāng)任務(wù)執(zhí)行完畢,如果需要對結(jié)果進(jìn)行返回,則使用這里指定的泛型作為返回值類型
基本使用方法:
class DownloadTask extends AsyncTask <Void,Integer,Boolean>{/*** 在后臺任務(wù)開始之前調(diào)用* 用于一些界面的初始化操作,例如顯示一個進(jìn)度條對話框* 主線程中運(yùn)行*/@Overrideprotected void onPreExecute() {super.onPreExecute();//顯示進(jìn)度對話框progressDialog.show();}/***在這里處理所有的耗時操作* 如果需要更新UI元素,例如反饋當(dāng)前任務(wù)的執(zhí)行進(jìn)度,可以調(diào)用publishProgress(Progress...)* 如果任務(wù)完成可以通過return語句來將任務(wù)執(zhí)行的結(jié)果返回,返回類型是AsyncTask中的第三參數(shù)* 如果AsyncTask的第三個參數(shù)是Void,那么就可以不返回任務(wù)執(zhí)行的結(jié)果* 子線程中運(yùn)行* @param voids* @return*/@Overrideprotected Boolean doInBackground(Void... voids) {try {while (true) {int downloadPercent = doDownload(); //這是一個虛構(gòu)的方法publishProgress(downloadPercent);if (downloadPercent >= 100) {break;}}} catch (Exception e) {return false;}return true;}/*** 當(dāng)doInBackground()中調(diào)用了publishProgress(Progress...),則此方法會很快被調(diào)用* 該方法中攜帶的參數(shù)就是在后臺任務(wù)中傳遞多來的* 可以對UI進(jìn)行操作,利用參數(shù)中的值可以對界面上的元素進(jìn)行更新* 主線程中運(yùn)行* @param values*/@Overrideprotected void onProgressUpdate(Integer... values) {//在這里更新下載進(jìn)度progressDialog.setMessage();}/*** 當(dāng)doInBackground()中調(diào)用了return語句時,這個方法會很快被調(diào)用* 返回的數(shù)據(jù)作為參數(shù)傳遞到此方法中* 可以利用返回的數(shù)據(jù)來進(jìn)行一些UI操作,比如提醒任務(wù)執(zhí)行的結(jié)果或關(guān)閉掉進(jìn)度條對話框等* 主線程中運(yùn)行* @param aBoolean*/@Overrideprotected void onPostExecute(Boolean aBoolean) {//關(guān)閉對話框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()中執(zhí)行具體的耗時操作
-
在onProgressUpdate()中進(jìn)行UI操作
-
在onPostExecute()中執(zhí)行一些任務(wù)的收尾工作
10.3 服務(wù)的基本用法
10.3.1 定義一個服務(wù)
可以通過Android Studio的快捷鍵來創(chuàng)建:
和
通過這樣創(chuàng)建的Service,會自動在AndroidManifest.xml中注冊。
public class MyService extends Service {public MyService() {}/*** 在服務(wù)創(chuàng)建的時候調(diào)用*/@Overridepublic void onCreate() {super.onCreate();}/*** 在每次服務(wù)啟動的時候調(diào)用* @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");}/*** 在服務(wù)銷毀的時候調(diào)用,一般用于回收那些不再使用的資源*/@Overridepublic void onDestroy() {super.onDestroy();} }10.3.2 啟動和停止服務(wù)
啟動和停止Service的方法都是借助Intent來實(shí)現(xiàn)的。
@Override public void onClick(View v) {switch (v.getId()) {case R.id.start_service:Intent startIntent = new Intent(this, MyService.class);startService(startIntent); // 啟動服務(wù)break;case R.id.stop_service:Intent stopIntent = new Intent(this, MyService.class);stopService(stopIntent); // 停止服務(wù)break;} }服務(wù)啟動后,可以在Settings—>Developer options—>Running servcies中找到它,如下圖所示:
onCreate()與onStartCommand()的區(qū)別:
-
onCreate()只在服務(wù)第一次創(chuàng)建的時候使用
-
onStartCommand()是在服務(wù)每次啟動的時候都會調(diào)用
10.3.3 活動和服務(wù)進(jìn)行通信
如果在活動中想決定何時開始下載和隨時查看下載進(jìn)度,我們可以創(chuàng)建一個專門的Binder對象來對下載功能進(jìn)行管理。同時借助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;}}/*** 在服務(wù)創(chuàng)建的時候調(diào)用*/@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "onCreate executed ");}@Overridepublic IBinder onBind(Intent intent) {return mDownloadBinder;}/*** 在服務(wù)銷毀的時候調(diào)用,一般用于回收那些不再使用的資源*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");} }在活動中綁定和解綁服務(wù):
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 表示在活動和服務(wù)進(jìn)行綁定后自動創(chuàng)建服務(wù)bindService(bindIntent, mConnection, BIND_AUTO_CREATE); // 綁定服務(wù)break;case R.id.unbind_service:Intent unbindIntent = new Intent(this, MyService.class);unbindService(mConnection); // 解綁服務(wù)break;default:break;}} }備注:服務(wù)在整個應(yīng)用程序范圍內(nèi)都是通用的,即可以和不同的活動進(jìn)行綁定,而且在綁定完成后它們都可以獲取到相同的DownloadBinder實(shí)例。
10.4 服務(wù)的生命周期
服務(wù)的生命周期主要有以下兩種路徑:
-
啟動服務(wù)
該服務(wù)在其他組件調(diào)用startService()時創(chuàng)建,并回調(diào)onStartCommand()方法。如果這個服務(wù)之前沒有創(chuàng)建過,onCreate()方法會先于onStartCommand()方法執(zhí)行。直到調(diào)用stopService()或stopSelf()方法服務(wù)才會停止。注意:雖然每調(diào)用一次startService()方法onStartCommand()都會執(zhí)行一次,但實(shí)際上每個服務(wù)都只會存在一個實(shí)例。
-
綁定服務(wù)
該服務(wù)在其他組件調(diào)用bindService()時創(chuàng)建,并回調(diào)服務(wù)中的onBind()方法。如果服務(wù)之前沒有創(chuàng)建過,onCreate()方法會優(yōu)于onBind()方法執(zhí)行??蛻舳双@取到onBind()方法里面返回的IBinder對象的實(shí)例,就能與服務(wù)端進(jìn)行通信了。
當(dāng)我們對服務(wù)同時進(jìn)行了startService()和bindService()時,這時候我們需要同時調(diào)用stopService()和unbindService()方法,服務(wù)才會被銷毀,onDestory()方法才會執(zhí)行。
Service兩種情況下生命周期圖如下:
10.5 服務(wù)的更多技巧
10.5.1 使用前臺服務(wù)
服務(wù)一直都在后臺運(yùn)行,優(yōu)先級比較低,當(dāng)系統(tǒng)出現(xiàn)內(nèi)存不足的情況時,有可能被系統(tǒng)回收掉。如果想服務(wù)一直保持運(yùn)行,可以考慮使用前臺服務(wù)。
前臺服務(wù)與后臺服務(wù)的區(qū)別:
-
不會被系統(tǒng)回收掉
-
狀態(tài)欄會有一個正在運(yùn)行的圖標(biāo),下拉狀態(tài)欄后可以看到更加詳細(xì)的信息。
讓服務(wù)變成前臺服務(wù)的方法如下:
public class MyService extends Service {....../*** 在服務(wù)創(chuàng)建的時候調(diào)用*/@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);}......}具體做法就是創(chuàng)建一個Notification,然后調(diào)用startForeground()方法即可。
startForeground()有兩個參數(shù):
參數(shù)一:通知的id
參數(shù)二:構(gòu)建的Notification對象。
點(diǎn)擊StartService或BindService按鈕后,MyService就會以前臺服務(wù)的模式啟動,并且在系統(tǒng)狀態(tài)欄會顯示一個通知圖標(biāo),下拉狀態(tài)欄后可以看到該通知的詳細(xì)內(nèi)容,如下圖所示:
10.5.2 使用IntentService
IntentService是一種異步的,會自動停止的服務(wù)。
基本用法如下:
public class MyIntentService extends IntentService {private static final String TAG = "MyIntentService";public MyIntentService() {super("MyIntentService"); //調(diào)用父類的有參構(gòu)造函數(shù)}/*** 處理一些耗時操作* 運(yùn)行在子線程中,不會出現(xiàn)ANR問題* @param intent*/@Overrideprotected void onHandleIntent(Intent intent) {Log.d(TAG, "Thread id is " + Thread.currentThread().getId());}/*** 服務(wù)運(yùn)行結(jié)束后,會自動停止*/@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "onDestroy executed");} }點(diǎn)擊Start IntentServcie按鈕后,打印log如下:
可以看出IntentServcie集開啟線程和自動停止于一身。
10.6 服務(wù)的最佳實(shí)踐——完整版的下載示例
完整版的功能需求:結(jié)合AsyncTask和Service實(shí)現(xiàn)開始下載、暫停下載和取消下載的功能。
代碼已上傳到我的github上,需要的朋友可以點(diǎn)擊如下鏈接查看:
服務(wù)的最佳實(shí)踐——完整版的下載示例
10.7 小結(jié)與點(diǎn)評
本章主要學(xué)習(xí)了Android多線程編程、服務(wù)的基本用法、服務(wù)的生命周期、前臺服務(wù)和IntentService等。最后通過完整版下載示例對前面學(xué)習(xí)的知識進(jìn)行了綜合運(yùn)用。
非常感謝您的耐心閱讀,希望我的文章對您有幫助。歡迎點(diǎn)評、轉(zhuǎn)發(fā)或分享給您的朋友或技術(shù)群。
總結(jié)
以上是生活随笔為你收集整理的第一行代码学习笔记第十章——探究服务的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第一行代码学习笔记第九章——使用网络技术
- 下一篇: 自定义控件——旋转菜单