1. 程式人生 > >服務 Service 簡介 啟動方式 生命週期 案例

服務 Service 簡介 啟動方式 生命週期 案例

Markdown版本筆記 我的GitHub首頁 我的部落格 我的微信 我的郵箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

服務 Service 簡介 啟動方式 生命週期 案例


目錄

目錄
Service 概述
Service 的啟動方式
startService 方式

特點
生命週期
顯示啟動和隱式啟動
onStartCommand 方法詳解
startId
flags
返回值
bindService 方式
典型過程
生命週期
特點
bindService 方法詳解
ServiceConnection
flag
bind 和 unbind 細節
混合方式啟動服務
清單檔案中可設定的屬性
案例
MainActivity
SecondActivity
MyService
IMyBinder
MyThread
清單檔案

Service 概述

demo地址
Service通常總是稱之為"後臺服務",其中"後臺"一詞是相對於前臺而言的,具體是指其本身的執行並不依賴於使用者可視的UI介面,因此,從實際業務需求上來理解,Service的適用場景應該具備以下條件:

  • 不依賴於使用者可視的UI介面。當然,這一條其實也不是絕對的,如前臺Service就是與Notification介面結合使用的。
  • 具有較長時間的執行特性

你可以繼承以下兩個類中的任何一個來建立一個service:

  • Service:這是所有service的基類
  • IntentService:這是Service的子類,它使用一個工作執行緒來處理所有的啟動請求
    ,一次一個。如果你不要求你的service同時處理多個請求,這是最好的方式。你需要做的就是實現onHandleIntent()方法,他將接受每個啟動請求的intent使得你可以做後臺工作。

Service 的啟動方式

startService 方式

特點

  • 最核心的一句話:當Client採用startService方式啟動一個Service後,Client就和Service沒有任何關聯了
  • Started Service相對比較簡單,通過context.startService啟動Service,context.stopService停止此Service。當然,在Service內部,也可以通過 stopSelf 方式停止其本身。
  • Client A 通過startService(..)啟動Service後,也可以在其他ClientB通過呼叫stopService(..)結束此Service。
  • onBind 函式是Service基類中的唯一抽象方法,子類都必須實現。但此函式的返回值僅針對Bound Service型別的Service才有用,在Started Service型別中,此函式直接返回 null 即可。
  • 對於同一型別的Service,Service例項一次永遠只存在一個,而不管Client是否是相同的元件,也不管Client是否處於相同的程序中。
  • 如果Service需要執行在單獨的程序中,需要通過android:process指明此程序名稱;如果此Service需要對其他App開放,android:exported屬性值需要設定為true。
  • 當Client呼叫startService啟動Service時,可以通過Intent傳遞資料給Service;在Service執行過程中,如果需要傳遞資料給Client,一般可以通過藉助於傳送廣播、EventBus、靜態全域性資料等方式。

生命週期

mark

  • 當Client呼叫startService後,如果Service是第一次啟動,首先會執行onCreate,然後再執行onStartCommand
  • 在Service啟動後,當Client再次呼叫startService,將只執行onStartCommand
  • 使用startService方式啟動Service不會回撥onBind方法。
  • 無論多少次的startService,只需要一次stopService()即可將此Service終止,此時Service會執行onDestroy()函式。
  • 多次呼叫stopService(),或在Service沒有啟動時呼叫stopService(),都不會出現任何報錯或問題。
  • 當用戶強制kill掉程序時,onDestroy()是不會執行的。
  • 完整生命週期:onCreate > onStartCommand > onStartCommand ... > onDestroy

顯示啟動和隱式啟動

結論

  • startService 和 stopService 中的 intent 既可以是顯式 Intent,也可以是隱式 Intent。
  • 當 Client 與 Service 同處於一個 App 時,一般推薦使用顯示 Intent;當處於不同 App 時,只能使用隱式 Intent。
  • 在高版本中,隱式方式開啟或關閉或繫結服務必須設定包名 intent.setPackage(getPackageName()),否則直接掛掉。

PS:通過顯式Intent啟動Service時,如果Service沒有在AndroidManifest.xml中宣告,則不會像Activity那樣直接崩潰並提示找不到Activity,而是會給出waring資訊 IllegalArgumentException: Service not registered

場景
如果在隱式啟動Service的時候,遇到類似這樣的問題:
mark

原因是在5.0後系統要求 Service Intent must be explicit(明確的),文件說明:
mark

