读书笔记——Android中的IPC方式

前言

Android平台跨进程方式有很多,比如可以通过Intent附加extras来传递信息,也可以通过共享文件的方式来共享数据,还可以采用Binder来进行跨进程通讯,另外,ContentProvider天生就是支持跨进程访问的,所以我们也可以通过它来进行IPC通信。此外,通过网络来传递数据也是可以的,所以,我们也可以通过Socket来实现IPC通讯。

Bundle

我们知道,四大组件的三大组件(Activity,Service,BroadCastReciver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以在方便的在不同的进程中传输,基于这一点,当我们在另一个进程中启动Activity,Service,BroadCastReciver时,我们可以通过Bundle附加我们需要传输的数据,从而实现跨进程通讯。当然,我们需要传输的数据必须是可序列化的。

使用文件共享

两个进程之间可以通过读写文件来交换数据,Android平台是基于Linux的,并不限制并发读/写,使得我们可以随心所欲的在两个进程间交换文件。我们只需要注意下可能存在的并发读/写的问题就可以了。

使用Messenger

Messenger是一种轻量级的IPC方案,它的底层是AIDL,系统为我们做了封装,使用起来比较方便。使用Messenger在不同进程间传递数据,传递的是Message对象,所以需要我们将我们想要传递的数据放入Message对象,Message对象能使用的载体只有what,arg1,arg2,replyTo,Bundle以及object,object只支持系统提供的实现了Parcelable接口的对象,我们自定义实现了Parcelable的对象不能使用。

Messenger构造方法

public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

使用Messenger实现跨进程通讯

  1. 客户端
    首先,我们需要绑定服务端的Service,绑定成功后,通过服务端返回的Binder对象,可以创建出一个Messenger对象,通过Messenger.send()方法便可以向服务端传递一个Message对象,这样就可以实现往服务端传递消息了,消息内容要包装在Meesage对象中。这时,只是客户端能往服务端发送消息,但服务端却不能往客户端发送消息。要实现客户端,服务端的双向通信,我们在客户端还需要创建一个Handler,并通过这个Handler创建一个Messenger对象,然后将这个Messenger对象,赋值给客户端发给服务端的Message中的replyTo字段,这样,服务端发送的消息就会回调给我们客户端创建的这个Handler对象。

  2. 服务端

首先,我们需要创建一个Service,处理客户端的请求,我们还需要创建一个Handler,通过这个Handler创建一个Messenger对象,最后在onBind()方法中,返回这个Messenger的Binder对象给客户端。如果我们需要发送消息给客户端,便可以在这个Handler中,通过客户端发送给服务端的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)
    }

}

服务端:

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)
                }
            }
        }
    }
  • 运行截图
    读书笔记——Android中的IPC方式_第1张图片
    Messenger测试.jpg

总结

Meseenger使用比较简单,能实现的功能也比较简单,它一次只能处理一个请求,所以当大量请求并发到达服务端时,服务端也只能一个个处理,所以它不适用大量的并发请求的情景,Meesnger主要是用来传递消息的,客户端也无法调用服务端方法,如果有这种需求,需要考虑其它的IPC方式。

使用AIDL

  1. AIDL接口简介

在 AIDL文件中,并不是所有数据类型都能够使用,以下是AIDL中支持的类型:

读书笔记——Android中的IPC方式_第2张图片
AIDL接口中支持的数据类型

以上6种数据类型就是AIDL中支持的所有数据类型,其中自定义的Parcelable对象和AIDL对象必须要显式的import,不管它们是否和当前IDL文件位于同一个包内。而且,如果用到了自定义的Parcelabel对象,必须新建一个和它同名的AIDL文件,并且要用parcelable 声明它为Parcelable类型除此之外,AIDL接口中,除了基本数据类型,其他类型的参数必须标明方向:in、out或inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,要合理地使用,因为这在底层都是有开销的。最后,在AIDL接口中只支持声明方法,不支持静态常量,这是区别于传统的接口的。下面我们来看一下例子:

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 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 ;

首先,我们创建一个AIDL文件,IBookManager.aidl,它有四个方法,由于用到了Book类,它是我们自定义的Parcelable对象,所以我们必须显式的将它import进来,IOnNewBookArrivedListener是一个接口,但要在AIDL中使用,所以也需要将它声明为一个AIDL文件,接下来我们来看下客户端和服务端的实现。

客户端

客户端只需要绑定服务端的Service,绑定成功后,把Service返回的Binder对象转化为AIDL接口所属的类型,便可以调用AIDL的方法了。

服务端

服务端需要创建一个Service来处理客户端的请求,然后需要创建一个AIDL文件,然后将暴露给客户端的接口,在这个AIDL文件里声明,最后在Service实现这个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,"收到新添加书籍提醒: 现在书库共有 $count 本书")
        }

    }


    private fun stopMyService() {
        try {
            if(mIBookManager.asBinder().isBinderAlive){
                mIBookManager.unRegisterListener(listener)
            }
            unbindService(mConncetion)
        }catch (e:Exception){

        }
    }

    override fun onDestroy() {
        super.onDestroy()
        stopMyService()
    }

}

我们客户端首先要绑定服务端Service,然后在
onServiceConnected方法中,可以通过IBookManager.Stub.asInterface(service)得到我们的IBookManager对象,接下来我们便可以通过IBookManager调用AIDL文件中方法了。

服务端代码

class BookManagerService: Service() {

    private val mBookList=CopyOnWriteArrayList()
    private val mListeners=RemoteCallbackList()

    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 {
            return mBookList
        }

    }
}

上面是一个服务端Service的典型实现,我们创建了一个继承于IBookManager.Stub的Binder对象,这个对象内部实现了我们AIDL文件中定义的方法,我们在Service的onBind方法中返回它。这里存储Book的List我们采用了CopyOnWriteArrayList,它继承List,支持并发读/写,AIDL方法是在服务端Binder线程池中执行的,所以,当有多个客户端同时访问服务端时,也就会存在多个线程同时访问服务端方法的问题,所以我们必须在服务端处理并发问题,因此这里采用了CopyOnWriteArrayList,前面提到,AIDL支持的List类型只有ArrayList,但是CopyOnWriteArrayList并不继承于ArrayList,而是继承的List,那为什么我们这里还可以使用呢?这是因为虽然我们在服务端用的是CopyOnWriteArrayList,但是,在Binder中访问数据,最终会形成一个新的ArrayList传递给客户端,虽然我们服务端用的是CopyOnWriteArrayList,但传递的时候却已经被转化为了ArrayList。还有一点我们要注意吗,我们存储IOnNewBookArrivedListener是用的RemoteCallbackList,IOnNewBookArrivedListener要在客户端绑定与解绑,虽然我们在客户端绑定与解绑是同一个对象,但是当这个对象传递到服务端时,Binder会把客户端传递的对象解析成一个新的对象,因为,对象跨进程传输的时候,都是一个反序列化过程,所以自然不会是同一个对象,这样就导致我们无法绑定与解绑,这时候我们就可以用RemoteCallbackList,它内部有一个Map,key是IBinder类型,value是CallBack类型,CallBack封装了客户端传递给服务端的Listener,虽然,我们传递的对象,服务端会生成一个新的对象,但是我们客户端和服务端的Binder却是同一个对象,所以,我们服务端只需要找到和客户端Binder一致的对象,就可以找到我们客户端传递过来的对象了,RemoteCallbackList帮助我们实现了这个需求,而且RemoteCallbackList会自动进行线程同步,我们也不需要考虑并发读/写,而且当客户端进程终止后,它会自动删除客户端传递过来的对象,遍历RemoteCallbackList,我们需要采用以下方式:

val count=mListeners.beginBroadcast()
            for (i in 0 until count){
                mListeners.getBroadcastItem(i).onNewBookArrived()
            }
            mListeners.finishBroadcast()

其中,beginBroadcast()和finishBroadcast()必须配对使用。

  • 运行截图
读书笔记——Android中的IPC方式_第3张图片
AIDL测试.jpg

Binder连接池

按照我们常规流程,我们使用AIDL的时候,要创建一个Service,然后创建一个继承于AIDL接口中Stub类的对象,在Service的onBind方法中返回它,客户端绑定服务端Service,就可以访问AIDL中的方法了。那么当我们有很多个AIDL接口时该怎么办呢?要创建很多个Service吗?这显然是不行的,Service本身就是一种系统资源,我们肯定不能创建太多。那怎么办呢?我们要把多个AIDL文件放到一个Service中管理,这样就完美实现了我们的需求,那我们如何做到AIDL文件的统一管理呢?我们可以根据业务模块来划分,每个业务模块创建自己的AIDL接口并实现,然后向服务端提供自己唯一的标识和Binder对象,对于服务端,就只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口可根据不同的业务模块标识,来返回相应的Binder对象,拿到这个对象,就可调用相应的服务端方法了,这也就是Binder连接池。由此可见,Binder连接池的主要目的便是避免重复创建Service。


读书笔记——Android中的IPC方式_第4张图片
Binder连接池.png

下面我们来看下代码实现:

