Android之Service使用AIDL实现跨进程通讯

概述

在Andorid 系统中,不同的app,独立运行于不同的进程,进程之间无法直接通讯,如果要实现两者通讯,可以通过创建aidl文件(底层是利用Binder机制)去实现。
 

 

AIDL是什么?

最直观的表现是一个*.aidl 为后缀的文件,可以这样理解:不同app就像不同国家的人,因为语言不同无法顺利沟通,因此他们只能寻求一种通用的国际语言去解决这个问题,如英语。那么这个 aidl 就相当于英语。

 

异常处理

在Binder调用过程中,即使服务端抛出异常,服务端也不会挂 (服务端中系统默认是对异常进行了try异常捕获的处理,所以挂不了)。
1、抛出的异常有些能够传到客户端,比如NullPointException,这时客户端就会挂
2、但有些异常不能传到客户端,比如RuntimeException,这时,客户端也不会挂
如果Binder调用过程中,服务端的代码是创建了新的线程中抛出异常(在这个线程中没有进行异常捕获处理),服务端会挂,客户端不会挂。

 

注意事项:详看文章后面说明

  • 客户端调用远程服务是同步的,如果客户端在UI线程调用远程服务就有可能因为服务端的耗时方法导致ANR,这种情况应该使用子线程来处理

  • 传递自定义对象的时候,注意aidl 文件夹和 java 文件夹的路径必须一致

 

常见应用示例

Service跨进程通讯的常见有两种:应用内通讯 和 应用间通讯。

  • App间的通讯,是指分别属于两个 app 的不同进程进行通讯(重点)
  • App内的通讯,是指一个app内,有两个不同的进程间进行通讯(没啥意义)

PS:这两种其实都是一样的处理方法,
跨进程通信的真正意义是为了让一个应用程序去访问另一个应用程序中的Service,以实现共享Service的功能。

所以app 内部很少用到跨进程通讯,但也有一定的例外,如微信为提高内存性能,单独为webview创建一个独立进程,如果这个webview 需要和原app 进程交互,可以使用这种做法。
有人也许会问:不同进程无法直接通讯,同一个app 内,用static 静态变量去实现,可不可以呢?
答案是肯定的,同一个app 内,不同进程间可以共享一个static变量

 

常见示例 1 ----App 之间的通讯

如下图:

1、服务端启动了一个service服务,这个service服务具备:1、加法,2、打印输出   功能

2、客户端绑定服务端的Service

3、客户端传入参数,调用service功能

4、客户端显示调用结果

 

一、创建服务端app

  • 1、创建aidl文件,定义跨进程要用到的方法
  • 2、编译服务端app,aidl工具自动生成Stub抽象类
  • 3、创建MyService继承Service,在类中:
           1)实现Stub抽象类的方法(就是aidl定义的方法)
           2)把这个Stub实现类作为onBind方法的返回值
  • 4、在AndroidManifest.xml文件,配置MyService为共享进程(声明为process、自定义intent-filter)

 

下面来看step 1 的具体操作:

 

step 2 :重新编译一下工程

 

step 3 :创建MyService类,继承Service,主要是:重写Service的OnBind方法、实现Stub抽象类

 

step 4 :在AndroidManifest.xml 中注册MyService,允许其他app进程调用它

 

 

二、创建客户端app

  • 1、复制服务端 app 的 aidl 路径 + 文件 到客户端app的相同位置
  • 2、开始连接服务端,获取到服务端app提供的对象,操作这个对象完成业务逻辑。

具体操作步骤如下:

step 1 :复制服务端 app 到 客户端(相同路径、相同文件)

 

step 2:连接服务端app,找到ITest对象,使用ITest里面的方法实现业务。

 

我们来验证一下效果,可以看到:

  • 一共有3个进程,它们分别是:
     1、客户端进程  com.test.binderclient
     2、服务端进程  com.test.binderserver(MainActivity所在进程)、com.test.myservice(MyService所在进程)
     3、logat 日志打印 运行在MyService所在进程

 

 

 

常见示例 2 ----App内部通讯

1、创建aidl文件,定义跨进程通讯方法后,rebuild project

2、创建MyService 继承 Service 类:
       1)实现Stub抽象类
       2)重写onBind方法,把Stub实现类返回

3、在 AndroidManifest.xml 中注册MyService类,把它设置为独立进程
       1)android:process=":my_remote" 设置为应用私有进程
       2)设置intent-filter 的 action,允许其他内部进程访问

4、连接到MyService,获取到接口对象,利用里面方法实现业务逻辑。

 

step 1 :创建aidl文件,在接口文件自定义方法

 

step 2 :继承Service类,实现Stub抽象类并返回

 

step 3 :在AndroidManifest.xml 文件注册MyService

 

step 4 :内部调用

 

效果如图:

示例代码:https://github.com/lvxiangan/Service-AIDL.git

 

注意事项:

1)如何传递自定义对象?

来个项目示例,工程结构如下:

AIDL 要传递的自定义对象(Book.java),必须实现Parcelable 接口。内容如下:

package com.test.aidl_demo;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * 书的序列化类
 */
public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {

        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }

    };

    private Book(Parcel source) {
        bookId = source.readInt();
        bookName = source.readString();
    }

    @Override
    public String toString() {
        return "ID: " + bookId + ", BookName: " + bookName;
    }
}

定义AIDL 接口,内容如下:

package com.test.aidl_demo;

import com.test.aidl_demo.Book;
import com.test.aidl_demo.IOnNewBookArrivedListener;

interface IBookManager {

    List<Book> getBookList();                                       // 返回书籍列表
    void addBook(in Book book);                                     // 添加书籍
    void registerListener(IOnNewBookArrivedListener listener);      // 注册接口
    void unregisterListener(IOnNewBookArrivedListener listener);    // 注册接口

}

上面用到Book.java类,因此需要创建Book.aidl 文件,声明它是一个parcelable 类型的变量。Book.aidl 内容如下:

// Book.aidl
package com.test.aidl_demo; // 关键点1
// 注意,Book.aidl 在app/src/main/aidl 文件夹下面的层级路径,必须和
   Book.java 在app/src/main/java 的层级路径保持一致,否则编译报错。



// 关键点2:因为IBookManager.aidl 用到自定义的Book对象,而Book又实现了Parcelable 接口,
// 所以需要在AIDL 中声明为 parcelable(小写) 类型
parcelable Book;  

注意:这里有两个关键点,请参考注释。

bean 类和声明bean的aidl目录结构是一样的,并且这个aidl 相当与c的文件头,不参与编译。

关键点1:如果路径不一致,报错如下:Process 'command 'D:\SDK\build-tools\27.0.3\aidl.exe'' finished with non-zero exit value 1

 

定义IOnNewBookArrivedListener.aidl,用于通知:

package com.test.aidl_demo;

import com.test.aidl_demo.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

 

使用应用内通讯

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.test.aidl_demo"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="com.test.aidl_demo.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>



        <!--与主应用不在同一进程中-->
        <service
            android:name="com.test.aidl_demo.BookManagerService"
            android:process=":remote"/>
    </application>

</manifest>

创建服务端

package com.test.aidl_demo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import android.util.Log;

import com.test.aidl_demo.IBookManager;
import com.test.aidl_demo.IOnNewBookArrivedListener;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 书籍管理服务
 */
public class BookManagerService extends Service {

    private static final String TAG = "DEBUG-TAG: " + BookManagerService.class.getSimpleName();

    // 支持并发读写
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);


    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            SystemClock.sleep(5000); // 延迟加载
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            int num = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.e(TAG, "添加完成, 注册接口数: " + num);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
            int num = mListenerList.beginBroadcast();
            mListenerList.finishBroadcast();
            Log.e(TAG, "删除完成, 注册接口数: " + num);
        }
    };


    // 向注册监听的, 发送新书的添加通知.
    private void onNewBookArrived(Book book) throws RemoteException {
        mBookList.add(book);
        Log.e(TAG, "发送通知的数量: " + mBookList.size());
        int num = mListenerList.beginBroadcast();
        for (int i = 0; i < num; ++i) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            Log.e(TAG, "发送通知: " + listener.toString());
            listener.onNewBookArrived(book);
        }
        mListenerList.finishBroadcast();
    }


    // 服务启动时, 添加两本新书, 并使用线程继续添加.
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "iOS"));
        new Thread(new ServiceWorker()).start();
    }


    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }


    private int num = 0;

    // 添加书籍, 并发送通知.
    private class ServiceWorker implements Runnable {
        @Override
        public void run() {
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num++;
                if (num == 5) {
                    mIsServiceDestroyed.set(true);
                }
                Message msg = new Message();
                mHandler.sendMessage(msg); // 向Handler发送消息,更新UI
            }
        }
    }

    private Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            int bookId = 1 + mBookList.size();
            Book newBook = new Book(bookId, "新书#" + bookId);
            try {
                onNewBookArrived(newBook);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

}

创建客户端

package com.test.aidl_demo;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.test.aidl_demo.IBookManager;
import com.test.aidl_demo.IOnNewBookArrivedListener;


import java.util.List;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "DEBUG_TAG: " + MainActivity.class.getSimpleName();
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
    private TextView mTvBookList;
    private IBookManager mRemoteBookManager;


    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };


    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(TAG, "收到的新书: " + msg.obj);
                    new BookListAsyncTask().execute();
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    };


    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mRemoteBookManager = bookManager;
                Book newBook = new Book(3, "学姐的故事");
                bookManager.addBook(newBook);
                new BookListAsyncTask().execute();   关键点3 
                bookManager.registerListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mRemoteBookManager = null;
            Log.e(TAG, "绑定结束");
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvBookList = (TextView) findViewById(R.id.main_tv_book_list);

    }

    @Override
    protected void onDestroy() {
        if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
            try {
                Log.e(TAG, "解除注册");
                mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        try {
            unbindService(mConnection);
        } catch (Exception e) {
            e.printStackTrace();
        }
        super.onDestroy();
    }

    /**
     * 绑定服务按钮的点击事件
     *
     * @param view 视图
     */
    public void bindService(View view) {
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    /**
     * 获取图书数量的异步线程, 直接绑定点击事件
     *
     * @param view 视图
     */
    public void getBookList(View view) {
        new BookNumAsyncTask().execute();
        Toast.makeText(getApplicationContext(), "正在获取中...", Toast.LENGTH_SHORT).show();
    }

    private class BookNumAsyncTask extends AsyncTask<Void, Void, Integer> {
        @Override
        protected Integer doInBackground(Void... params) {
            return getListNum();
        }

        @Override
        protected void onPostExecute(Integer integer) {
            Toast.makeText(getApplicationContext(), "图书数量: " + integer, Toast.LENGTH_SHORT).show();
        }
    }

    private class BookListAsyncTask extends AsyncTask<Void, Void, List<Book>> {
        @Override
        protected List<Book> doInBackground(Void... params) {
            List<Book> list = null;
            try {
                list = mRemoteBookManager.getBookList();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            return list;
        }

        @Override
        protected void onPostExecute(List<Book> books) {
            String content = "";
            for (int i = 0; i < books.size(); ++i) {
                content += books.get(i).toString() + "\n";
            }
            mTvBookList.setText(content);
        }
    }

    /**
     * 获取列表的图书数量
     *
     * @return 数量
     */
    private int getListNum() {
        int num = 0;
        if (mRemoteBookManager != null) {
            try {
                List<Book> list = mRemoteBookManager.getBookList();
                num = list.size();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return num;
    }

}

注意 关键点3:获取IBinder 服务成功后,Client 端开始向 Server 端请求执行耗时的操作,此时Client 端不会往下执行,直到等待结果的返回(这个等待的阻塞不是UI 线程阻塞,因为等待也是一种正常的状态),但执行请求需要使用子线程去请求结果,如果不这样做,Client 端就会出现ANR 的情况。

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值