Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Service組件開發用到的幾個知識點

Android Service組件開發用到的幾個知識點

編輯:關於Android編程

啟動方式

啟動Local Service(Client跟Service在同一個進程)

這類服務有個特點,就是它主要用於為某一個客戶(Activity)提供單獨的後台服務;

Context.startService()啟動

如果調用該方法時對應Service還沒有創建出來,則會調用onCreate方法;即Service只會創建一個實例;
調用多少次該方法就對應調用多少次Service端的onStartCommand方法;

Context.stopService()結束

無論之前調用了多少次startService,只需要調用一次該方法

 

啟動Remote Service(Client跟Service不在同一個進程)

這類服務有個特點,就是它會定義一些接口並把接口暴露出來,以便其他應用進行操作;

多個客戶端可以綁定同一個服務;

Context.bindService()方法建立連接,並啟動

如果調用該方法時對應Service還沒有創建出來,則會調用onCreate方法;即Service只會創建一個實例;
Context如果之前沒有和該Service綁定過,即執行bindService方法,那麼就會執行Service的bindService方法;如果綁定過了就不會執行onBind方法

Context.unbindService()關閉連接

注意:

上面只是默認一種約定行為;如果是Remote Service建議使用bindService,因為bindService可以獲取 Remote Service提供的接口;如果是Local Service建議使用sartService,因為startService很簡單;

 

生命周期

Part1(純粹的Start)

啟動:startService方法啟動
終止:調用stopService,或自身的stopSelf方法,系統資源不足android系統結束服務;(執行Service的onDestroy方法)

Part2(純粹的Bind)

啟動:bindService方法啟動
終止:調用unbindService斷開連接,之前調用bindService 的 Context 不存在了(如Activity被finish的時候);(執行Service的unBind和onDestroy方法)

Part3(混合的Start&Bind)

啟動:如先startService,後bindService
終止:(調用stopService 或者 調用stopSelf)並且(不再有綁定的連接)==》onDestroy方法將會被調用;

注意:

Activity旋轉的時候,意味著Activity重建了;旋轉之前的使用 bindService 建立的連接便會斷開(Context 不存在了);
使用了startService啟動Service就需要調用stopService關閉服務;使用了bindService啟動Service就需要調用unbindService關閉服務;它們都是成對出現的!!!
這裡對bindService啟動的Service會和啟動它的Context共生死,這一點進行一下說明;如果ActivityA進行了bindService,ActivityB也bindService,那麼當ActivityA進行了unbindService時,Service並不會調用onDestroy,因為它還有個bind沒有解除(即跟ActivityB之間的bind)~;但是activityA跳轉到ActivityB,有可能導致activityA被系統銷毀,這樣即使activityA代碼中沒有調用unbind,系統也會自動將activityA和service解綁,進而可能service就onDestroy了;當Activity2在bind的時候該service已經不存在了,最終還是要去創建一個service實例;為了避免上述問題這裡建議,通過startService啟動service,隨後bindService進行activity和service的綁定,這樣即使activity內容被銷毀了,service還是不會調用onDestroy方法!

實現原理

Local Service:(Client和Service在同一個進程中)

Service端:

編寫類繼承android.app.Service類;實現onCreate、onbind、onStartCommand、onDestroy等方法
在Manifest.xml文件中注冊該Service:如下



    
         
         
    


 

Client端:

bindService方式啟動(關閉調用對應的unbindService())
bindService(new Intent(ServiceDemo.ACTION).setPackage(getPackageName()), conn, BIND_AUTO_CREATE);
參數1說明:ServiceDemo.ACTION=="com.demo.SERVICE_DEMO";即上面Service在Manifest的定義的Action!public Intent (String action);Android5.0不支持隱式調用,需要指定packegename,如上面的方式,packegename指明了在哪個包中找和Action對應的組件,如果不指名package那麼則在當前系統全局搜索,每個app對應一個package,定義在Manifest標簽裡面,此package並非指我們想要啟動服務類所在的包名;
或者用Intent intent = new Intent(); intent.setClass(this,MyLocalService.class);的方式啟動服務;之前我們經常用這個方式,而不是用Action;其實這部分是Intent的內容,可以去研究一下intent的使用方法和構造器!
參數3說明:BIND_AUTO_CREATE,即當服務不存在時,自動創建,如果服務已經啟動了或者創建了,那麼只會掉調用onBind方法
參數2說明:conn是一個實現了ServiceConnection接口的對象;實現了下面兩個方法
onServiceConnected(ComponentName name, IBinder service)
客戶端調用bindService;Service端對應調用onBind方法;如果onBind方法返回值不為null則執行這裡的onServiceConnected方法!(就是該方法的第二個參數,就是onBind方法的返回值);無論這裡是不是執行onServiceConnected方法,都已經完成了客戶端和Service的綁定,最後還是需要調用unBindService進行解綁;
通過該方法的第二個參數service可以獲得Service提供的接口!!如:
MyService.SimpleBinder sBinder = (MyService.SimpleBinder)service;
//SimpleBinder是MyService的內部類,繼承了Binder類,定義了add方法;
sBinder.add(3, 5);
onServiceDisconnected(ComponentName name)
同上
startService方式啟動(關閉調用對應的stopService())
startService(new Intent(ServiceDemo.ACTION)).setPackage(getPackageName());

