轉載請标明出處:
http://blog.csdn.net/gj782128729/article/details/52510925;
本文出自:【高境的部落格】
1. Android中程序和線程概述
1.1. Android中的程序
當一個程式第一次啟動的時候,Android會啟動一個Linux程序和一個主線程。預設的情況下,所有該程式的元件都将在該程序和線程中運作。Android會盡量保留一個正在運作程序,隻在記憶體資源出現不足時,Android會嘗試停止一些程序進而釋放足夠的資源給其他新的程序使用,也能保證使用者正在通路的目前程序有足夠的資源去及時地響應使用者的事件。
我們可以将一些元件運作在其他程序中,并且可以為任意的程序添加線程。元件運作在哪個程序中是在manifest檔案裡設定的,其中
<activity>
,
<service>
,
<receiver>
和
<provider>
都有一個process屬性來指定該元件運作在哪個程序之中。我們可以設定這個屬性,使得每個元件運作在它們自己的程序中,或是幾個元件共同享用一個程序,或是不共同享用。
<application>
元素也有一個process屬性,用來指定所有的元件的預設屬性。
Android會根據程序中運作的元件類别以及元件的狀态來判斷該程序的重要性,Android會首先停止那些不重要的程序。按照重要性從高到低一共有五個級别:
1.前台程序:
前台程序是使用者目前正在使用的程序。隻有一些前台程序可以在任何時候都存在。他們是最後一個被結束的,當記憶體低到根本連他們都不能運作的時候。一般來說,在這種情況下,裝置會進行記憶體排程,中止一些前台程序來保持對使用者互動的響應。
如果有以下的情形的那麼就是前台程序:
(a)這個程序運作着一個正在和使用者互動的Activity(這個Activity的onResume()方法被調用)。
(b)這個程序裡有綁定到目前正在和使用者互動的Activity的一個service。
(c)這個程序裡有一個service對象,這個service對象正在執行一個它的生命周期的回調函數(onCreate(), onStart(), onDestroy())
(d)這個程序裡有一個正在運作onReceive()方法的BroadCastReiver對象。
2.可見程序
可見程序是不包含前台的元件但是仍會影響使用者在螢幕上所見内容的程序,除非前台程序需要擷取它的資源,不然不會被中止。
如果有如下的一種情形就是可見程序:
(a)這個程序中含有一個不位于前台的Activity,但是仍然對使用者是可見的(這個Activity的onPause()方法被調用),這是很可能發生的,例如,如果前台Activity是一個對話框的話,就會允許在它後面看到前一個Activity。
(b)這個程序裡有一個綁定到一個可見的Activity的Service。
3.服務程序
運作着一個通過startService() 方法啟動的service,它不會更新為上面兩種級别。service所在的程序雖然對使用者不是直接可見的,但是他們執行了使用者非常關注的任務(比如播放mp3,從網絡下載下傳資料)。隻要前台程序和可見程序有足夠的記憶體,系統不會回收他們。
4.背景程序
運作着一個對使用者不可見的activity(調用過 onStop() 方法)。這些程序對使用者體驗沒有直接的影響,可以在服務程序、可見程序、前台程序需要記憶體的時候回收。通常,系統中會有很多不可見程序在運作,他們被儲存在LRU (least recently used) 清單中,以便記憶體不足的時候被第一時間回收。如果一個activity正确的執行了它的生命周期,關閉這個程序對于使用者體驗沒有太大的影響。
5.空程序
未運作任何程式元件。運作這些程序的唯一原因是作為一個緩存,縮短下次程式需要重新使用的啟動時間。系統經常中止這些程序,這樣可以調節程式緩存和系統緩存的平衡。
Android 對程序的重要性評級的時候,選取它最高的級别。例如,如果一個程序含有一個service和一個可視activity,程序将被歸入一個可視程序而不是service程序。
1.2. Android中的線程
應用程式啟動時,系統會為它建立一個名為“main”的主線程。主線程非常重要,因為它負責把事件分發給相應的使用者界面widget——包括螢幕繪圖事件。它也是應用程式與Android UI元件包(來自android.widget和android.view包)進行互動的線程。是以,主線程有時也被叫做UI線程。
系統并不會為每個元件的執行個體都建立單獨的線程。運作于同一個程序中的所有元件都是在UI線程中執行個體化的,對每個元件的系統調用也都是由UI線程分發的。是以,對系統回調進行響應的方法(比如報告使用者操作的onKeyDown()或生命周期回調方法)總是運作在UI線程中。
如果UI線程需要處理每一件事情,那些耗時很長的操作——諸如通路網絡或查詢資料庫等将會阻塞整個UI(線程)。一旦線程被阻塞,所有事件都不能被分發,包括螢幕繪圖事件。從使用者的角度看來,應用程式看上去像是挂起了。更糟糕的是,如果UI線程被阻塞超過一定時間(目前大約是5秒鐘),使用者就會被提示那個可惡的“應用程式沒有響應”(ANR)對話框。
此外,Andoid的UI元件包并不是線程安全的。是以不允許從工作線程中操作UI——隻能從UI線程中操作使用者界面。于是,Andoid的單線程模式必須遵守兩個規則:
(a)不要阻塞UI線程。
(b)不要在UI線程之外通路Andoid的UI元件包。
2. Service
2.1. Service概述
Service是Android中四大元件之一,在Android開發中有非常重要的作用。
Service(服務)是一個沒有使用者界面的在背景運作執行耗時操作的應用元件。其他應用元件能夠啟動Service,并且當使用者切換到另外的應用場景,Service将持續在背景運作。另外,一個元件能夠綁定到一個service與之互動(IPC機制),例如,一個service可能會處理網絡操作,播放音樂,操作檔案 I/O或者與内容提供者(content provider)互動,所有這些活動都是在背景進行。
2.2. Service的兩種狀态
Service有兩種狀态,“啟動的”和“綁定”:
Started
通過startService() 啟動的服務處于“啟動的”狀态,一旦啟動,Service就在背景運作,即使啟動它的應用元件已經被銷毀了。通常started狀态的Service執行單任務并且不傳回任何結果給啟動者。比如當下載下傳或上傳一個檔案,當這項操作完成時,Service應該停止它本身。
Bound
還有一種“綁定”狀态的Service,通過調用bindService()來啟動,一個綁定的Service提供一個允許元件與Service互動的接口,可以發送請求、擷取傳回結果,還可以通過跨程序通信來互動(IPC)。綁定的Service隻有當應用元件綁定後才能運作,多個元件可以綁定一個Service,當調用unbind()方法時,這個Service就會被銷毀了。
另外,在官方的說明文檔中還有一個警告:
Service 與Activity一樣都存在與目前程序的主線程中,是以,一些阻塞UI的操作,比如耗時操作不能放在Service裡進行,比如另外開啟一個線程來處理 諸如網絡請求的耗時操作。如果在Service裡進行一些耗CPU和耗時操作,可能會引發ANR警告,這時應用會彈出強制關閉還是等待的對話框。是以,對Service的了解就是和Activity平級的,隻不過是看不見的,在背景運作的一個元件,這也是為什麼和Activity同被說為Android的基本元件。
2.3. Service的簡單使用
1) 建立一個Service,需要繼承Service類。
2) 覆寫一些回調函數來處理服務生命周期的一些關鍵要素,并且如果合适的話,需要提供一個機制讓元件綁定到服務。這些最重要的需要覆寫的函數如下:
onStartCommand():
onStartCommand()方法當另一個元件(如Activity)通過調用startService()請求Service啟動時調用。一旦這個方法執行,這個服務就被開啟并且在背景無限期地運作。如果你實作了這個方法,你就有責任在服務工作完畢後通過調用stopSelf()或者stopService()關閉它(如果僅僅用來提供綁定,就不需要實作這個方法)。
onBind():
onBind()方法當另一個元件(如執行RPC)通過bindService()和這個服務綁定的時候調用。在這個方法的實作中,需要通過傳回一個IBinder對象提供用戶端用來和Service通訊的接口,你必須一緻實作該方法,除非不想綁定服務,這時候需要傳回null。
onCreate()
Service第一次建立的時候調用該方法來執行一次性安裝程式(之前調用要麼onStartCommand()要麼onBind())。如果Service已經運作了,這個方法不會被調用。
onDestory()
當Service不再使用并且被銷毀的時候調用。服務需要實作這個方法來清理資源如線程,注冊的監聽器,接受者等。這個方法是Service最後一個調用的。
3) 在清單檔案中注冊服務
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
3. 服務的第一種啟動方式
服務的第一種啟動方式是調用startService()方法。
建立一個服務,必須實作onBind()方法,實作onCreate(),onStartCommand()和onDestory()方法:
public class DemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
System.out.println("onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
System.out.println("onDestroy");
super.onDestroy();
}
}
在清單檔案中注冊
MainActivity中實作開啟服務
點選按鈕開啟服務
public void click(View v){
Intent intent = new Intent(MainActivity.this,DemoService.class);
startService(intent);
}
檢視Logcat日志發現開啟服務調用onCreate()和onStartCommand()方法:
在此點選按鈕,檢視日志,發現onStartCommand()又執行了一次:
那麼如何檢視服務真的被啟動了呢?我們可以在模拟器設定界面中的應用界面檢視,如下圖:
那麼如何停止服務呢?點開上圖示出的條目,跳轉到如下圖的另一個界面,點選停止按鈕。如下圖:
這時候,服務就停止了。我們可以檢視Logcat日志,發現調用了onDestory()方法:
通過上面的操作可以得出調用startService()方法時Service的生命周期如下圖:
4. 電話竊聽器案例
本案例實作在背景監聽電話狀态,當手機來電并且接通後,開始錄音,并且儲存到sdcard中。
為什麼需要在服務中監聽電話狀态呢?因為服務開啟後可以在背景一直運作,如果放在Activity中監聽電話狀态,當Activity銷毀後就不能監聽到電話狀态了。
使用Thread(){}.start()方法也可以在背景實作監聽,那麼為什麼不使用子線程而使用Service呢?因為之前我們已經了解了程序的等級,當應用程式退出時,目前應用的程序就變成一個空程序,最容易被系統回收。開啟服務是在服務程序中運作,服務程序的優先級比背景程序高。
4.1. 監聽電話狀态
監聽電話狀态需要使用TelephonyManager類。TelephonyManager類主要提供了一系列用于通路與手機通訊相關的狀态和資訊的get方法。其中包括手機SIM的狀态和資訊、電信網絡的狀态及手機使用者的資訊。
Context.getSystemService(Context.TELEPHONY_SERVICE)方法可以用來擷取到TelephonyManager類的對象。需要注意的是有些通訊資訊的擷取,對應用程式的權限有一定的限制,在開發的時候需要為其添加相應的權限。
檢視api文檔,找到如下圖方法,可以用來監聽電話狀态:
listen()方法,注冊一個監聽對象用來接收指定電話狀态變化的通知。
在服務中利用TelephonyManager監聽電話狀态:
public class PhoneService extends Service {
private TelephonyManager tManager;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
tManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
//調用TelephonyManager的listen()方法監聽電話狀态。參數1表示電話狀态監聽器,參數2表示需要監聽的電話事件
tManager.listen(new MyPhoneStateListener(),PhoneStateListener.LISTEN_CALL_STATE);
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
建立自定義電話狀态監聽器:
private class MyPhoneStateListener extends PhoneStateListener {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
super.onCallStateChanged(state, incomingNumber);
//判斷state狀态
switch (state) {
case TelephonyManager.CALL_STATE_IDLE: // CALL_STATE_IDLE表示空閑狀态
System.out.println("判斷使用者是否已經開啟錄音機,如果開啟,上傳");
break;
case TelephonyManager.CALL_STATE_OFFHOOK: // CALL_STATE_OFFHOOK表示接通狀态
System.out.println("開始錄");
break;
case TelephonyManager.CALL_STATE_RINGING: // CALL_STATE_RINGING表示電話響鈴狀态
System.out.println("電話響鈴的時候 我就準備一個錄音機 ");
break;
default:
break;
}
}
}
清單檔案加入讀取手機狀态權限:
開啟服務
public void click(View view){
Intent intent = new Intent(this,PhoneService.class);
startService(intent);
}
運作結果
當開啟服務後,沒有來電,Logcat列印如下日志:
當有來電時Logcat列印如下日志:
當挂斷電話後,列印如下日志:
4.2. 錄音
上面已經實作了電話各種狀态的監聽,現在可以在不同的狀态下操作錄音,将通話錄音記錄到sdcard,這裡需要用到MediaRecorder對象。具體的介紹我們可以參考api。
在監聽方法中實作錄音功能:
//建立MediaRecorder
private MediaRecorder recorder;
......
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
//關閉Recorder,釋放資源
if (recorder!=null) {
recorder.stop();
recorder.reset();
recorder.release();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
System.out.println("開始錄");
//開啟錄音
recorder.start();
break;
case TelephonyManager.CALL_STATE_RINGING:
//建立MediaRecorder對象
recorder = new MediaRecorder();
//設定音頻來源
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//設定影音的輸出格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
//設定音頻檔案的編碼格式
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
//設定音頻檔案儲存的路徑
recorder.setOutputFile("/mnt/sdcard/luyin.3gp");
try {
//準備錄音
recorder.prepare();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
default:
break;
}
加入權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
運作結果,可以在sdcard中看到儲存的錄音luyin.3gp。
4.3. 自動開啟服務
如何自動開啟服務呢?可以在系統重新開機時,利用廣播接受者接收系統重新開機廣播,在廣播中開啟監聽電話狀态的服務。
定義廣播接受者
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent intent2 = new Intent(context,PhoneService.class);
context.startService(intent2);
}
}
需要加入權限
5. 服務的第二種啟動方式
開啟服務的第二種方式是bindService()。第一種方式是startService()方法開啟,與之對應的停止服務的方法是stopService(),bindService()對應的方法是unbindService()。
首先編寫Service類:
public class TestService extends Service {
//當服務被成功綁定的時候調用
@Override
public IBinder onBind(Intent intent) {
System.out.println("onBind");
return null;
}
@Override
public void onCreate() {
System.out.println("onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
System.out.println("onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
System.out.println("onDestroy");
super.onDestroy();
}
}
然後實作Activity中按鈕點選事件開啟服務:
MainActivity中實作兩種方式開啟服務:
public class MainActivity extends Activity {
private MyConn conn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// 采用start-service方式開啟服務
public void click1(View v) {
Intent intent = new Intent(this, TestService.class);
startService(intent);
}
// 采用stop-service方式開啟服務
public void click2(View v) {
Intent intent = new Intent(this, TestService.class);
stopService(intent);
}
// 采用bind-service方式開啟服務
public void click3(View v) {
Intent intent = new Intent(this, TestService.class);
// conn 是ServiceConnection接口用來監視服務的狀态 flags 為了綁定服務
conn = new MyConn();
//bindService()方法,用來綁定服務,當這個方法一調用,Servie中的onBind()方法就會執行。參數1表示需要傳入的服務的intent,參數2便是一個ServiceConnection的實作類,用來監視服務的狀态,參數3是綁定的操作選項
bindService(intent, conn, BIND_AUTO_CREATE);
}
// 采用unbind-service方式開啟服務
public void click4(View v) {
unbindService(conn);
}
//MyConn類實作ServiceConnection接口,用來監視服務的狀态。其中有兩個回調方法,onServiceConnected()方法表示當伺服器連接配接的時候調用,onServiceDisconnected()方法表示當服務失去連接配接時調用
private class MyConn implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
//unbindService()方法,用來解綁服務
//unbindService(conn);
super.onDestroy();
}
}
首先點選bindService(),Service會調用onCreate()和onBind()方法,檢視Logcat日志:
我們點選傳回鍵,這樣Activity就會銷毀,這時候檢視日志,發現Service調用了onDestory()方法,同時Logcat日志會報錯,如下圖:
出現這個錯誤的原因是由于當Activity銷毀的時候會調用onDestory()方法,在Activity需要調用unbindService()方法将服務解綁。是以我們還需要在Activity的onDestory()方法中加入以下代碼:
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
接下來,我們重新部署項目,然後點選bind-service按鈕,Service會調用onCreate()和onBind()方法,Logcat日志會列印如下結果:
這時候我們檢視模拟器設定裡的應用中查找我們的服務,發現找不到我們開啟的服務,如下圖:
然後點選unbind-service按鈕,Service會調用onUnbind()和onDestory()方法,Logcat日志會列印以下結果:
接着,我們重新部署項目,連續點選兩次bind-service按鈕,然後我們點選unbind-service按鈕,再次點選unbind-service按鈕,這時候應用會崩潰,并且Logcat會輸出以下錯誤日志:
總結:
(a)使用bindService()綁定服務,不會使程序變成服務程序;
(b)使用bindService()綁定服務,隻可以綁定一次,不能多次綁定。
使用bindService()方法開啟服務,Service的生命周期如下圖:
6. 調用服務中的方法
6.1. 普通方式調用服務中方法
建立一個Service,在Service中定義一個内部方法:
public class TestDemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void serviceMethod(){
Toast.makeText(this, "我是服務裡面的方法", ).show();
}
}
在MainActivity中點選按鈕調用服務中的方法:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
//建立TestDemoService對象
TestDemoService testDemoService = new TestDemoService();
//調用serviceMethod()方法
testDemoService.serviceMethod();
}
}
運作結果:
應用程式會崩潰,并且Logcat會有如下日志輸出:
定位到代碼中,是Service中自定義的方法中彈出吐司出現了錯誤。由于這邊調用Service服務中的方法是通過Service的一個執行個體來調用的,那麼這個執行個體對象就是一個普通的類,普通的類中沒有上下文,是以彈出吐司需要傳遞的上下文就為null,是以程式會報空指針異常。
6.2. 通過IBinder調用服務中的方法
當使用bindService()綁定服務的時候,需要傳入一個參數,這個參數是ServiceConnection的一個實作類,這個實作類代碼如下:
private class MyConn implements ServiceConnection{
@Override
//onServiceConnected()方法當綁定成功後調用,其中有一個IBinder對象
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
然後分析Service中的onBind()方法代碼:
@Override
public IBinder onBind(Intent intent) {
return null;
}
通過onBind()方法發現這個方法傳回值是IBinder類型,和我們上面ServiceConnection中綁定成功回調中的一個參數類型一緻。其實,這邊的兩個對象是同一個對象,當服務綁定成功後,會傳回IBinder對象給ServiceConnection中的回調函數。
檢視IBinder類,發現這個類是一個接口,是以我們要麼實作IBinder接口,要麼繼承它已知的子類,這裡我們繼承IBinder的一個子類Binder類。
public class TestDemoService extends Service {
@Override
public IBinder onBind(Intent intent) {
//傳回自定義MyBinder對象
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
public void serviceMethod(){
Toast.makeText(this, "我是服務裡面的方法", ).show();
}
//建立MyBinder類繼承Binder類
public class MyBinder extends Binder{
//建立callServiceMethod()方法
void callServiceMethod(){
//調用Service中的serviceMethod()方法
serviceMethod();
}
}
}
這時候當服務成功綁定後,ServiceConnection中的回調函數就能接收到這個對象,通過這個對象來調用服務中的方法。在MainActivity中綁定服務,并且通過IBinder調用服務中的方法:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
bindService(new Intent(this,TestDemoService.class), new MyConn(), BIND_AUTO_CREATE);
}
class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
//接收Service中onBind()傳回的IBinder對象,這個IBinder對象就是我們自定義的MyBinder對象
MyBinder binder = (MyBinder) service;
//通過IBinder對象調用服務中的方法
binder.callServiceMethod();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
下圖是簡易的Activity中調用Service中的方法圖:
這時候,問題又來了,如果Service中有很多方法,那麼中間人IBinder就可以調用Service中的方法,那麼如何控制IBinder中的方法呢?我們可以定義一個接口,将Service中的方法定義到接口中,讓我們的自定義的Binder實作這個接口。
定義的接口:
public interface Iservice {
public void callBanZheng(int money);
public void callPlayMaJiang();
public void callXiSangNa();
}
自定義的MyBinder實作該接口:
private class Mybinder extends Binder implements IService {
@Override
public void callBanZheng(int money) {
banZheng(money);
}
@Override
public void callPlayMaJiang() {
playMaJiang();
}
@Override
public void callXiSangNa() {
xiSangna();
}
}
這樣,自定義的MyBinder類就可以調用Service中的方法。如果Service不希望MyBinder具有其中某個方法,那麼可以在IService接口中不提供該方法。
7. 混合方式開啟Service
7.1. 音樂播放器案例
音樂播放器需要使用到服務,因為當音樂播放器需要在背景也能運作,當手機按Home鍵後,音樂播放器也需要能夠播放,本案例實模拟音樂播放器的實作。在背景服務中,既要保證服務能夠在背景一直運作,又要保證Activity中能夠調用服務中的方法,這就需要利用兩種啟動方法混合使用。
主界面如下圖:
定義一個服務MusicService用來在服務中播放音樂,暫停播放,繼續播放等:
public class MusicService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
//定義開始播放方法
public void play(){
System.out.println("音樂開始播放了 ");
}
//定義音樂暫停方法
public void pause(){
System.out.println("音樂暫停了 ");
}
//定義音樂繼續播放方法
public void rePlay(){
System.out.println("音樂繼續播放 ");
}
private class MyBinder extends Binder implements IService{
@Override
public void callPlay() {
play();
}
@Override
public void callPause() {
pause();
}
@Override
public void callReplay() {
rePlay();
}
}
}
IService接口中定義方法:
public interface Iservice {
public void callPlay();
public void callPause();
public void callReplay();
}
在Activity中點選按鈕,實作音樂播放器的功能:
public class MainActivity extends Activity {
private Myconn myconn;
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this,MusicService.class);
//調用startService()方法開啟服務,保證服務在背景長期運作
startService(intent);
myconn = new Myconn();
//調用bindService()方法,目的是為了調用服務中的方法
bindService(intent, myconn, BIND_AUTO_CREATE);
}
public void click1(View v) {
iservice.callPlay();
}
public void click2(View v) {
iservice.callPause();
}
public void click3(View v) {
iservice.callReplay();
}
private class Myconn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
iservice = (Iservice) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onDestroy() {
unbindService(myconn);
super.onDestroy();
}
}
運作結果:
當應用程式一起動,就開啟服務,并且一直運作。如下圖:
首先點選播放,這時候音樂開始播放,Logcat輸出如下日志:
然後點選暫停,這時候音樂暫停播放,Logcat輸出如下日志:
接下來點選繼續播放,這時候音樂繼續播放,Logcat輸出如下日志:
7.2. 混合方式開啟服務流程
(a)采用startServie()方法開啟服務,保證服務能夠在背景長期運作;
(b)調用bindServie()方法,目的是能夠調用服務中的方法;
(c)Activity銷毀時調用unbindService()方法解綁;
(d)最後調用stopService()方法停止服務。
8. 使用服務注冊特殊廣播接收者
安卓中,有些廣播需要廣播接受者動态注冊(采用代碼的方式注冊),比如電池電量低、解鎖屏等特殊的廣播。
8.1. 接收解鎖屏廣播
定義廣播接受者類ScreenReceiver:
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ("android.intent.action.SCREEN_OFF".equals(action)) {
System.out.println("螢幕off了");
}else if("android.intent.action.SCREEN_ON".equals(action)){
System.out.println("螢幕on了");
}
}
}
在清單檔案中注冊廣播:
<receiver android:name="com.itheima.register.ScreenReceiver" >
<intent-filter>
<action android:name="android.intent.action.SCREEN_ON" />
<action android:name="android.intent.action.SCREEN_OFF" />
</intent-filter>
</receiver>
我們在模拟器上,解屏或者鎖屏,發現Logcat沒有日志輸出,說明靜态注冊廣播接受者不能接收到解鎖屏的廣播。
為什麼不能接收到螢幕解鎖屏的廣播呢?因為手機一天會解鎖屏很多次,是以廣播接受者會不斷的注冊,這樣就比較耗電,是以谷歌就對發送這種特殊的廣播做了特殊處理,隻有動态注冊,才能監聽到這類廣播。
我們在Activity中注冊廣播:
public class MainActivity extends Activity {
private ScreenReceiver screenReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
screenReceiver = new ScreenReceiver();
//建立IntentFilter對象,給該對象設定Action
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
//調用Context對象的registerReceiver()注冊廣播。參數1表示廣播接受者,參數2表示意圖過濾器
registerReceiver(screenReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
運作結果:
當點選鎖屏按鈕,螢幕黑屏,Logcat會輸出如下日志:
點選解屏按鈕,螢幕亮起,Logcat會輸出如下日志:
這時候,如果點選傳回按鈕,應用程式退出,Logcat會報如下錯誤,提示我們需要調用unregisterReceiver()方法:
在Activity的onDestory()中調用unregisterReceiver()方法:
protected void onDestroy() {
unregisterReceiver(screenReceiver);
super.onDestroy();
}
8.2. 保持廣播監聽
因為在Activity中注冊廣播,當Activity被銷毀後,就不能監聽到解鎖屏的廣播,是以,可以在Service中背景一直監聽廣播。
public class ScreenService extends Service {
private ScreenReceiver screenReceiver;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
screenReceiver = new ScreenReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.SCREEN_OFF");
filter.addAction("android.intent.action.SCREEN_ON");
registerReceiver(screenReceiver, filter);
super.onCreate();
}
@Override
public void onDestroy() {
unregisterReceiver(screenReceiver);
super.onDestroy();
}
}
9. 程序間通信
IPC(Inter-Process Communication):程序間通信。
Android系統中,每個應用程式都會有一個程序,由于應用程式之間不能共享記憶體,那麼就需要程序間通信。
9.1. 本地服務和遠端服務
本地服務:服務和啟動它的元件在同一個程序。
遠端服務:服務和啟動它的元件不在同一個程序。
遠端服務隻能隐式啟動,類似隐式啟動Activity,在清單檔案中配置Service标簽時,必須配置intent-filter子節點,并指定action子節點。
9.2. AIDL
AIDL(Android Interface Definition Language):安卓接口定義語言。AIDL是安卓中用來跨程序通信的一種接口定義語言。
應用場景:遠端服務中的中間人對象,其他應用是拿不到的,那麼在通過綁定服務擷取中間人對象時,就無法強制轉換,使用aidl技術,就可以在其他應用中拿到中間人類所實作的接口。
9.3. AIDL的使用
建立一個遠端應用項目,在項目中建立一個遠端服務類RemoteService:
public class RemoteService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
public void remoteMethod(){
System.out.println("我是遠端服務裡面的方法");
}
private class MyBinder extends Binder implements Iservice{
@Override
public void callRemoteMethod() {
remoteMethod();
}
}
}
IService代碼:
public interface Iservice {
public void callRemoteMethod();
}
配置遠端服務:
建立本地應用項目,在MainActivity中實作點選按鈕事件:
public class MainActivity extends Activity {
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
bindService(intent, new MyConn(), BIND_AUTO_CREATE);
}
public void click(View v){
}
private class MyConn implements ServiceConnection{
//當服務被連結成功的時候調用
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
以上是調用本地服務的操作步驟,下面利用aidl實作本地應用通路遠端服務。
首先,将定義方法的接口類Iservice檔案的字尾名改成.aidl,這時候重新整理下工程,Iservice檔案會報錯,如下圖:
從上圖可以看出,需要将public關鍵字去掉,這樣我們的Iservice就不會報錯。為什麼不能使用public呢?既然Iservice這個aidl是用來解決程序間通信的,那麼它肯定是public的,是以不需要加上public。
這時候到工程目錄的gen目錄下可以看到Iservice.java這個檔案,打開看一下,如下圖:
從上圖可以看到生成的檔案中,有一個Stub類,繼承Binder類并且實作Iservice接口。這時候我們發現,自定義的中間人可以繼承這個Stub類。
接下來我們要在本地應用中通過中間人來通路遠端服務,那麼如何保證本地應用的中間人和遠端服務的中間人是同一個呢?谷歌規定,隻要在本地應用中,包名和aidl檔案名和遠端服務一緻就可以了。
遠端服務的包名:
本地應用中需要建立和遠端服務一樣的包名,然後将Iservice.aidl檔案拷貝到本地應用中建立的包中:
做完以上操作後,在本地應用的gen目錄下就可以看到自動生成的Iservice.java檔案,有了這個檔案,就可以調用遠端服務中的方法,如下圖:
在本地應用的MainActivity中完善代碼:
public class MainActivity extends Activity {
private Iservice iservice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
//通過設定intent的action來指定打開的服務,這邊需要在遠端服務的項目的清單檔案中配置這個action
intent.setAction("com.itheima.remoteservice");
bindService(intent, new MyConn(), BIND_AUTO_CREATE);
}
public void click(View v){
try {
//調用中間人對象調用遠端服務中的方法
iservice.callRemoteMethod();
} catch (RemoteException e) {
e.printStackTrace();
}
}
private class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
//通過Iservice.Stub.asInterface(service)方法擷取IBinder對象
iservice = Iservice.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
在遠端服務的項目的清單檔案中配置這個action
<service android:name="com.itheima.remoteservice.RemoteService">
<intent-filter >
<action android:name="com.itheima.remoteservice"/>
</intent-filter>
</service>
運作效果:
首先部署遠端服務到手機中,然後部署本地服務,點選按鈕後,檢視日志,發現調用了遠端服務中的方法。如下圖:
10. 遠端服務的應用場景
10.1. 支付寶服務
遠端服務最常見的應用場景就是支付寶,本案例模拟支付寶支付過程。
建立Iservice接口,在接口中定義中間人對象需要實作的方法:
public interface Iservice {
public boolean callPay(String name,String pwd,int money);
}
将Iservice檔案字尾名改為.aidl,取消通路修飾符public,如下圖:
定義支付寶的服務,在服務中定義pay方法:
public class ALiPayService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public void onDestroy() {
super.onDestroy();
}
//定義pay方法,傳回值為boolean類型,用來判斷支付結果(成功或失敗)
public boolean pay(String name,String pwd,int money){
System.out.println("檢查使用者名和密碼是否正确....");
System.out.println("檢查手機是否有病毒....");
System.out.println("檢查餘額是否夠用....");
if ("abc".equals(name)&&"123".equals(pwd)&&money>) {
return true;
}else {
return false;
}
}
}
在服務中定義中間人對象MyBinder類,直接繼承Stub類,實作方法:
private class MyBinder extends Stub{
@Override
public boolean callPay(String name, String pwd, int money) {
//調用Service中的pay方法
return pay(name, pwd, money);
}
}
注冊支付寶服務,需要在AndroidManifest.xml檔案中配置,并且加上意圖過濾器:
<service android:name="com.itheima.alipay.ALiPayService">
<intent-filter >
<action android:name="com.itheima.alipay"/>
</intent-filter>
</service>
10.2. 其他應用調用支付寶服務
接下來模拟“歡樂鬥地主”應用調用支付寶支付,在歡樂鬥地主項目工程目錄下建立與遠端服務同名的包,将遠端服務的aidl檔案拷貝到建立的包下:
綁定到遠端服務,點選按鈕調用遠端服務的支付方法支付:
public class MainActivity extends Activity {
private MyConn myConn;
private Iservice iservice
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("com.itheima.alipay");
myConn = new MyConn();
bindService(intent, myConn, BIND_AUTO_CREATE);
}
public void click(View v){
try {
boolean result = iservice.callPay("abc", "123", );
if (result) {
Toast.makeText(getApplicationContext(), "夠買歡樂豆成功", ).show();
}else {
Toast.makeText(getApplicationContext(), "夠買失敗", ).show();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
private class MyConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
iservice = Iservice.Stub.asInterface(service);
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
我們調用支付功能,支付501,運作結果:
我們将支付金額改成5001,重新部署項目,再次調用支付功能,運作結果: