前言
Android平臺跨進程方式有很多,比如可以通過Intent附加extras來傳遞信息样漆,也可以通過共享文件的方式來共享數(shù)據(jù),還可以采用Binder來進行跨進程通訊,另外连茧,ContentProvider天生就是支持跨進程訪問的核蘸,所以我們也可以通過它來進行IPC通信。此外啸驯,通過網(wǎng)絡(luò)來傳遞數(shù)據(jù)也是可以的客扎,所以,我們也可以通過Socket來實現(xiàn)IPC通訊罚斗。
Bundle
我們知道徙鱼,四大組件的三大組件(Activity,Service,BroadCastReciver)都是支持在Intent中傳遞Bundle數(shù)據(jù)的,由于Bundle實現(xiàn)了Parcelable接口针姿,所以它可以在方便的在不同的進程中傳輸袱吆,基于這一點,當我們在另一個進程中啟動Activity,Service,BroadCastReciver時距淫,我們可以通過Bundle附加我們需要傳輸?shù)臄?shù)據(jù)绞绒,從而實現(xiàn)跨進程通訊。當然榕暇,我們需要傳輸?shù)臄?shù)據(jù)必須是可序列化的蓬衡。
使用文件共享
兩個進程之間可以通過讀寫文件來交換數(shù)據(jù),Android平臺是基于Linux的彤枢,并不限制并發(fā)讀/寫狰晚,使得我們可以隨心所欲的在兩個進程間交換文件。我們只需要注意下可能存在的并發(fā)讀/寫的問題就可以了缴啡。
使用Messenger
Messenger是一種輕量級的IPC方案家肯,它的底層是AIDL,系統(tǒng)為我們做了封裝,使用起來比較方便盟猖。使用Messenger在不同進程間傳遞數(shù)據(jù)讨衣,傳遞的是Message對象,所以需要我們將我們想要傳遞的數(shù)據(jù)放入Message對象式镐,Message對象能使用的載體只有what反镇,arg1,arg2娘汞,replyTo歹茶,Bundle以及object,object只支持系統(tǒng)提供的實現(xiàn)了Parcelable接口的對象你弦,我們自定義實現(xiàn)了Parcelable的對象不能使用惊豺。
Messenger構(gòu)造方法
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
使用Messenger實現(xiàn)跨進程通訊
客戶端
首先,我們需要綁定服務(wù)端的Service禽作,綁定成功后尸昧,通過服務(wù)端返回的Binder對象,可以創(chuàng)建出一個Messenger對象旷偿,通過Messenger.send()方法便可以向服務(wù)端傳遞一個Message對象烹俗,這樣就可以實現(xiàn)往服務(wù)端傳遞消息了爆侣,消息內(nèi)容要包裝在Meesage對象中。這時幢妄,只是客戶端能往服務(wù)端發(fā)送消息兔仰,但服務(wù)端卻不能往客戶端發(fā)送消息。要實現(xiàn)客戶端蕉鸳,服務(wù)端的雙向通信乎赴,我們在客戶端還需要創(chuàng)建一個Handler,并通過這個Handler創(chuàng)建一個Messenger對象潮尝,然后將這個Messenger對象榕吼,賦值給客戶端發(fā)給服務(wù)端的Message中的replyTo字段,這樣衍锚,服務(wù)端發(fā)送的消息就會回調(diào)給我們客戶端創(chuàng)建的這個Handler對象友题。服務(wù)端
首先嗤堰,我們需要創(chuàng)建一個Service戴质,處理客戶端的請求,我們還需要創(chuàng)建一個Handler踢匣,通過這個Handler創(chuàng)建一個Messenger對象,最后在onBind()方法中,返回這個Messenger的Binder對象給客戶端狡相。如果我們需要發(fā)送消息給客戶端呀癣,便可以在這個Handler中,通過客戶端發(fā)送給服務(wù)端的Meesage對象的replyTo屬性输莺,便可以得到一個Meesenger對象戚哎,我們可以通過這個Meesenger對象和客戶端通信。
客戶端:
class MessengerServiceActivity : AppCompatActivity(){
private lateinit var mConncetion:MessengerServiceConnection
private val replyHandler=ReplyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_messenger_service)
startBindService()
}
private fun startBindService(){
mConncetion=MessengerServiceConnection()
val intent=Intent()
intent.setAction("messenger.test")
intent.setPackage("messenger.test")
bindService(intent,mConncetion,Service.BIND_AUTO_CREATE)
}
class MessengerServiceConnection:ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val replyMessenger=Messenger(replyHandler)
val messenger= Messenger(service)
val bundle=Bundle()
val message= Message.obtain(null,0)
bundle.putString("message","一條新消息")
message.data=bundle
message.replyTo=replyMessenger
try {
messenger.send(message)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
class ReplyHandler:Handler(){
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
when(msg?.what){
0-> {
Log.e("test",msg.data.getString("reply"))
}
}
}
}
override fun onDestroy() {
super.onDestroy()
replyHandler.removeCallbacksAndMessages(null)
unbindService(mConncetion)
}
}
服務(wù)端:
class MessengerService :Service() {
private val sMessenger = Messenger(MessengerHandler())
override fun onBind(intent: Intent?): IBinder? {
return sMessenger.binder
}
class MessengerHandler : Handler(){
private val TAG = "MessengerTest"
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
when(msg?.what){
0->{
Log.e(TAG, msg.data.getString("message"))
val message= Message()
val bundle=Bundle()
bundle.putString("reply","我們已收到您的反饋")
message.data= bundle
msg.replyTo.send(message)
}
}
}
}
-
運行截圖
總結(jié)
Meseenger使用比較簡單嫂用,能實現(xiàn)的功能也比較簡單型凳,它一次只能處理一個請求,所以當大量請求并發(fā)到達服務(wù)端時嘱函,服務(wù)端也只能一個個處理甘畅,所以它不適用大量的并發(fā)請求的情景,Meesnger主要是用來傳遞消息的往弓,客戶端也無法調(diào)用服務(wù)端方法疏唾,如果有這種需求,需要考慮其它的IPC方式函似。
使用AIDL
-
AIDL接口簡介
在 AIDL文件中槐脏,并不是所有數(shù)據(jù)類型都能夠使用,以下是AIDL中支持的類型:
以上6種數(shù)據(jù)類型就是AIDL中支持的所有數(shù)據(jù)類型撇寞,其中自定義的Parcelable對象和AIDL對象必須要顯式的import准给,不管它們是否和當前IDL文件位于同一個包內(nèi)泄朴。而且,如果用到了自定義的Parcelabel對象露氮,必須新建一個和它同名的AIDL文件祖灰,并且要用parcelable 聲明它為Parcelable類型除此之外,AIDL接口中畔规,除了基本數(shù)據(jù)類型局扶,其他類型的參數(shù)必須標明方向:in、out或inout叁扫,in表示輸入型參數(shù)三妈,out表示輸出型參數(shù),inout表示輸入輸出型參數(shù)莫绣,要合理地使用畴蒲,因為這在底層都是有開銷的。最后对室,在AIDL接口中只支持聲明方法模燥,不支持靜態(tài)常量,這是區(qū)別于傳統(tǒng)的接口的掩宜。下面我們來看一下例子:
package study.android.devart.client.ipc.aidl;
import study.android.devart.client.ipc.aidl.Book;
import study.android.devart.client.ipc.aidl.IOnNewBookArrivedListener;
interface IBookManager {
void addBook(in Book book);
List<Book> getBookList();
void registerListener(IOnNewBookArrivedListener listener);
void unRegisterListener(IOnNewBookArrivedListener listener);
}
// IOnNewBookArrivedListener.aidl
package study.android.devart.client.ipc.aidl;
interface IOnNewBookArrivedListener {
void onNewBookArrived();
}
// Book.aidl
package study.android.devart.client.ipc.aidl;
// Declare any non-default types here with import statements
parcelable Book ;
首先蔫骂,我們創(chuàng)建一個AIDL文件,IBookManager.aidl,它有四個方法牺汤,由于用到了Book類辽旋,它是我們自定義的Parcelable對象,所以我們必須顯式的將它import進來檐迟,IOnNewBookArrivedListener是一個接口补胚,但要在AIDL中使用,所以也需要將它聲明為一個AIDL文件追迟,接下來我們來看下客戶端和服務(wù)端的實現(xiàn)溶其。
客戶端
客戶端只需要綁定服務(wù)端的Service,綁定成功后怔匣,把Service返回的Binder對象轉(zhuǎn)化為AIDL接口所屬的類型握联,便可以調(diào)用AIDL的方法了。
服務(wù)端
服務(wù)端需要創(chuàng)建一個Service來處理客戶端的請求每瞒,然后需要創(chuàng)建一個AIDL文件金闽,然后將暴露給客戶端的接口,在這個AIDL文件里聲明剿骨,最后在Service實現(xiàn)這個AIDL接口就好代芜。
客戶端代碼
class BookManagerActivity:AppCompatActivity(){
private lateinit var mBtnTest: Button
private lateinit var mConncetion:MessengerServiceConnection
private lateinit var mIBookManager: IBookManager
private val listener=MyListener()
private val TAG="BookManagerActivity"
private var pos=1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_aidl_test)
mBtnTest=findViewById(R.id.btn_test)
mBtnTest.setOnClickListener {
addBook()
}
startBindService()
}
private fun addBook() {
val book="第"+pos+++"行代碼"
mIBookManager.addBook(Book(book))
}
private fun startBindService(){
mConncetion= MessengerServiceConnection()
val intent= Intent()
intent.setAction("study.android.devart.service.aidl")
intent.setPackage("study.android.devart.service")
bindService(intent,mConncetion, Service.BIND_AUTO_CREATE)
}
inner class MessengerServiceConnection: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mIBookManager=IBookManager.Stub.asInterface(service)
mIBookManager.registerListener(listener)
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
inner class MyListener:IOnNewBookArrivedListener.Stub(){
override fun onNewBookArrived() {
val count =mIBookManager.bookList.size
Log.e(TAG,"收到新添加書籍提醒: 現(xiàn)在書庫共有 $count 本書")
}
}
private fun stopMyService() {
try {
if(mIBookManager.asBinder().isBinderAlive){
mIBookManager.unRegisterListener(listener)
}
unbindService(mConncetion)
}catch (e:Exception){
}
}
override fun onDestroy() {
super.onDestroy()
stopMyService()
}
}
我們客戶端首先要綁定服務(wù)端Service,然后在
onServiceConnected方法中浓利,可以通過IBookManager.Stub.asInterface(service)得到我們的IBookManager對象挤庇,接下來我們便可以通過IBookManager調(diào)用AIDL文件中方法了钞速。
服務(wù)端代碼
class BookManagerService: Service() {
private val mBookList=CopyOnWriteArrayList<Book>()
private val mListeners=RemoteCallbackList<IOnNewBookArrivedListener>()
override fun onCreate() {
super.onCreate()
mBookList.add(Book("第一行代碼"))
mBookList.add(Book("第二行代碼"))
}
override fun onBind(intent: Intent?): IBinder? {
return MyBinder()
}
inner class MyBinder : IBookManager.Stub() {
override fun registerListener(listener: IOnNewBookArrivedListener?) {
mListeners.register(listener)
}
override fun unRegisterListener(listener: IOnNewBookArrivedListener?) {
mListeners.unregister(listener)
}
override fun addBook(book: Book?) {
mBookList.add(book)
val count=mListeners.beginBroadcast()
for (i in 0 until count){
mListeners.getBroadcastItem(i).onNewBookArrived()
}
mListeners.finishBroadcast()
}
override fun getBookList(): MutableList<Book> {
return mBookList
}
}
}
上面是一個服務(wù)端Service的典型實現(xiàn),我們創(chuàng)建了一個繼承于IBookManager.Stub的Binder對象嫡秕,這個對象內(nèi)部實現(xiàn)了我們AIDL文件中定義的方法渴语,我們在Service的onBind方法中返回它。這里存儲Book的List我們采用了CopyOnWriteArrayList昆咽,它繼承List,支持并發(fā)讀/寫驾凶,AIDL方法是在服務(wù)端Binder線程池中執(zhí)行的,所以掷酗,當有多個客戶端同時訪問服務(wù)端時调违,也就會存在多個線程同時訪問服務(wù)端方法的問題,所以我們必須在服務(wù)端處理并發(fā)問題泻轰,因此這里采用了CopyOnWriteArrayList技肩,前面提到,AIDL支持的List類型只有ArrayList浮声,但是CopyOnWriteArrayList并不繼承于ArrayList虚婿,而是繼承的List,那為什么我們這里還可以使用呢阿蝶?這是因為雖然我們在服務(wù)端用的是CopyOnWriteArrayList雳锋,但是黄绩,在Binder中訪問數(shù)據(jù)羡洁,最終會形成一個新的ArrayList傳遞給客戶端,雖然我們服務(wù)端用的是CopyOnWriteArrayList爽丹,但傳遞的時候卻已經(jīng)被轉(zhuǎn)化為了ArrayList筑煮。還有一點我們要注意嗎,我們存儲IOnNewBookArrivedListener是用的RemoteCallbackList粤蝎,IOnNewBookArrivedListener要在客戶端綁定與解綁真仲,雖然我們在客戶端綁定與解綁是同一個對象,但是當這個對象傳遞到服務(wù)端時初澎,Binder會把客戶端傳遞的對象解析成一個新的對象秸应,因為,對象跨進程傳輸?shù)臅r候碑宴,都是一個反序列化過程软啼,所以自然不會是同一個對象,這樣就導致我們無法綁定與解綁延柠,這時候我們就可以用RemoteCallbackList祸挪,它內(nèi)部有一個Map,key是IBinder類型贞间,value是CallBack類型贿条,CallBack封裝了客戶端傳遞給服務(wù)端的Listener雹仿,雖然,我們傳遞的對象整以,服務(wù)端會生成一個新的對象胧辽,但是我們客戶端和服務(wù)端的Binder卻是同一個對象,所以公黑,我們服務(wù)端只需要找到和客戶端Binder一致的對象票顾,就可以找到我們客戶端傳遞過來的對象了,RemoteCallbackList幫助我們實現(xiàn)了這個需求帆调,而且RemoteCallbackList會自動進行線程同步奠骄,我們也不需要考慮并發(fā)讀/寫,而且當客戶端進程終止后番刊,它會自動刪除客戶端傳遞過來的對象含鳞,遍歷RemoteCallbackList,我們需要采用以下方式:
val count=mListeners.beginBroadcast()
for (i in 0 until count){
mListeners.getBroadcastItem(i).onNewBookArrived()
}
mListeners.finishBroadcast()
其中,beginBroadcast()和finishBroadcast()必須配對使用芹务。
- 運行截圖
Binder連接池
按照我們常規(guī)流程蝉绷,我們使用AIDL的時候,要創(chuàng)建一個Service枣抱,然后創(chuàng)建一個繼承于AIDL接口中Stub類的對象熔吗,在Service的onBind方法中返回它,客戶端綁定服務(wù)端Service佳晶,就可以訪問AIDL中的方法了桅狠。那么當我們有很多個AIDL接口時該怎么辦呢?要創(chuàng)建很多個Service嗎轿秧?這顯然是不行的中跌,Service本身就是一種系統(tǒng)資源,我們肯定不能創(chuàng)建太多菇篡。那怎么辦呢漩符?我們要把多個AIDL文件放到一個Service中管理,這樣就完美實現(xiàn)了我們的需求驱还,那我們?nèi)绾巫龅紸IDL文件的統(tǒng)一管理呢嗜暴?我們可以根據(jù)業(yè)務(wù)模塊來劃分,每個業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實現(xiàn)议蟆,然后向服務(wù)端提供自己唯一的標識和Binder對象闷沥,對于服務(wù)端,就只需要一個Service就可以了咪鲜,服務(wù)端提供一個queryBinder接口狐赡,這個接口可根據(jù)不同的業(yè)務(wù)模塊標識,來返回相應(yīng)的Binder對象,拿到這個對象颖侄,就可調(diào)用相應(yīng)的服務(wù)端方法了鸟雏,這也就是Binder連接池。由此可見览祖,Binder連接池的主要目的便是避免重復創(chuàng)建Service孝鹊。
下面我們來看下代碼實現(xiàn):
首先,我們創(chuàng)建兩個AIDL接口展蒂,來模擬客戶端有多個AIDL接口的情況
interface IPayManager {
void pay(double price);
}
interface IBookManager {
Book getBook();
}
接下來創(chuàng)建這兩個AIDL接口的實現(xiàn)類
class PayManagerImpl : IPayManager.Stub() {
override fun pay(price: Double) {
Log.e("Binder連接池測試","花了 $price 元錢")
}
}
class GetBookManagerImpl : IGetBookManager.Stub() {
val name="買了一本書又活,第一行代碼"
override fun getBook(): Book {
Log.e("Binder連接池測試",name)
return (Book(name))
}
}
然后,我們創(chuàng)建一個IBinderPool AIDL接口文件锰悼,它只有一個方法柳骄,負責根據(jù)客戶端傳過來的唯一標識,返回對應(yīng)的Binder對象給客戶端箕般。
interface IBinderPool {
IBinder queryBinder(int binderCode);
}
接下來我們看看Binder連接池的具體實現(xiàn)
//Binder連接池的遠程Service
class BinderPoolService:Service() {
override fun onBind(p0: Intent?): IBinder? {
return BinderPool.IBinderPoolImpl
}
}
class BinderPool private constructor(var mContext: Context) {
private val mServiceConnection=BinderPoolServiceConnection()
private var mIBinderPool:IBinderPool?=null
companion object {
@Volatile
private var instance: BinderPool? = null
fun getInstance(context: Context): BinderPool {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = BinderPool(context)
instance = created
created
}
}
}
}
init {
mContext=mContext.applicationContext
connectBinderPoolService()
}
@Synchronized
private fun connectBinderPoolService() {
var intent=Intent(mContext,BinderPoolService::class.java)
mContext.bindService(intent,mServiceConnection, Service.BIND_AUTO_CREATE)
}
fun queryBinder(binderCode: Int): IBinder?{
return mIBinderPool?.queryBinder(binderCode)
}
object IBinderPoolImpl:IBinderPool.Stub(){
override fun queryBinder(binderCode: Int): IBinder {
var binder:IBinder
when(binderCode){
0-> binder= PayManagerImpl()
1->binder= GetBookManagerImpl()
else->binder= PayManagerImpl()
}
return binder
}
}
inner class BinderPoolServiceConnection:ServiceConnection{
override fun onServiceDisconnected(p0: ComponentName?) {
}
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
mIBinderPool=IBinderPool.Stub.asInterface(p1)
}
}
}
首先耐薯,我們創(chuàng)建一個BinderPool作為Binder連接池的具體實現(xiàn),并且它是一個單例實現(xiàn)丝里,我們在其內(nèi)部將它和我們的遠程Service綁定曲初,創(chuàng)建一個繼承于IBinderPool.Stub的靜態(tài)內(nèi)部類,并在遠程Service的onBind方法中
返回它的對象杯聚,然后臼婆,我們定義一個queryBinder方法,它接收一個Int類型的參數(shù)幌绍,通過這個方法我們可以得到我們需要的Binder對象颁褂,從而可以調(diào)用AIDL文件中的方法。下面是客戶端代碼:
val binderPool=BinderPool.getInstance(applicationContext)
val binder = binderPool?.queryBinder(0)
val binder1 = binderPool?.queryBinder(1)
IPayManager.Stub.asInterface(binder).pay(20.0)
IGetBookManager.Stub.asInterface(binder1).book
打印的Log日志:
可以看到纷捞,我們成功的調(diào)用了AIDL文件中的方法痢虹。不過被去,Binder是有可能會突然死亡的主儡,為了使我們的代碼更加健壯,我們應(yīng)該為我們的Binder連接池惨缆,增加斷線重連機制糜值,當我們遠程服務(wù)意外終止時,我們的Binder連接池會重新進行連接坯墨,而且當我們客戶端調(diào)用時出現(xiàn)異常寂汇,我們也應(yīng)該重新去獲取最新的Binder對象。所以BinderPool中我們應(yīng)該做如下修改:
private val mDeathRecipient=DeathRecipient()
inner class DeathRecipient:IBinder.DeathRecipient{
override fun binderDied() {
//新增給Binder擦除死亡代理
mIBinderPool?.asBinder()?.unlinkToDeath(mDeathRecipient,0)
mIBinderPool=null
connectBinderPoolService()
}
}
inner class BinderPoolServiceConnection:ServiceConnection{
override fun onServiceDisconnected(p0: ComponentName?) {
}
override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
mIBinderPool=IBinderPool.Stub.asInterface(p1)
//新增給Binder設(shè)置死亡代理
mIBinderPool?.asBinder()?.linkToDeath(mDeathRecipient,0)
}
}
首先我們需要自定義一個繼承于IBinder.DeathRecipient的類并創(chuàng)建其對象捣染,在里面實現(xiàn)當Binder死亡時骄瓣,我們重新綁定遠程服務(wù)的邏輯,然后在ServiceConnection的onServiceConnected方法中耍攘,新增給Binder設(shè)置死亡代理榕栏。我們也可以在ServiceConnection的onServiceDisconnected方法中重新連接服務(wù)畔勤。
總結(jié)
- 服務(wù)端方法是在服務(wù)端Binder線程池中執(zhí)行的,所以當我們客戶端調(diào)用服務(wù)端方法時扒磁,如果方法耗時庆揪,注意在子線程中執(zhí)行,以免阻塞主線程妨托,發(fā)生ANR缸榛,同理,服務(wù)端調(diào)用客戶端方法兰伤,也是在客戶端Binder線程中執(zhí)行的内颗,所以服務(wù)端也要保證此方法在線程池中執(zhí)行,否則敦腔,會導致服務(wù)端無響應(yīng)起暮。
- Binder是會意外死亡的,這時候我們需要重新連接服務(wù)会烙,第一種方法是給Binder設(shè)置死亡代理负懦,在binderDied方法中,我們可以重新連接服務(wù)柏腻,第二種方法是在客戶端的onServiceDisconnected中纸厉,我們可以重新連接服務(wù),這兩種方法的區(qū)別是五嫂,onServiceDisconnected是在客戶端的UI線程中被回調(diào)颗品,而binderDied方法是在客戶端的Binder線程池中被回調(diào)。
- 關(guān)于權(quán)限驗證:
- 我們可以在服務(wù)端OnBind方法中做權(quán)限驗證沃缘,驗證不通過直接返回null躯枢,這樣客戶端就無法訪問服務(wù)端方法,至于驗證方式有很多槐臀,比如Permission驗證锄蹂,我們可以自定義一個Permission,我們自己的客戶端水慨,只需要聲明這個權(quán)限得糜,由于別的客戶端沒有權(quán)限,則會驗證失敗晰洒。
首先朝抖,自定義一個我們的權(quán)限:
<permission android:name="study.android.devart.service.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
定義權(quán)限之后,我們就可以在onBind方法中做權(quán)限驗證:
override fun onBind(intent: Intent?): IBinder? {
if(checkCallingOrSelfPermission(Manifest.permission.ACCESS_BOOK_SERVICE)==PackageManager.PERMISSION_GRANTED){
return MyBinder()
}
return null
}
最后谍珊,我們需要使用這個定義權(quán)限:
<uses-permission android:name="study.android.devart.service.permission.ACCESS_BOOK_SERVICE"/>
- 我們可以在服務(wù)端的onTransact方法中做權(quán)限驗證治宣,如果驗證失敗,就直接返回false,這樣侮邀,客戶端就無法執(zhí)行AIDL的方法了缆巧,同樣可以采用Permission方式驗證,也可以使用Uid和Pid來做驗證豌拙,可以通過getCallingUid和getCallingPid來拿到客戶端的Uid和Pid陕悬,通過這兩個參數(shù)我們可以做一些驗證操作,比如驗證包名等等按傅。
var packageName:String?=null
val packagesForUid = packageManager.getPackagesForUid(Binder.getCallingUid())
if(packagesForUid.isNullOrEmpty())
return false
packageName= packagesForUid.get(0)
if(!packageName.equals("study.android.devart.client"))
return false
return super.onTransact(code, data, reply, flags)
注意:通過Permission驗證的方式客戶端和服務(wù)端必須是在同一個應(yīng)用的情況下才能使用捉超,因為不同的應(yīng)用onBind方法不是同一個Binder調(diào)用的,checkCallingPermission方法只能在AIDL IPC方法中調(diào)用唯绍。
- 可以通過Binder連接池的方式來管理AIDL文件
使用ContentProvider
前言
ContentProvider是Android提供的專門用于不同應(yīng)用間進行數(shù)據(jù)共享的一種方式拼岳,ContentProvider的底層實現(xiàn)是Binder,由于系統(tǒng)為我們做了封裝况芒,所以它的使用也比較簡單惜纸。Android系統(tǒng)也給我們預置了很多ContentProvider,例如聯(lián)系人绝骚,通話記錄等等耐版,使得我們可以方便的訪問這些信息。ContentProvider主要以表格的形式來組織數(shù)據(jù)压汪,
自定義ContentProvider
創(chuàng)建一個自定義的ContentProvider很簡單粪牲,只需要繼承ContentProvider類并實現(xiàn)其6個抽象方法即可,這6個方法分別是onCreate,query,update,insert,delete和getType止剖,除了onCreate方法由系統(tǒng)回調(diào)運行在主線程中腺阳,其余5個方法均運行在ContentProvider的進程中,接下來我們簡單分析下這6個抽象方法:
onCreate
代表著ContentProvider的創(chuàng)建穿香,一般來說亭引,我們需要在這里做一些初始化工作。insert,delete皮获,update焙蚓,query
實現(xiàn)對于數(shù)據(jù)庫的增刪改查功能。getType
用來返回一個Uri請求對應(yīng)的MIME類型(媒體類型)魔市,比如圖片主届、視頻等,sssssss我們可以直接在這個方法中返回null或者*/*
待德。
我們來自定義一個ContentProvider——BookProvider,它可以訪問我們自定義的兩張表Book表和User表。
BookSqlHelper
class BookSqlHelper : SQLiteOpenHelper{
private val BOOK_TABLE_NAME="book"
private val USER_TABLE_NAME="user"
private val CREATE_BOOK_TABLE_SQL="CREATE TABLE IF NOT EXISTS"+BOOK_TABLE_NAME+"(_id INTEGER PRIMARY KEY,"+"name TEXT)"
private val CREATE_USER_TABLE_SQL="CREATE TABLE IF NOT EXISTS"+USER_TABLE_NAME+"(_id INTEGER PRIMARY KEY,"+"name TEXT)"
constructor(context:Context ) : super(context,"book_provider.db",null,1)
override fun onCreate(p0: SQLiteDatabase?) {
p0?.execSQL(CREATE_BOOK_TABLE_SQL)
p0?.execSQL(CREATE_USER_TABLE_SQL)
}
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
}
BookProvider
class BookProvider : ContentProvider() {
val AUTHORITY="study.android.devart.service.bookprovider"
val BOOK_CONTENT_CODE=0
val USER_CONTENT_CODE=1
val uriMatcher=UriMatcher(UriMatcher.NO_MATCH)
var sqlHelper:SQLiteDatabase?=null
init{
uriMatcher.addURI(AUTHORITY,"book",BOOK_CONTENT_CODE)
uriMatcher.addURI(AUTHORITY,"user",USER_CONTENT_CODE)
}
override fun onCreate(): Boolean {
initSqlDataBase()
return false
}
private fun initSqlDataBase() {
Thread(Runnable {
sqlHelper=BookSqlHelper(context).writableDatabase
sqlHelper?.execSQL("delete from "+BookSqlHelper.BOOK_TABLE_NAME)
sqlHelper?.execSQL("delete from "+BookSqlHelper.USER_TABLE_NAME)
sqlHelper?.execSQL("insert into "+BookSqlHelper.BOOK_TABLE_NAME+" values(1,'Android')")
sqlHelper?.execSQL("insert into "+BookSqlHelper.BOOK_TABLE_NAME+" values(2,'Ios')")
sqlHelper?.execSQL("insert into "+BookSqlHelper.USER_TABLE_NAME+" values(1,'小明')")
sqlHelper?.execSQL("insert into "+BookSqlHelper.USER_TABLE_NAME+" values(2,'小紅')")
}).start()
}
override fun insert(p0: Uri, p1: ContentValues?): Uri? {
val tableName=getTable(p0)
sqlHelper?.insert(tableName,null,p1)
context?.contentResolver?.notifyChange(p0,null)
return p0
}
override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? {
val tableName=getTable(p0)
return sqlHelper?.query(tableName,p1,p2,p3,null,null,p4,null)
}
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {
val tableName=getTable(p0)
val row=sqlHelper?.update(tableName,p1,p2,p3)
if(row!! >0){
context?.contentResolver?.notifyChange(p0,null)
}
return row
}
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {
val tableName=getTable(p0)
val count=sqlHelper?.delete(tableName,p1,p2)
if(count!!>0){
context?.contentResolver?.notifyChange(p0,null)
}
return count
}
override fun getType(p0: Uri): String? {
return null
}
private fun getTable(uri: Uri):String{
return when(uriMatcher.match(uri)){
BOOK_CONTENT_CODE->BookSqlHelper.BOOK_TABLE_NAME
USER_CONTENT_CODE->BookSqlHelper.USER_TABLE_NAME
else->""
}
}
}
總結(jié)
我們知道枫夺,ContentProvider通過Uri來區(qū)分外界需要訪問的數(shù)據(jù)集合将宪,在本例中,BookProvider支持外界訪問Book和User兩張表,為了知道外界到底要訪問哪張表较坛,我們需要為它們定義單獨的Uri和Uri_Code印蔗,并將Uri和對應(yīng)的Uri_Code關(guān)聯(lián),這樣丑勤,當外界發(fā)起請求時华嘹,我們就可以通過Uri,得到Uri_Code法竞,從而我們就可以知道外界是要訪問哪張表的數(shù)據(jù)了耙厚。
我們可以發(fā)現(xiàn),除了query外岔霸,另外的三個方法薛躬,insert、delete和update呆细,這三個方法都會引起數(shù)據(jù)源的改變型宝,所以我們需要手動調(diào)用ContentProvider的notifyChange方法,通知外界當前ContentProvider的數(shù)據(jù)已經(jīng)發(fā)生改變絮爷,如果我們需要監(jiān)聽這個改變趴酣,可以通過ContentProvider的registerContentObserver方法,注冊一個觀察者坑夯,通過unRegisterContentObserver來取消注冊就可以了价卤。
需要注意的是,insert渊涝,delete慎璧,update,query這四個方法跨释,都是存在多線程并發(fā)訪問的胸私,因此,在這四個方法內(nèi)部鳖谈,我們應(yīng)該做好線程同步岁疼,在本例中,由于CotnentProvider的底層數(shù)據(jù)是SQLite缆娃,并且捷绒,只有一個SQLiteDatabase的連接,因此不用考慮線程同步問題贯要,因為SQLiteDatabase內(nèi)部對數(shù)據(jù)庫的操作已經(jīng)做了線程同步的處理暖侨,但是假如通過多個SQLiteDatabase對象來操作數(shù)據(jù)庫,就無法保證線程同步了崇渗,因為多個SQLiteDatabase對象之間無法進行線程同步字逗,再假如ContentProvider的底層數(shù)據(jù)是一塊內(nèi)存京郑,比如是一個list,這時候也需要考慮線程同步的問題葫掉。
ContentProvider除了支持對數(shù)據(jù)源進行增刪改查操作些举,還支持自定義調(diào)用,這個過程是通過ContentResolver的call方法和CotentProvider的call方法來完成的俭厚。
使用Socket
前言
socket户魏,又稱作"套接字",是網(wǎng)絡(luò)通信中的概念挪挤,它分為流式套接字和用戶數(shù)據(jù)報套接字兩種叼丑,分別對應(yīng)網(wǎng)絡(luò)傳輸控制層的TCP和UDP協(xié)議。TCP是面向連接的協(xié)議电禀,提供穩(wěn)定的雙向通信功能幢码,它的連接的建立需要通過"三次握手"才能完成,它具備超時重試機制尖飞,因此有很高的穩(wěn)定性症副;UDP是無連接的,提供不穩(wěn)定的單向通信功能政基,當然贞铣,它也可以實現(xiàn)雙向通信,在性能上沮明,它的效率更高辕坝,但在穩(wěn)定性上,它不能保證數(shù)據(jù)能被正確傳輸荐健,尤其是在網(wǎng)絡(luò)阻塞的情況下酱畅。Socket支持傳輸任意的字節(jié)流。
使用Socket實現(xiàn)進程間通信
我們設(shè)計一個簡單的在線客服程序江场,首先纺酸,我們需要創(chuàng)建一個遠程Service,在其內(nèi)部創(chuàng)建一個TCP服務(wù)址否,在客戶端連接成功后餐蔬,客戶端就可以給服務(wù)端發(fā)送消息,服務(wù)端接收到客戶端發(fā)送過來的消息就可以回復消息給客戶端了佑附,下面來看下代碼實現(xiàn)樊诺。
- 服務(wù)端代碼
class TCPService :Service() {
private var mIsServiceDestroyed=false
private val replyConent= "我們已經(jīng)收到您的反饋"
override fun onCreate() {
super.onCreate()
Thread(TCPServer()).start()
}
override fun onBind(p0: Intent?): IBinder? {
return null
}
inner class TCPServer:Runnable{
override fun run() {
var serverSocket:ServerSocket?=null
try {
serverSocket=ServerSocket(8888)
}catch (e:IOException){
return
}
while (!mIsServiceDestroyed){
try {
val clientSocket=serverSocket.accept()
responseClient(clientSocket)
}catch (e:IOException){
}
}
}
private fun responseClient(clientSocket: Socket?) {
val reader=BufferedReader(InputStreamReader(clientSocket?.getInputStream()))
val outer=PrintWriter(BufferedWriter(OutputStreamWriter(clientSocket?.getOutputStream())),true)
outer.println("歡迎使用在線客服")
while (!mIsServiceDestroyed){
val str=reader.readLine()
if(TextUtils.isEmpty(str)){
break
}
outer.println(replyConent)
}
reader.close()
outer.close()
clientSocket?.shutdownInput()
clientSocket?.close()
}
}
override fun onDestroy() {
super.onDestroy()
mIsServiceDestroyed=true
}
}
首先我們創(chuàng)建一個遠程Service,在其內(nèi)部新開一個線程音同,建立一個TCP服務(wù)词爬,本例中監(jiān)聽的是8888端口,然后等待客戶端的連接瘟斜,待客戶端連接成功后缸夹,我們會回復客戶端一句話:歡迎使用在線客服
作為歡迎語痪寻。當客戶端斷開連接后螺句,我們服務(wù)端也應(yīng)該斷開socket的連接虽惭,這里是通過判斷服務(wù)端的輸入流來判斷的,當客戶端斷開后蛇尚,服務(wù)端的輸入流會返回null芽唇。
- 客戶端代碼
class SocketTestActivity:AppCompatActivity() {
private lateinit var mBtnStart: Button
private lateinit var mBtnTest: Button
private lateinit var mBtnStop: Button
private lateinit var mTvContent: TextView
private lateinit var mTvTitle:TextView
private val content="您好,我有個問題"
private val sb=StringBuilder()
private var mClientSocket:Socket?=null
private var printWriter:PrintWriter?=null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_aidl_test)
mBtnStart=findViewById(R.id.btn_start)
mBtnTest=findViewById(R.id.btn_test)
mBtnStop=findViewById(R.id.btn_stop)
mTvContent=findViewById(R.id.tv_content)
mTvTitle=findViewById(R.id.tv_title)
mTvTitle.text="Socket使用"
mBtnStart.setOnClickListener {
startTestSocket()
var socket:Socket?=null
Thread(Runnable {
while (socket==null){
try {
socket=Socket("localhost",8888)
mClientSocket=socket
socket!!.getInputStream()
printWriter=PrintWriter(BufferedWriter(OutputStreamWriter(socket!!.getOutputStream())),true)
val reader=BufferedReader(InputStreamReader(socket!!.getInputStream()))
while (!isFinishing){
sb.append(reader.readLine()).append("\n")
updateText()
}
printWriter?.close()
reader.close()
socket?.shutdownInput()
socket?.close()
}catch (e:IOException){
SystemClock.sleep(1000)
}
}
}).start()
}
mBtnTest.setOnClickListener{
Thread(Runnable {
sb.append(content).append("\n")
printWriter?.println(content)
updateText()
}).start()
}
mBtnStop.setOnClickListener {
stopSocket()
}
updateText()
}
private fun startTestSocket(){
val intent=Intent()
intent.setAction("study.android.devart.service.socket")
intent.setPackage("study.android.devart.service")
startService(intent)
}
fun updateText(){
runOnUiThread {
mTvContent.text=sb.toString()
}
}
private fun stopSocket() {
try {
mClientSocket?.shutdownInput()
mClientSocket?.close()
}catch (e: Exception){
}
}
override fun onDestroy() {
stopSocket()
super.onDestroy()
}
}
當SocketTestActivity
啟動后取劫,我們在onCreate
方法中開啟一個線程匆笤,連接我們的服務(wù)端Socket,并且我們也做了超時重試機制谱邪,以免連接失敗炮捧,為了降低重試機制的開銷,我們把每次重試的時間間隔改為1000ms馅而。當客戶端連接成功后此疹,我們就可以向服務(wù)端傳遞消息了棕孙,本例中是固定的向服務(wù)端發(fā)送一句話:您好,我有個問題
,在連接成功后书蚪,我們通過while循環(huán)可以不斷地讀取服務(wù)端傳遞過來的消息,客戶端Activity銷毀后迅栅,我們跳出循環(huán)即可殊校。
- 運行結(jié)果
- 總結(jié)
- 使用Socket實現(xiàn)IPC,需要注意加上訪問網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- 本例中读存,實現(xiàn)了一個客戶端與一個服務(wù)端之間的通信为流,實際上也可以實現(xiàn)多個客戶端與一個服務(wù)端之間的通信,這個可以自行擴展让簿。
選用合適的IPC方式
參考
- 《Android開發(fā)藝術(shù)探索》