前言
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实现跨进程通讯
客户端
首先,我们需要绑定服务端的Service,绑定成功后,通过服务端返回的Binder对象,可以创建出一个Messenger对象,通过Messenger.send()方法便可以向服务端传递一个Message对象,这样就可以实现往服务端传递消息了,消息内容要包装在Meesage对象中。这时,只是客户端能往服务端发送消息,但服务端却不能往客户端发送消息。要实现客户端,服务端的双向通信,我们在客户端还需要创建一个Handler,并通过这个Handler创建一个Messenger对象,然后将这个Messenger对象,赋值给客户端发给服务端的Message中的replyTo字段,这样,服务端发送的消息就会回调给我们客户端创建的这个Handler对象。服务端
首先,我们需要创建一个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)
}
}
}
}
- 运行截图
总结
Meseenger使用比较简单,能实现的功能也比较简单,它一次只能处理一个请求,所以当大量请求并发到达服务端时,服务端也只能一个个处理,所以它不适用大量的并发请求的情景,Meesnger主要是用来传递消息的,客户端也无法调用服务端方法,如果有这种需求,需要考虑其它的IPC方式。
使用AIDL
-
AIDL接口简介
在 AIDL文件中,并不是所有数据类型都能够使用,以下是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()必须配对使用。
- 运行截图
Binder连接池
按照我们常规流程,我们使用AIDL的时候,要创建一个Service,然后创建一个继承于AIDL接口中Stub类的对象,在Service的onBind方法中返回它,客户端绑定服务端Service,就可以访问AIDL中的方法了。那么当我们有很多个AIDL接口时该怎么办呢?要创建很多个Service吗?这显然是不行的,Service本身就是一种系统资源,我们肯定不能创建太多。那怎么办呢?我们要把多个AIDL文件放到一个Service中管理,这样就完美实现了我们的需求,那我们如何做到AIDL文件的统一管理呢?我们可以根据业务模块来划分,每个业务模块创建自己的AIDL接口并实现,然后向服务端提供自己唯一的标识和Binder对象,对于服务端,就只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口可根据不同的业务模块标识,来返回相应的Binder对象,拿到这个对象,就可调用相应的服务端方法了,这也就是Binder连接池。由此可见,Binder连接池的主要目的便是避免重复创建Service。
下面我们来看下代码实现:
首先,我们创建两个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日志:
可以看到,我们成功的调用了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线程池中被回调。
- 关于权限验证:
- 我们可以在服务端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
}
最后,我们需要使用这个定义权限:
- 我们可以在服务端的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销毁后,我们跳出循环即可。
- 运行结果
- 总结
- 使用Socket实现IPC,需要注意加上访问网络权限
- 本例中,实现了一个客户端与一个服务端之间的通信,实际上也可以实现多个客户端与一个服务端之间的通信,这个可以自行扩展。
选用合适的IPC方式
参考
- 《Android开发艺术探索》