所以如果這樣隱式啟動服務:startService(new Intent("YourAction")),程式會直接crash掉
我們改成顯示啟動就可以了:startService(new Intent(getApplicationContext(), MyRemoteService.class))

但是如果我們不能引用服務的.class檔案,只能隱式繫結服務才行,怎麼辦呢?這時我們只需加上包名即可:

Intent service = new Intent("YourAction");  
service.setPackage("com.bqt.aidlservice");//設定包名後就可以正常使用隱式服務了  
startService(service );

這樣就可以了~

onStartCommand 方法詳解

最開始其實只有onStart方法的,後來onStart方法被廢棄掉了,並增加了onStartCommand方法,但是呼叫onStartCommand時首先呼叫的還是onStart方法,只不過多了一個返回值:

@Deprecated
public void onStart(Intent intent, int startId) { ... }

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}

startId

A unique integer representing this specific request to start. Use with stopSelfResult(int)

startId表示的是:對這個service請求的activity(或者其他實體)的編號

每次startService()開啟服務時,系統都會自動為開啟的Service產生一個不同的startId,之前賦予它的startId(如果有)將會被覆蓋,並且這個新產生的startId會成為這個Service的新的startId,無論Service是否正在執行。

考慮如下情況:當多個元件啟用了同一個Service,Service提供互斥的服務(使用synchronized關鍵字),且要保證在Service把所有工作完成之前不能自殺。
這個時候,startId就相當有用了。在Service 的 onStartCommand() 中把startId儲存起來,因為互斥的使用服務,則Service是按順序提供服務的,則Service自殺的時候只用檢查當前Service的startId與儲存的這個startId是否相同,不同則說明Service之後還有任務,不能自殺,相同則說明正在執行的任務已經是最後一個任務了,執行完後就可以使用stopSelf(int startId)方法自殺了。
mark

flags

Additional data about this start request. Currently either 0, START_FLAG_REDELIVERY, or START_FLAG_RETRY.

flags表示啟動服務的方式,取值有:【0】或【START_FLAG_REDELIVERY=1】或【START_FLAG_RETRY=2】

  • START_FLAG_REDELIVERY:表示該 Intent 是先前傳遞的 intent 的重新傳遞(re-delivery),該服務的onStartCommand方法先前返回的是START_REDELIVER_INTENT,但是該服務在呼叫 stopSelf 之前已被殺死。

    This flag is set in onStartCommand if the Intent is a re-delivery of a previously delivered intent, because the service had previously returned START_REDELIVER_INTENT but had been killed before calling stopSelf(int) for that Intent.

  • START_FLAG_RETRY:表示服務之前被設為START_STICKY

    This flag is set in onStartCommand if the Intent is a retry because the original attempt never got to or returned from onStartCommand(Intent, int, int).

返回值

return The return value indicates指示 what semantics語義 the system should use for the service's current started state.
It may be one of the constants associated with the START_CONTINUATION_MASK bits.

具體的可選值及含義如下:

  • START_STICKY_COMPATIBILITY = 0; START_STICKY的相容版本,不保證onStartCommand在被殺死後將再次被呼叫。
  • START_STICKY = 1; 當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,當系統記憶體足夠可用的情況下,系統將會嘗試重新建立此Service,一旦建立成功後將回調onStartCommand方法,但其中的Intent將是null(pendingintent除外)。
  • START_NOT_STICKY = 2; 當Service因為記憶體不足而被系統kill後,接下來未來的某個時間內,即使系統記憶體足夠可用,系統也不會嘗試重新建立此Service,除非程式中Client明確再次呼叫startService啟動此Service。
  • START_REDELIVER_INTENT = 3; 與START_STICKY唯一不同的是,回撥onStartCommand方法時,其中的Intent將是非空,將是最後一次呼叫startService中的intent。

注意:
以上的描述中,當Service因為記憶體不足而被系統kill後 這句話一定要非常注意,因為此函式的返回值設定只是針對此種情況才有意義的。
換言之,當人為的kill掉Service程序,此函式返回值無論怎麼設定,未來的某個時間內,即使系統記憶體足夠可用,Service也不會重啟

另外,需要注意的是,小米等國產手機針對此處可能做了一定的修改:

  • 自啟動管理中有一個自啟動應用列表,預設情況下,只有極少應用預設是可以自啟動的,其他應用預設都是禁止的。
  • 當然使用者可以手動新增自啟動應用,被允許自啟動應用的Started Service,如果onStartCommand()的返回值是START_STICKYSTART_REDELIVER_INTENT,那麼當用戶在小米手機上長按Home鍵結束App後,接下來未來的某個時間內,當系統記憶體足夠可用時,Service依然可以按照上述規定重啟。
  • 當然,如果使用者在設定 > 應用 > 強制kill掉App程序,此時Service也是不會重啟的。