首先,我们创建两个AIDL接口,来模拟客户端有多个AIDL接口的情况

interface IPayManager {
   void pay(double price);
}
interface IBookManager {
   Book getBook();
}

接下来创建这两个AIDL接口的实现类

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))
    }
}

然后,我们创建一个IBinderPool AIDL接口文件,它只有一个方法,负责根据客户端传过来的唯一标识,返回对应的Binder对象给客户端。

interface IBinderPool {
     IBinder queryBinder(int binderCode);
}

接下来我们看看Binder连接池的具体实现

//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)
        }

    }
}

首先,我们创建一个BinderPool作为Binder连接池的具体实现,并且它是一个单例实现,我们在其内部将它和我们的远程Service绑定,创建一个继承于IBinderPool.Stub的静态内部类,并在远程Service的onBind方法中
返回它的对象,然后,我们定义一个queryBinder方法,它接收一个Int类型的参数,通过这个方法我们可以得到我们需要的Binder对象,从而可以调用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

可以看到,我们成功的调用了AIDL文件中的方法。不过,Binder是有可能会突然死亡的,为了使我们的代码更加健壮,我们应该为我们的Binder连接池,增加断线重连机制,当我们远程服务意外终止时,我们的Binder连接池会重新进行连接,而且当我们客户端调用时出现异常,我们也应该重新去获取最新的Binder对象。所以BinderPool中我们应该做如下修改:

 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设置死亡代理
            mIBinderPool?.asBinder()?.linkToDeath(mDeathRecipient,0)
        }

    }

首先我们需要自定义一个继承于IBinder.DeathRecipient的类并创建其对象,在里面实现当Binder死亡时,我们重新绑定远程服务的逻辑,然后在ServiceConnection的onServiceConnected方法中,新增给Binder设置死亡代理。我们也可以在ServiceConnection的onServiceDisconnected方法中重新连接服务。

总结

  • 服务端方法是在服务端Binder线程池中执行的,所以当我们客户端调用服务端方法时,如果方法耗时,注意在子线程中执行,以免阻塞主线程,发生ANR,同理,服务端调用客户端方法,也是在客户端Binder线程中执行的,所以服务端也要保证此方法在线程池中执行,否则,会导致服务端无响应。
  • Binder是会意外死亡的,这时候我们需要重新连接服务,第一种方法是给Binder设置死亡代理,在binderDied方法中,我们可以重新连接服务,第二种方法是在客户端的onServiceDisconnected中,我们可以重新连接服务,这两种方法的区别是,onServiceDisconnected是在客户端的UI线程中被回调,而binderDied方法是在客户端的Binder线程池中被回调。
  • 关于权限验证:
  1. 我们可以在服务端OnBind方法中做权限验证,验证不通过直接返回null,这样客户端就无法访问服务端方法,至于验证方式有很多,比如Permission验证,我们可以自定义一个Permission,我们自己的客户端,只需要声明这个权限,由于别的客户端没有权限,则会验证失败。
    首先,自定义一个我们的权限:
   

定义权限之后,我们就可以在onBind方法中做权限验证:

override fun onBind(intent: Intent?): IBinder? {
        if(checkCallingOrSelfPermission(Manifest.permission.ACCESS_BOOK_SERVICE)==PackageManager.PERMISSION_GRANTED){
            return MyBinder()
        }
        return null
    }

最后,我们需要使用这个定义权限:

   
  1. 我们可以在服务端的onTransact方法中做权限验证,如果验证失败,就直接返回false,这样,客户端就无法执行AIDL的方法了,同样可以采用Permission方式验证,也可以使用Uid和Pid来做验证,可以通过getCallingUid和getCallingPid来拿到客户端的Uid和Pid,通过这两个参数我们可以做一些验证操作,比如验证包名等等。
 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验证的方式客户端和服务端必须是在同一个应用的情况下才能使用,因为不同的应用onBind方法不是同一个Binder调用的,checkCallingPermission方法只能在AIDL IPC方法中调用。

  • 可以通过Binder连接池的方式来管理AIDL文件

使用ContentProvider

前言

ContentProvider是Android提供的专门用于不同应用间进行数据共享的一种方式,ContentProvider的底层实现是Binder,由于系统为我们做了封装,所以它的使用也比较简单。Android系统也给我们预置了很多ContentProvider,例如联系人,通话记录等等,使得我们可以方便的访问这些信息。ContentProvider主要以表格的形式来组织数据,

自定义ContentProvider