Remote Service:(Client和Service不在同一個進程中)(進程間通信毫無疑問使用了Binder)

Service端:

編寫一個服務接口,不需要繼承任何接口(假設該接口名為: ITestRemoteService);定義該服務會提供的服務(如一個add方法);以aidl格式保存!!
android會自動將該文件轉換為java文件,並添加一些必要的內容,使其能夠進行IPC(我們需要做的就是寫aidl文件,隨後編譯一下工程即可!)
IPC即進程間通信,底層通過Binder技術實現
編寫類繼承android.app.Service類;
有一個如下的Field:private ITestRemoteService.Stub stub= new ITestRemoteService.Stub(){ ..//實現了上面定義的如add方法 }
上面的ITestRemoteService是android對aidl文件轉換為java文件後得到的java類,類中自動生成了一個Stub內部類;
在實現的onbind方法中返回上面生成的stub;
實現onCreate、onStartCommand、onDestroy等方法
在Manifest.xml文件中注冊該Service


    
         
         
    
注意:對於android:process="remote"和android:process=":remote"的區別
android:process=":remote",代表在應用程序裡,當需要該service時,會自動創建新的進程。
android:process="remote",沒有“:”分號的,則創建全局進程,不同的應用程序共享該進程。
具體的因為解釋參見:http://wear.techbrood.com/guide/topics/manifest/service-element.html

Client端:

 

 

bindService方式啟動(關閉調用對應的unbindService())
bindService(intent, conn,Context.BIND_AUTO_CREATE);
參數1說明:Intent intent = new Intent("com.yq.RemoteService").setPackage(getPackageName());//packegename指明了在哪個包中找和Action對應的組件,如果不指名package那麼則在當前系統全局搜索,每個app對應一個package,定義在Manifest標簽裡面,此package並非指我們想要啟動服務類所在的包名;
參數2說明:BIND_AUTO_CREATE,即當服務不存在時,自動創建,如果服務已經啟動了或者創建了,那麼只會調用onBind方法
參數3說明:conn是一個實現了ServiceConnection接口的對象;實現了下面兩個方法
onServiceConnected(ComponentName name, IBinder service)
客戶端調用bindService;Service端對應調用onBind方法;如果onBind方法返回值不為null則執行這裡的onServiceConnected方法!(就是該方法的第二個參數,就是onBind方法的返回值);無論這裡是不是執行onServiceConnected方法,都已經完成了客戶端和Service的綁定,最後還是需要調用unBindService進行解綁;
通過該方法的第二個參數service可以獲得Service提供的接口!!如:
ITestRemoteService remoteService = ITestRemoteService.Stub.asInterface(service);
ITestRemoteService就是我們定義了的接口,裡面定義了add方法;
之後我們的Client就獲得了這個Service,就可以任意時間訪問對應的服務了~~
onServiceDisconnected(ComponentName name)
同上
startService方式啟動(關閉調用對應的stopService())
startService(intent);
參數說明:Intent intent = new Intent(); intent.setClassName("com.demo","com.demo.SERVICE_DEMO");
可以發現如果通過startService啟動服務那麼就客戶端沒有任何返回可以收到!只能單向的向Service發送一個信號;

 

 

相關概念的區分(易模糊點)

StartService和bindService啟動方法使用場景:

如果你只是想要啟動一個後台服務長期進行某項任務那麼使用 startService便可以了;證明startService啟動服務,你的期望不要太高;因為它不給客戶端任何反饋;你可以通過broadcast來向客戶端傳數據,然而BroadcastReceiver 本身執行代碼的時間是很短的!!因此不能對該方法期望過高!!總之startService很弱很弱的;
想要與正在運行的 Service 取得聯系,推薦我們的終極武器bindService!!bindService很好很強大;

 

AndroidManifest.xml 裡 Service 元素的常見選項

android:name,服務類名
android:label,服務的名字,如果此項不設置,那麼默認顯示的服務名則為類名
android:process,表示該服務是否運行在另外一個進程,如果設置了此項,那麼將會在包名後面加上這段字符串表示另一進程的名字
android:enabled,如果此項設置為 true,那麼 Service 將會默認被系統啟動,不設置默認此項為 false
android:exported,表示該服務是否能夠被其他應用程序所控制或連接,不設置默認此項為 false
android:icon,服務的圖標

 

Service&Thread

Thread:Thread 是程序執行的最小單元,它是分配CPU的基本單位。可以用 Thread 來執行一些異步的操作。
Service 是android的一種機制,當它運行的時候如果是Local Service,那麼對應的Service 是運行在主進程的 main 線程上的(運行耗時操作會導致ANR);如果是Remote Service,那麼對應的 Service 則是運行在獨立進程的 main 線程上;
用Service而不用Thread的好處就是我們可以隨時通過BindService或者startService跟Service進行通信,換成Thread一旦start我們將無能為力了~;Service很乖,Thread很野;
Service的職責是負責後台的耗時的工作;
Android的後台就是指,它的運行是完全不依賴UI的,即使Activity被銷毀,或者程序被關閉,只要進程還在,Service就可以繼續運行;所以在Service中創建Thread比在Acitivity中創建線程的好處在於,Service在一個應用中是穩定存在的,而Activity朝不保夕(應用中經常有各種Activity之間的跳轉操作),一旦它銷毀了,那麼它所創建的線程,將處於游離狀態,不受控制,Service不會出現這樣的問題;

一個比較好的Service往往是如下的樣子:

 

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 開始執行後台任務  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  
class MyBinder extends Binder {  
    public void startDownload() {  
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 執行具體的下載任務  
            }  
        }).start();  
    }  
}  

 

 