bindService 方式

bindService()方式啟動的 Service 的生命週期和呼叫bindService()方法的 Activity 的生命週期是一致的,也就是說,如果 Activity 結束了,那麼 Service 也就結束了。Service 和呼叫 bindService() 方法的程序是同生共死的。

相對於Started Service,Bound Service 具有更多的知識點。Bound Service 的最重要的特性在於,通過 Service 中的Binder物件可以較為方便進行Client-Service通訊。

典型過程

  • 自定義 Service 繼承 Service,並重寫 onBind 方法,此方法中需要返回具體的IBinder物件,一般將其定義為 Service 的內部類
  • Client通過bindService(Intent, ServiceConnection, flag)將Service繫結到此Client上,繫結時需傳遞一個ServiceConnection例項,一般讓Client自身實現ServiceConnection介面,或在Client中定義一個內部類
  • Client可在ServiceConnection的回撥方法onServiceConnected(ComponentName, IBinder)中獲取Service端IBinder例項,獲取到IBinder例項後,Client端便可以通過自定義介面中的API間接呼叫Service端的方法,實現和Service的通訊
  • 當Client在恰當的生命週期需要和Service解綁時,通過呼叫unbindService(ServiceConnection)即可和Service解綁。

生命週期

mark

  • 當Client呼叫bindService後,如果Service沒有啟動,首先會執行onCreate(),然後再執行onBind
  • 使用bindService方式啟動Service不會回撥onStartCommand()方法。
  • 在bindService後,當Client再次呼叫bindService,不會回撥任何方法。
  • 當Client呼叫unbindService()和Service解綁後,Service會回撥onUnbind方法。如果再沒有其他Client與Service繫結,那麼Service會回撥onDestory方法自行銷燬;如果還和其他Client與Service繫結,則不會回撥onDestory方法。
  • 如果對一個服務進行多次解綁,會丟擲服務沒有註冊的異常
  • 完整生命週期:onCreate > onBind > onUnbind > onDestroy(並非Client呼叫unbindService後就會回撥)。

特點

  • bindService啟動的服務在呼叫者和服務之間是典型的client-server模式,即呼叫者是客戶端,service是服務端,service就一個,但是連線繫結到service上面的客戶端client可以是一個或多個。這裡特別要說明的是,這裡所提到的client指的是元件,比如某個Activity。
  • 客戶端client可以通過IBinder介面獲取到Service的例項,從而可以實現在client端直接呼叫Service中的方法以實現靈活的互動。另外還可藉助IBinder實現跨程序的client-server的互動,這在純startService啟動的Service中是無法實現的。
  • 不同於startService啟動的服務預設是無限期執行的,bindService啟動的服務的生命週期與其繫結的client息息相關,當client銷燬的時候,client會自動與Service解除繫結。client也可以通過明確呼叫Context的unbindService方法與Service解除繫結。
  • 當沒有任何client與Service繫結的時候,Service會自行銷燬(當然,通過startService啟動的Service除外)。
  • startService啟動的服務會涉及Service的的onStartCommand回撥方法,而通過bindService啟動的服務會涉及Service的onBind、onUnbind回撥方法。

bindService 方法詳解

bindService(Intent, ServiceConnection, flag)

ServiceConnection

ServiceConnection可以理解為Client和Service之間的橋樑(聯結器),其作用有兩個:

  • 當Client和Service成功繫結後,Service可以通過回撥ServiceConnection的onServiceConnected方法,將Client需要的IBinder物件返回給Client。
  • 當Client和Service解繫結時,也是通過解除Client和Service之間的ServiceConnection來實現的

ServiceConnection介面有2個方法需要重寫。一個是當Service成功繫結後會被回撥的onServiceConnected()方法,另一個是當Service被關閉時被回撥的onServiceDisconnected()。前者會被傳入一個IBinder引數,這個IBinder就是在Service的生命週期的回撥方法onBind中的返回值,它對Service的繫結式IPC起到非常重要的作用。 

注意:
Client 在 bindService 成功之後就會回撥 onServiceConnected 方法,但在 unbindService 成功之後並不會回撥 onServiceDisconnected 方法,這個方法通常發生在託管(hosting)服務的程序崩潰或被殺死時。這種情況下不會刪除ServiceConnection本身,且對服務的繫結將保持活動狀態,並且當下次執行Service時,您將收到對onServiceConnected的呼叫。

flag

flag則是表明繫結Service時的一些設定,一般情況下可以直接使用0,其標誌位可以為以下幾種:
官方文件

  • BIND_AUTO_CREATE = 1;//常用。表示收到繫結請求的時候,如果服務尚未建立,則即刻建立
  • BIND_DEBUG_UNBIND = 2;//通常用於除錯場景中,判斷繫結的服務是否正確,但容易引起記憶體洩漏
  • BIND_NOT_FOREGROUND = 4;//表示系統將阻止駐留該服務的程序具有前臺優先順序,僅在後臺執行
  • BIND_ABOVE_CLIENT = 8;
  • BIND_ALLOW_OOM_MANAGEMENT = 16;
  • BIND_WAIVE_PRIORITY = 32;
  • BIND_IMPORTANT = 64;
  • BIND_ADJUST_WITH_ACTIVITY = 128;
  • BIND_EXTERNAL_SERVICE = -2147483648;

bind 和 unbind 細節

  • 繫結服務,首先要做的事情就是先用Map記錄當前繫結服務所需的一些資訊, 然後啟動服務。
  • 解綁服務,先從早前的Map集合中移除記錄,然後根據Map集合中是否還有元素決定是否銷燬服務。
  • 如果解綁後再次解綁,無非就是再到這個map集合中找這條記錄,沒有找到就丟擲服務沒有註冊的異常。

混合方式啟動服務

當bindService之前已通過startService開啟了服務,則其生命週期方法回撥順序為
startService -> bindService -> unbindService -> startService -> startService -> stopService
mark

startService -> bindService -> stopService -> unbindService
mark

這種方式其實就是上面那兩種方式特性的綜合,或者說,兼具兩者的優點:

  • 由於是通過startService方式開啟的服務,所以在client通過unbindService解綁後Service並不會銷燬,並且client登出後Service也不會銷燬。
  • 由於又通過bindService方式綁定了服務,所以client同樣可以方便的和Service進行通訊。

PS:
使用bindService來繫結一個已通過startService方式啟動的Service時,系統只是將Service的內部IBinder物件傳遞給啟動者,並不會將Service的生命週期與啟動者繫結,所以,此後呼叫unBindService方法取消繫結後,Service不會呼叫onDestroy方法。

清單檔案中可設定的屬性

可以設定的屬性:

  • enabled=["true" | "false"] //是否這個service能被系統例項化,如果能則為true,否則為false。預設為true。
  • exported=["true" | "false"] //是否其它應用元件能呼叫這個service或同它互動,能為true,預設值依賴於是否包含過濾器。
  • icon="drawable resource" //服務呈現的圖示
  • isolatedProcess=["true" | "false"] //如果設定為true,這個服務將執行在專門的程序中
  • label="string resource" //這個服務給使用者顯示的名稱
  • name="string" //實現這個service的Service子類名稱
  • permission="string" //為了啟動這個service或繫結到它一個實體必須要有的許可權的名稱
  • process="string" //服務將要執行的程序名稱

android:enabled

  • 是否這個 service 能被系統例項化,如果能則為true,否則為false。預設為true。
  • <application>元素有它自身的能應用到所有元件的enabled屬性,要使這個service能夠enabled,那麼<application>和這個<service>的此屬性都必須為true(預設值都是true);如果有一個為false,這個服務就會disabled。

android:exported
mark

  • 是否其它應用的元件能呼叫這個 service 或同它互動,如果能則為true,否則為false。
  • 當值為false時,只有同一個應用的元件有相同使用者ID的應用的元件能啟動這個服務或繫結它。
  • 預設值依賴於服務是否包含intent filters
    • 沒有intent filters意味著它只能通過指定它的準確類名來呼叫它,這就意味著這個服務只能在應用內部被使用(因為其它應用不知道這個類的類名)。因此,在這種情況下,預設值是false。
    • 至少有一個intent filters意味著這個服務可以在外部被使用,因此,預設值為true。
  • 這個屬性並非是限制這個服務暴露給其它服務的唯一途徑,你也能通過許可權來限制跟服務互動的外部實體(參見permisson屬性)。

android:icon

  • 服務呈現的圖示。
  • 如果沒有設定,那麼將使用application的圖示代替

android:isolatedProcess

  • 如果設定為true,這個服務將執行在專門的程序中,這個程序從系統的剩餘部分獨立出來,它自身沒有許可權。同它唯一的通訊方式就是通過這個Service API(binding或starting)。

android:label

  • 這個服務給使用者顯示的名稱。
  • 如果這個屬性沒有設定,將使用<application>的label屬性代替

android:name

  • 實現這個service的Service子類名稱,沒有預設值,這個名稱必須被指定。
  • 可以是完整格式的類名,也可以是一個簡寫。

android:permission

  • 為了啟動這個service或繫結到它一個實體必須要有的許可權的名稱
  • 如果startService(),bindService()或stopService()的呼叫者還沒有獲取這個授權,那麼這些方法就不會工作,而且這個intent物件也不會傳遞到service。
  • 如果這個屬性沒有設定,由<application>元素的permission屬性設定的許可權就會應用到這個service。如果<application>也沒有設定,那麼這個服務就不再受許可權保護。

android:process

  • 服務將要執行的程序名稱
  • 一般來講,應用的所有元件都執行在應用建立的預設程序中。就像應用的包名一樣。
  • <application>元素的process屬效能對所有元件設定不同的預設值。然而,元件能通過它自身的process屬性重寫預設值,從而允許你擴充套件你的應用跨越多個程序。
  • 如果分配到這個屬性的名稱以冒號:開始,那麼當需要它的時候,一個新的、對這個應用私有的程序就被建立,同時這個服務就在哪個程序執行。
  • 如果程序的名字以小寫字母開始,那麼這個服務將執行在全域性程序中。這就允許在不同應用中的元件共享這個程序,降低資源的消耗。

案例

MainActivity

public class MainActivity extends ListActivity implements ServiceConnection {
    public boolean isKeepThreadRunning;//執行緒結束條件
    private IMyBinder mIBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"0、開啟一個執行緒,執行死迴圈操作",
            "1、通過標記位關閉上面開啟的所有執行緒",
            "2、通過startService方式顯示開啟服務",
            "3、通過stopService方式顯示關閉服務",
            "4、隱式方式開啟或關閉服務,必須設定包名",
            "5、bindService方式開啟服務 ",
            "6、unbindService方式解除繫結服務",
            "7、通過IBinder間接呼叫服務中的方法",
            "8、啟動另一個Activity"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
        isKeepThreadRunning = true;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        isKeepThreadRunning = false; //在onDestroy中把執行緒的關閉條件設為true,防止記憶體洩漏
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
            case 0: //開啟一個執行緒,執行死迴圈操作
                new MyThread(this).start();
                break;
            case 1: //通過標記位關閉上面開啟的所有執行緒
                isKeepThreadRunning = false;
                break;
            case 2: //startService方式顯示開啟服務
                startService(new Intent(this, MyService.class));
                break;
            case 3://stopService方式顯示關閉服務
                stopService(new Intent(this, MyService.class));
                break;
            case 4://隱式方式開啟或關閉服務,必須設定包名
                Intent intent = new Intent(MyService.ACTION_MY_SERVICE);
                intent.setPackage(getPackageName()); //在高版本中,隱式方式開啟或關閉或繫結服務必須設定包名,否則直接掛掉
                startService(intent);
                break;
            case 5://bindService方式開啟服務
                bindService(new Intent(this, MyService.class), this, Context.BIND_AUTO_CREATE);
                break;
            case 6: //unbindService方式解除繫結服務
                unbindMyService();
                break;
            case 7: //通過IBinder間接呼叫服務中的方法
                callMethodInService();
                break;
            case 8: //啟動另一個Activity
                startActivity(new Intent(this, SecondActivity.class));
                break;
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i("bqt", "MainActivity-onServiceConnected," + name.toString());
        mIBinder = (IMyBinder) service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i("bqt", "MainActivity-onServiceDisconnected," + name.toString());
    }

    private void unbindMyService() {
        if (mIBinder != null) {
            unbindService(this);//多次呼叫會報IllegalArgumentException異常,但是並不崩潰
            mIBinder = null;//若不把mIBinder置為空,則服務銷燬後仍然可以呼叫服務裡的方法,因為內部類的引用還在
        } else {
            Toast.makeText(this, "還沒有繫結服務,不需要解綁", Toast.LENGTH_SHORT).show();
        }
    }

    private void callMethodInService() {
        if (mIBinder != null) {
            mIBinder.callMethodInService(new Random().nextInt(3));
        } else {
            Toast.makeText(this, "還沒有繫結服務", Toast.LENGTH_SHORT).show();
        }
    }
}

