讀書筆記——Android中的IPC方式

前言

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)跨進程通訊

  1. 客戶端
    首先,我們需要綁定服務(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對象友题。

  2. 服務(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)
                }
            }
        }
    }
  • 運行截圖
    Messenger測試.jpg

總結(jié)

Meseenger使用比較簡單嫂用,能實現(xiàn)的功能也比較簡單型凳,它一次只能處理一個請求,所以當大量請求并發(fā)到達服務(wù)端時嘱函,服務(wù)端也只能一個個處理甘畅,所以它不適用大量的并發(fā)請求的情景,Meesnger主要是用來傳遞消息的往弓,客戶端也無法調(diào)用服務(wù)端方法疏唾,如果有這種需求,需要考慮其它的IPC方式函似。

使用AIDL

  1. AIDL接口簡介

在 AIDL文件中槐脏,并不是所有數(shù)據(jù)類型都能夠使用,以下是AIDL中支持的類型:

AIDL接口中支持的數(shù)據(jù)類型

以上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()必須配對使用芹务。

  • 運行截圖
AIDL測試.jpg

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孝鹊。


Binder連接池.png

下面我們來看下代碼實現(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日志:


Log日志.png

可以看到纷捞,我們成功的調(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)限驗證:
  1. 我們可以在服務(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"/>
  1. 我們可以在服務(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é)果
Socket測試.jpg
  • 總結(jié)
  1. 使用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"/>
  1. 本例中读存,實現(xiàn)了一個客戶端與一個服務(wù)端之間的通信为流,實際上也可以實現(xiàn)多個客戶端與一個服務(wù)端之間的通信,這個可以自行擴展让簿。

選用合適的IPC方式

IPC方式比較.png

參考

  • 《Android開發(fā)藝術(shù)探索》
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敬察,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拜英,更是在濱河造成了極大的恐慌静汤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件居凶,死亡現(xiàn)場離奇詭異虫给,居然都是意外死亡,警方通過查閱死者的電腦和手機侠碧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門抹估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弄兜,你說我怎么就攤上這事药蜻〈墒剑” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵语泽,是天一觀的道長贸典。 經(jīng)常有香客問我,道長踱卵,這世上最難降的妖魔是什么廊驼? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮惋砂,結(jié)果婚禮上妒挎,老公的妹妹穿的比我還像新娘。我一直安慰自己西饵,他們只是感情好酝掩,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眷柔,像睡著了一般期虾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闯割,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天彻消,我揣著相機與錄音,去河邊找鬼宙拉。 笑死宾尚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谢澈。 我是一名探鬼主播煌贴,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锥忿!你這毒婦竟也來了牛郑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤敬鬓,失蹤者是張志新(化名)和其女友劉穎淹朋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钉答,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡础芍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了数尿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仑性。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖右蹦,靈堂內(nèi)的尸體忽然破棺而出诊杆,到底是詐尸還是另有隱情歼捐,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布晨汹,位于F島的核電站豹储,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宰缤。R本人自食惡果不足惜颂翼,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一晃洒、第九天 我趴在偏房一處隱蔽的房頂上張望慨灭。 院中可真熱鬧,春花似錦球及、人聲如沸氧骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽筹陵。三九已至,卻和暖如春镊尺,著一層夾襖步出監(jiān)牢的瞬間朦佩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工庐氮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留语稠,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓弄砍,卻偏偏與公主長得像仙畦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子音婶,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容