创建一个自定义的ContentProvider很简单,只需要继承ContentProvider类并实现其6个抽象方法即可,这6个方法分别是onCreate,query,update,insert,delete和getType,除了onCreate方法由系统回调运行在主线程中,其余5个方法均运行在ContentProvider的进程中,接下来我们简单分析下这6个抽象方法:

  • onCreate
    代表着ContentProvider的创建,一般来说,我们需要在这里做一些初始化工作。

  • insert,delete,update,query
    实现对于数据库的增删改查功能。

  • getType
    用来返回一个Uri请求对应的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?, p2: String?, p3: Array?, 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?): 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?): 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->""
        }
    }
}

总结

  • 我们知道,ContentProvider通过Uri来区分外界需要访问的数据集合,在本例中,BookProvider支持外界访问Book和User两张表,为了知道外界到底要访问哪张表,我们需要为它们定义单独的Uri和Uri_Code,并将Uri和对应的Uri_Code关联,这样,当外界发起请求时,我们就可以通过Uri,得到Uri_Code,从而我们就可以知道外界是要访问哪张表的数据了。

  • 我们可以发现,除了query外,另外的三个方法,insert、delete和update,这三个方法都会引起数据源的改变,所以我们需要手动调用ContentProvider的notifyChange方法,通知外界当前ContentProvider的数据已经发生改变,如果我们需要监听这个改变,可以通过ContentProvider的registerContentObserver方法,注册一个观察者,通过unRegisterContentObserver来取消注册就可以了。

  • 需要注意的是,insert,delete,update,query这四个方法,都是存在多线程并发访问的,因此,在这四个方法内部,我们应该做好线程同步,在本例中,由于CotnentProvider的底层数据是SQLite,并且,只有一个SQLiteDatabase的连接,因此不用考虑线程同步问题,因为SQLiteDatabase内部对数据库的操作已经做了线程同步的处理,但是假如通过多个SQLiteDatabase对象来操作数据库,就无法保证线程同步了,因为多个SQLiteDatabase对象之间无法进行线程同步,再假如ContentProvider的底层数据是一块内存,比如是一个list,这时候也需要考虑线程同步的问题。

  • ContentProvider除了支持对数据源进行增删改查操作,还支持自定义调用,这个过程是通过ContentResolver的call方法和CotentProvider的call方法来完成的。

使用Socket

前言

socket,又称作"套接字",是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应网络传输控制层的TCP和UDP协议。TCP是面向连接的协议,提供稳定的双向通信功能,它的连接的建立需要通过"三次握手"才能完成,它具备超时重试机制,因此有很高的稳定性;UDP是无连接的,提供不稳定的单向通信功能,当然,它也可以实现双向通信,在性能上,它的效率更高,但在稳定性上,它不能保证数据能被正确传输,尤其是在网络阻塞的情况下。Socket支持传输任意的字节流。

使用Socket实现进程间通信

我们设计一个简单的在线客服程序,首先,我们需要创建一个远程Service,在其内部创建一个TCP服务,在客户端连接成功后,客户端就可以给服务端发送消息,服务端接收到客户端发送过来的消息就可以回复消息给客户端了,下面来看下代码实现。

  • 服务端代码
class TCPService :Service() {

    private  var mIsServiceDestroyed=false
    private val replyConent= "我们已经收到您的反馈"

    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
    }
}

首先我们创建一个远程Service,在其内部新开一个线程,建立一个TCP服务,本例中监听的是8888端口,然后等待客户端的连接,待客户端连接成功后,我们会回复客户端一句话:欢迎使用在线客服作为欢迎语。当客户端断开连接后,我们服务端也应该断开socket的连接,这里是通过判断服务端的输入流来判断的,当客户端断开后,服务端的输入流会返回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方法中开启一个线程,连接我们的服务端Socket,并且我们也做了超时重试机制,以免连接失败,为了降低重试机制的开销,我们把每次重试的时间间隔改为1000ms。当客户端连接成功后,我们就可以向服务端传递消息了,本例中是固定的向服务端发送一句话:您好,我有个问题,在连接成功后,我们通过while循环可以不断地读取服务端传递过来的消息,客户端Activity销毁后,我们跳出循环即可。

  • 运行结果
读书笔记——Android中的IPC方式_第5张图片
Socket测试.jpg
  • 总结
  1. 使用Socket实现IPC,需要注意加上访问网络权限
 
 
  1. 本例中,实现了一个客户端与一个服务端之间的通信,实际上也可以实现多个客户端与一个服务端之间的通信,这个可以自行扩展。

选用合适的IPC方式

读书笔记——Android中的IPC方式_第6张图片
IPC方式比较.png

参考

  • 《Android开发艺术探索》

你可能感兴趣的:(读书笔记——Android中的IPC方式)