SecondActivity

public class SecondActivity extends ListActivity implements ServiceConnection {
    private IMyBinder mIBinder;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"通過startService方式顯示開啟服務",
            "通過stopService方式顯示關閉服務",
            "bindService方式開啟服務",
            "unbindService方式解除繫結服務"};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        switch (position) {
            case 0:
                startService(new Intent(this, MyService.class));
                break;
            case 1:
                stopService(new Intent(this, MyService.class));
                break;
            case 2:
                bindService(new Intent(this, MyService.class), this, Context.BIND_AUTO_CREATE);
                break;
            case 3:
                unbindMyService();
                break;
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i("bqt", "SecondActivity-onServiceConnected," + name.toString());
        mIBinder = (IMyBinder) service;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i("bqt", "SecondActivity-onServiceDisconnected," + name.toString());
    }

    private void unbindMyService() {
        if (mIBinder != null) {
            unbindService(this);//多次呼叫會報IllegalArgumentException異常,但是並不崩潰
            mIBinder = null;//若不把mIBinder置為空,則服務銷燬後仍然可以呼叫服務裡的方法,因為內部類的引用還在
        } else {
            Toast.makeText(this, "還沒有繫結服務,不需要解綁", Toast.LENGTH_SHORT).show();
        }
    }
}

MyService

public class MyService extends Service {
    public static final String ACTION_MY_SERVICE = "com.bqt.service.my_action";

    @Override
    public void onCreate() {
        Log.i("bqt", "MyService-onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("bqt", "MyService-onStartCommand--flags=" + flags + "--startId=" + startId);//flags一直為0,startId每次會自動加1
        return super.onStartCommand(intent, flags, startId); //每次呼叫startService時都會回撥;呼叫bindService時不會回撥
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("bqt", "MyService-onBind");
        return new MyMyBinder(); //再次bindService時,系統不會再呼叫onBind()方法,而是直接把IBinder物件傳遞給其他後來增加的客戶端
    }

    @Override
    public void onRebind(Intent intent) {
        super.onRebind(intent);
        Log.i("bqt", "MyService-onRebind");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("bqt", "MyService-onUnbind");
        return super.onUnbind(intent); //繫結多客戶端情況下,需要解除所有的繫結後才會(就會自動)呼叫onDestoryed方法
    }

    @Override
    public void onDestroy() {
        Log.i("bqt", "MyService-onDestroy");
        super.onDestroy(); //不知道什麼時候呼叫,沒有發現他被回撥過
    }

    /**
     * 這是服務裡面的一個方法,對外是隱藏的,只能通過IBinder間接訪問
     */
    private void methodInService(int money) {
        Log.i("bqt", "MyService-call method in service");
        Toast.makeText(this, "呼叫了服務裡的方法,開啟了大招:" + money, Toast.LENGTH_SHORT).show();
    }

    private class MyMyBinder extends Binder implements IMyBinder {//實現IBinder介面或繼承Binder類

        @Override
        public void callMethodInService(int money) {
            if (money < 1) {
                Toast.makeText(MyService.this, "對不起,餘額不足,不能發大招:" + money, Toast.LENGTH_SHORT).show();
            } else {
                methodInService(money);//間接呼叫了服務中的方法
            }
        }
    }
}

IMyBinder

public interface IMyBinder {
    void callMethodInService(int money);
}

MyThread

public class MyThread extends Thread {
    private SoftReference<MainActivity> context;

    MyThread(MainActivity activity) {
        context = new SoftReference<>(activity);
    }

    @Override
    public void run() {
        if (context != null && context.get() != null) {
            context.get().runOnUiThread(() -> showToast("執行緒" + getId() + "已開啟……"));
            while (context.get().isKeepThreadRunning) {//這是一個死迴圈,關閉執行緒的唯一條件就是isKeepThreadRunning==false
                Log.i("bqt", getId() + " - " + new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date()));
                SystemClock.sleep(2000);
            }
            context.get().runOnUiThread(() -> showToast("執行緒" + getId() + "已關閉……"));
        }
    }

    private void showToast(String text) {
        if (context != null && context.get() != null) {
            Toast.makeText(context.get(), text, Toast.LENGTH_SHORT).show();
        }
    }
}

清單檔案

<service
    android:name=".MyService"
    android:permission="com.bqt.service.test_permission">
    <intent-filter>
        <action android:name="com.bqt.service.my_action"/>
    </intent-filter>
</service>

2018-11-19