前言
最近有很多朋友問我這個AIDL怎么用,也許由於是工作性質的原因,很多人都沒有使用過aidl,所以和他們講解完以后,感覺對方也是半懂不懂的,所以今天我就從淺到深的分析一下這個aidl具體是怎么用的,希望對大家有幫助。
作為一名合格Android開發人員,如果沒聽過Service,那就有點說不過去了啊,Service是Android四大組件之一,它是不依賴於用戶界面的,就是因為Service不依賴與用戶界面,所以我們常常用於進行一些耗時的操作,比如:下載數據等;
有些朋友可能是從事開發工作的時間不是特別的長,所以覺得Service相對與另外兩個組件activity、broadcast receiver來說,使用可能並不是特別的多,所以對Service來說,理解不是特別的深入,只是有一個大概的概念,今天就和一塊來走一下Service,希望能夠幫助到大家對Service有更深入的理解。
Service基本用法——本地服務
我們知道服務分為本地服務和遠程服務,而本地服務由於它的啟動方式不一樣,所以生命周期也就不一樣,對Service生命周期不熟悉的朋友,自行去百度一下啊。好了,那么我們分別看一下兩種不同的啟動方式。我們先創建好Service:
ServiceTest.java
1 package com.example.administrator.servicetestaidl; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.support.annotation.IntDef; 7 import android.util.Log; 8 9 public class ServiceTest extends Service { 10 11 12 @Override 13 public void onCreate() { 14 super.onCreate(); 15 Log.d("ServiceTest"," -----> onCreate"); 16 } 17 18 19 @Override 20 public int onStartCommand(Intent intent,int flags, int startId) { 21 22 Log.d("ServiceTest"," -----> onStartCommand"); 23 24 return super.onStartCommand(intent, flags, startId); 25 } 26 27 28 @Override 29 public void onDestroy() { 30 super.onDestroy(); 31 32 Log.d("ServiceTest"," -----> onDestroy"); 33 34 } 35 36 @Override 37 public IBinder onBind(Intent intent) { 38 // TODO: Return the communication channel to the service. 39 throw new UnsupportedOperationException("Not yet implemented"); 40 } 41 }
在看看MainActivity的代碼:
1 package com.example.administrator.servicetestaidl; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 import android.widget.Button; 8 9 public class MainActivity extends Activity { 10 private Button startService, stopService; 11 12 13 @Override 14 protected void onCreate(Bundle savedInstanceState) { 15 super.onCreate(savedInstanceState); 16 setContentView(R.layout.activity_main); 17 18 startService = (Button) findViewById(R.id.start_service); 19 stopService = (Button) findViewById(R.id.stop_service); 20 21 22 /** 23 * 開啟服務 24 */ 25 startService.setOnClickListener(new View.OnClickListener() { 26 @Override 27 public void onClick(View v) { 28 Intent startService = new Intent(MainActivity.this,ServiceTest.class); 29 startService(startService); 30 31 } 32 }); 33 34 35 /** 36 * 停止服務 37 */ 38 stopService.setOnClickListener(new View.OnClickListener() { 39 @Override 40 public void onClick(View v) { 41 Intent stopService = new Intent(MainActivity.this,ServiceTest.class); 42 stopService(stopService); 43 } 44 }); 45 46 47 } 48 }
布局activity_main
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical"> 6 7 <Button 8 android:id="@+id/start_service" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:text="開啟服務" /> 12 13 <Button 14 android:id="@+id/stop_service" 15 android:layout_width="wrap_content" 16 android:layout_height="wrap_content" 17 android:text="停止服務" /> 18 19 </LinearLayout>
配置文件AndroidManifest.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.example.administrator.servicetestaidl"> 4 5 <application 6 android:allowBackup="true" 7 android:icon="@mipmap/ic_launcher" 8 android:label="@string/app_name" 9 android:roundIcon="@mipmap/ic_launcher_round" 10 android:supportsRtl="true" 11 android:theme="@style/AppTheme"> 12 <activity android:name=".MainActivity"> 13 <intent-filter> 14 <action android:name="android.intent.action.MAIN" /> 15 16 <category android:name="android.intent.category.LAUNCHER" /> 17 </intent-filter> 18 </activity> 19 20 <service 21 android:name=".ServiceTest" 22 android:enabled="true" 23 android:exported="true"></service> 24 </application> 25 26 </manifest>
上面的代碼很簡單,並不難理解,在頁面上加兩個按鈕,一個是啟動服務,一個是銷毀服務的,並且我們在ServiceTest里面的幾個方法都加上了log,那我們點擊開啟服務,看看Log,如圖:
然后我們多次點擊開啟服務,如圖:
我們看到,后面即使多點幾下這個開啟服務,但是也只會調onStartCommand方法,onCreate方法並不會重復調用,那是因為我們點擊Service,由於該service已經存在,所以並不會重新創建,所以onCreate方法只會調用一次。
我們還可以到手機的應用程序管理界面來檢查一下Service是不是正在運行,如下圖所示:
那當我們點擊停止服務按鈕呢,看看log:如圖
這時候說明了服務已經銷毀了。
有些朋友可能注意到了,我們剛剛那種啟動服務的方式,好像除了對Service進行開啟和銷毀以外,很難在activity里進行對Service進行控制,什么意思呢?舉個例子,如果說
我現在用Service進行下載某些東西,我現在在Service寫有下載這兩個東西的方法,方法a,方法b,那么我怎樣在activity里面控制什么時候調用方法a,什么時候調用方法b呢,如果
按照原本的啟動方式,好像並不好實現,或者說靈活性很差,那么有沒有辦法辦到呢,接着看Service另一種啟動方式
在前面我們有一個方法一直都沒有動onBind方法,我們就從這個方法入手,先看ServiceTest代碼:
1 package com.example.administrator.servicetestaidl; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.support.annotation.IntDef; 8 import android.util.Log; 9 10 public class ServiceTest extends Service { 11 12 13 @Override 14 public void onCreate() { 15 super.onCreate(); 16 Log.d("ServiceTest"," -----> onCreate"); 17 } 18 19 20 @Override 21 public int onStartCommand(Intent intent,int flags, int startId) { 22 23 Log.d("ServiceTest"," -----> onStartCommand"); 24 25 return super.onStartCommand(intent, flags, startId); 26 } 27 28 29 @Override 30 public void onDestroy() { 31 super.onDestroy(); 32 33 Log.d("ServiceTest"," -----> onDestroy"); 34 35 } 36 37 @Override 38 public IBinder onBind(Intent intent) { 39 40 return new Mybind(); 41 } 42 43 44 class Mybind extends Binder{ 45 public void getString(){ 46 Log.d("ServiceTest"," -----> getString"); 47 } 48 } 49 50 51 52 }
在ServiceTest中增加了一個內部類Mybind,並且在Mybind中增加一個getString方法,在方法中打印log,然后在onBind方法中返回Mybind對象。
再看看MainActivity的代碼
1 package com.example.administrator.servicetestaidl; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.view.View; 10 import android.widget.Button; 11 12 public class MainActivity extends Activity { 13 14 private Button startService,stopService,bindService,unbindService; 15 private ServiceTest.Mybind mybind; 16 17 18 private ServiceConnection connection = new ServiceConnection() { 19 @Override 20 public void onServiceConnected(ComponentName name, IBinder service) { 21 mybind = (ServiceTest.Mybind) service; 22 mybind.getString(); //獲取到getString方法 23 } 24 25 @Override 26 public void onServiceDisconnected(ComponentName name) { 27 28 } 29 }; 30 31 32 33 34 @Override 35 protected void onCreate(Bundle savedInstanceState) { 36 super.onCreate(savedInstanceState); 37 setContentView(R.layout.activity_main); 38 39 startService = (Button) findViewById(R.id.start_service); 40 stopService = (Button) findViewById(R.id.stop_service); 41 bindService = (Button) findViewById(R.id.bind_service); 42 unbindService = (Button) findViewById(R.id.unbind_service); 43 44 45 /** 46 * 開啟服務 47 */ 48 startService.setOnClickListener(new View.OnClickListener() { 49 @Override 50 public void onClick(View v) { 51 Intent startService = new Intent(MainActivity.this,ServiceTest.class); 52 startService(startService); 53 54 } 55 }); 56 57 58 /** 59 * 停止服務 60 */ 61 stopService.setOnClickListener(new View.OnClickListener() { 62 @Override 63 public void onClick(View v) { 64 Intent stopService = new Intent(MainActivity.this,ServiceTest.class); 65 stopService(stopService); 66 } 67 }); 68 69 /** 70 * 綁定服務 71 */ 72 bindService.setOnClickListener(new View.OnClickListener() { 73 @Override 74 public void onClick(View v) { 75 Intent bindService = new Intent(MainActivity.this,ServiceTest.class); 76 bindService(bindService,connection,BIND_AUTO_CREATE); 77 } 78 }); 79 80 /** 81 * 解綁服務 82 */ 83 unbindService.setOnClickListener(new View.OnClickListener() { 84 @Override 85 public void onClick(View v) { 86 unbindService(connection); 87 } 88 }); 89 } 90 }
主頁面布局:activity_main
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical"> 6 7 <Button 8 android:id="@+id/start_service" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:text="開啟服務" /> 12 13 <Button 14 android:id="@+id/stop_service" 15 android:layout_width="wrap_content" 16 android:layout_height="wrap_content" 17 android:text="停止服務" /> 18 19 <Button 20 android:id="@+id/bind_service" 21 android:layout_width="wrap_content" 22 android:layout_height="wrap_content" 23 android:text="綁定服務" /> 24 25 <Button 26 android:id="@+id/unbind_service" 27 android:layout_width="wrap_content" 28 android:layout_height="wrap_content" 29 android:text="解綁服務" /> 30 31 </LinearLayout>
可以看到,這里我們首先創建了一個ServiceConnection的匿名類,在里面重寫了onServiceConnected()方法和onServiceDisconnected()方法,這兩個方法分別會在Activity與Service建立關聯和解除關聯的時候調用。在onServiceConnected()方法中,我們又通過向下轉型得到了MyBind的實例,有了這個實例,Activity和Service之間的關系就變得非常緊密了。現在我們可以在Activity中根據具體的場景來調用MyBind中的任何public方法,即實現了Activity指揮Service干什么Service就去干什么的功能。
當我們點擊綁定服務的時候,結果如下,如圖
點擊解綁服務的時候,結果如下,如圖
注意:Service 是運行在后台,沒有可視化的頁面,我們很多時候會把耗時的操作放在Service中執行,但是注意,Service是運行在主線程的,不是在子線程中,
Service和Thread沒有半毛錢的關系,所以如果在Service中執行耗時操作,一樣是需要開起線程,否則會引起ANR,這個需要區別開來。
好了~~~本地服務基本到這就結束,在后面我會將本地服務這部分代碼的鏈接發出來,需要的可以進行下載~~
遠程服務 —— AIDL
AIDL(Android Interface Definition Language)是Android接口定義語言的意思,它可以用於讓某個Service與多個應用程序組件之間進行跨進程通信,從而可以實現多個應用程序共享同一個Service的功能。實際上實現跨進程之間通信的有很多,
比如廣播,Content Provider,但是AIDL的優勢在於速度快(系統底層直接是共享內存),性能穩,效率高,一般進程間通信就用它。
既然是跨進程,那必須的有兩個應用,一個是service端,一個是client端,然后實現客戶端從服務端獲取數據。那么我們創建一個服務端,項目結構如圖所示:
服務端
我們在服務端下建立一個MyAIDLService.aidl文件,目錄結構為如圖所示:
然后,我們在MyAIDLService下增加一個獲取字符串的方法。代碼如下:(注:剛剛建立的aidl文件中存在一個方法,那個方法可以忽略,可以刪掉不要)
1 // MyAIDLService.aidl 2 package aidl; 3 4 // Declare any non-default types here with import statements 5 6 interface MyAIDLService { 7 //獲取String數據 8 String getString(); 9 }
創建完aidl文件以后,我們build一下項目,然后會在build - >generated ->source ->aidl->debug下會生成一個aidl文件,那說明AIDL文件已經編譯成功。
接着建立一個MyService類,代碼如下:
1 package com.example.service; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.os.RemoteException; 8 import android.util.Log; 9 10 import java.util.Map; 11 12 import aidl.MyAIDLService; 13 14 public class MyService extends Service { 15 16 @Override 17 public void onCreate() { 18 super.onCreate(); 19 } 20 21 22 @Override 23 public int onStartCommand(Intent intent, int flags, int startId) { 24 return super.onStartCommand(intent, flags, startId); 25 } 26 27 28 @Override 29 public void onDestroy() { 30 super.onDestroy(); 31 } 32 33 @Override 34 public IBinder onBind(Intent intent) { 35 return new Mybind(); 36 } 37 38 class Mybind extends MyAIDLService.Stub { 39 40 @Override 41 public String getString() throws RemoteException { 42 String string = "我是從服務起返回的"; 43 44 return string; 45 } 46 } 47 }
代碼看起來是不是很熟悉,唯一不一樣的就是原來在本地服務的時候內部類繼承的是Binder,而現在繼承的是MyAIDLService.Stub,繼承的是我們剛剛建立的aidl文件,然后實現我們剛剛的定義的
getString()方法,在這里,我們只是返回一句話,"我是從服務起返回的"~~~~~~~~~~~
客戶端
首先將剛剛在服務端創建的MyAIDLService原封不動的復制到客戶端來。(注意:路徑要一模一樣)。接着我們在客戶端的MainActivity中加兩個按鈕,並且和服務端進行相連,代碼如下:
MainActivity
1 package com.example.administrator.servicetestaidl; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.content.ServiceConnection; 7 import android.os.Bundle; 8 import android.os.IBinder; 9 import android.os.RemoteException; 10 import android.view.View; 11 import android.widget.Button; 12 import android.widget.TextView; 13 14 import aidl.MyAIDLService; 15 16 public class MainActivity extends Activity { 17 18 private Button bindService,unbindService; 19 private TextView tvData; 20 private MyAIDLService myAIDLService; 21 22 23 private ServiceConnection connection = new ServiceConnection() { 24 @Override 25 public void onServiceConnected(ComponentName name, IBinder service) { 26 myAIDLService = MyAIDLService.Stub.asInterface(service); 27 try { 28 String str = myAIDLService.getString(); 29 tvData.setText(str); 30 } catch (RemoteException e) { 31 e.printStackTrace(); 32 } 33 } 34 35 @Override 36 public void onServiceDisconnected(ComponentName name) { 37 myAIDLService = null; 38 } 39 }; 40 41 42 43 44 @Override 45 protected void onCreate(Bundle savedInstanceState) { 46 super.onCreate(savedInstanceState); 47 setContentView(R.layout.activity_main); 48 49 bindService = (Button) findViewById(R.id.bind_service); 50 unbindService = (Button) findViewById(R.id.unbind_service); 51 tvData = (TextView) findViewById(R.id.tv_data); 52 53 54 /** 55 * 綁定服務 56 */ 57 bindService.setOnClickListener(new View.OnClickListener() { 58 @Override 59 public void onClick(View v) { 60 Intent intent = new Intent(); 61 intent.setAction("com.example.service.MyService"); 62 //從 Android 5.0開始 隱式Intent綁定服務的方式已不能使用,所以這里需要設置Service所在服務端的包名 63 intent.setPackage("com.example.service"); 64 bindService(intent, connection, BIND_AUTO_CREATE); 65 66 67 68 } 69 }); 70 71 /** 72 * 解綁服務 73 */ 74 unbindService.setOnClickListener(new View.OnClickListener() { 75 @Override 76 public void onClick(View v) { 77 unbindService(connection); 78 } 79 }); 80 } 81 }
大家是不是感覺和連接本地服務的代碼差不多,沒錯,這里只需要注意兩個地方,一個是綁定服務的時候,因為從 Android 5.0開始 隱式Intent綁定服務的方式已不能使用,所以這里需要設置Service所在服務端的包名
那么這個action是怎么來的呢,我們回來服務端的AndroidManifest.xml,代碼如下
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.example.service"> 4 5 <application 6 android:allowBackup="true" 7 android:icon="@mipmap/ic_launcher" 8 android:label="@string/app_name" 9 android:roundIcon="@mipmap/ic_launcher_round" 10 android:supportsRtl="true" 11 android:theme="@style/AppTheme"> 12 <activity android:name=".MainActivity"> 13 <intent-filter> 14 <action android:name="android.intent.action.MAIN" /> 15 16 <category android:name="android.intent.category.LAUNCHER" /> 17 </intent-filter> 18 </activity> 19 20 <service 21 android:name=".MyService" 22 > 23 <intent-filter> 24 <action android:name="com.example.service.MyService" /> 25 </intent-filter> 26 27 </service> 28 </application> 29 30 </manifest>
另一個需要注意的就是獲取MyAIDLService對象是通過MyAIDLService.Stub.asInterface(service);這里大家需要注意一下的。
不過還有一點需要說明的是,由於這是在不同的進程之間傳遞數據,Android對這類數據的格式支持是非常有限的,
基本上只能傳遞Java的基本數據類型、字符串、List或Map等。那么如果我想傳遞一個自定義的類該怎么辦呢?這就必須要讓這個類去實現Parcelable接口,
並且要給這個類也定義一個同名的AIDL文件。這部分內容並不復雜,而且和Service關系不大,所以就不再詳細進行講解了,感興趣的朋友可以自己去查閱一下相關的資料。
注意:從服務器復制過來的aidl文件不能直接放到Java文件夾下面,必須建立一個aidl文件夾存放,否則會編譯不成功
好了,到這里,基本上就結束了,附上一張效果圖:
最后附上源碼鏈接
本地服務源碼:https://github.com/343661629/nativeService
遠程服務源碼:https://github.com/343661629/remoteService