前台Service和後台Service的區別於實現

後台Service存在被回收的隱患

我們啟動的Service一般都是後台運行的Service;因為Service的系統優先級還是比較低的,當系統出現內存不足情況時,就有可能會回收掉正在後台運行的Service;

前台Service避免被回收的隱患

前台Service和普通Service最大的區別就在於,它會一直有一個正在運行的圖標在系統的狀態欄顯示,下拉狀態欄後可以看到更加詳細的信息,非常類似於通知的效果;這讓人想到,小米一個rom開發工程師控訴qq雖然進入了後台,但是在屏幕中展示一個像素點,使得該應用永遠處於前台,所以Service永遠不會被清除掉!!

創建一個前台Service(以下操作都是在Service裡面進行的)

 

創建一個Notification
CharSequence text = getText(R.string.remote_service_started);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, Controller.class), 0); //指定點擊該Notification的時候,應該跳轉到的Activity
Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.drawable.stat_sample)  // the status icon
                .setTicker(text)  // the status text
                .setWhen(System.currentTimeMillis())  // the time stamp
                .setContentTitle(getText(R.string.local_service_label))  // the label of the entry
                .setContentText(text)  // the contents of the entry
                .setContentIntent(contentIntent)  // The intent to send when the entry is clicked
                .build();
讓Service變成一個前台Service,並會將通知顯示出來
startForeground(R.string.remote_service_started, notification); //等價於NotificationManager.notify(int, Notification)
讓Service變成一個後台Service,並會將通知從通知欄撤銷
stopForeground(true); //該動作不會使得Service被destroy但是將Service轉為後台應用

 

遠程Service和本地Service的優劣比較

ANR問題

ANR問題,本地Service會運行本進程的主線程即UI線程中,因此運行耗時程序,觸發ANR;如果是遠程Service則阻塞的是另外一個進程的主線程,對當前進程無影響,因此不會觸發ANR;

遠程Service比本地Service要好?

其實遠程Service實現起來比本地Service要復雜一點,大體流程如下,具體實現代碼參考:

本地service:

寫一個繼承Service類的子類
xml文件注冊該Serive
客戶端startService、bindServcie;傳入的intent為(this. MyService.class)的形式

遠程Service:

寫一個aidl文件(RemoteServce.aidl),內容就是定義的一個普通java接口的代碼;隨後編譯得到一個對應同名的java文件(RemoteService.java)
寫一個實現了RemoteService.Stub接口的類(RemoteServiceImp)
寫一個繼承Service類的子類,該類中有一個RemoteServiceImp的對象,並在onBind方法中返回該對象
xml注冊上面的service:注意添加標簽android:process =":remote"android:name = "com.evan.MyService"
客戶端startService、bindServcie;傳入的intent為(this."com.evan.MyService")的形式
ServiceConnection中得到的IBinder對象,轉換RemoteServiceImp remoteService =RemoteService.Stub.AsInterface(ibinder);

Client和Service之間通信方式的概述:

通過startAcitvity啟動Service,可以傳輸一個Intent過去;這是單向的傳輸,適用於命令模式;
通過bindActivity啟動Service,可以利用ServiceConnection接口,回調得到Iservice對象(注意這裡的Iservice是我們定義的一個接口),通過該對象我們可以獲取到所有Service向外界提供的服務;
用法一:(Android關於Serive給的例子中有這個案例)
獲得來自Service的一個android.os.Messenger對象;該Messenger跟Handler使用方法類似,其實Messenger的某一個構造器參數就可以是一個Handler對象;
這樣我們就可以利用該Messenger向Remote Service發送信息了;
Client也可以創建一個Messenger,將該Messenger注冊到Remote Service那邊,這樣Service就可以通過這個Messenger向Client發送數據了;上面就實現了雙向通行
注意:服務端Messager就是通過Handler來構建的;利用mMessenger.getBinder()返回IBinder對象;客戶單使用IBinder對象構造出Messager對象;而且Messager實現了Parcel接口!!意味著可以利用Binder在進程間進行傳遞了。
用法二:利用獲得的Iservice對象,調用其中的服務接口,獲取相關數據,或者發送某種命令;
跟StartActivity的區別:可以得到服務的返回值,因為是實實在在的調用了Service的一個方法;
跟StartActivity相同的地方:Client占據了主動權;
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved