Android IPC程式間通訊之AIDL和Messenger的使用

weixin_33936401發表於2017-06-09

IPC簡介

Android IPC機制是 Interprocess-communication的簡稱,android系統預設情況下是一個程式預設一個程式例項,不同程式間的資料都是互相獨立,但是google提供了可以對不同程式間通訊的機制,這就是IPC機制。android平臺提供了 AIDL和Messenger來針對不同程式間通訊的方式。

使用場景不同的程式間的通訊

比如一個應用可能一些特殊原因,一個程式的記憶體不夠用這個時候可能就會多開一個程式來獲取系統更大的記憶體,四大元件都支援獨立程式執行,通過在清單檔案中配置 process來實現。這個時候不同程式要通訊就要通過aidl來實現了。 或者是不同應用間的通訊也需要用到IPC機制。

AIDL實現方式

aidl是(Android Interface Definition Language)的簡稱是一種介面描述語言,用來定義程式間通訊的介面。使用aidl首先需要知道那個程式需要暴露什麼介面和資料給別人使用,可以把定義的一端叫做服務端就像web伺服器端一樣提供給別人訪問。有了服務端後,其他應用就可以根據提供的介面來訪問伺服器了。

服務端的AIDL定義

android studio ide提供了生成aidl的快捷方式,這裡建立了IMyService.aidl和Student.aidl。IMyService就是提供介面給其他應用訪問,Student是對映Student實體類。

IMyService.aidl

  /
  package com.jw.code;

  // Declare any non-default types here with import statements

  import com.jw.code.Student;
  interface IMyService {
      /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
      List<Student> getStudent();
      void addStudent(in Student student);
  }

aidl中支援的引數型別為:
1.基本型別(int,long,char,boolean等),String,CharSequence,List,Map,
2.另外,介面中的引數除了aidl支援的型別,其他型別必須標識其方向:到底是輸入還是輸出抑或兩者兼之,用in,out或者inout來表示,上面的程式碼我們用in標記,因為它是輸入型引數

Student.aidl

 
  package com.jw.code;

  // Declare any non-default types here with import statements
  parcelable Student;

1930388-c1c86d6b0965a821.png

build 一下可以看到ide自動生成了對應的.java類

public interface IMyService extends android.os.IInterface {
   /** Local-side IPC implementation stub class. */
   public static abstract class Stub extends android.os.Binder implements com.jw.code.IMyService {
       private static final java.lang.String DESCRIPTOR = "com.jw.code.IMyService";


       /** Construct the stub at attach it to the interface. */
       public Stub() {
           this.attachInterface(this, DESCRIPTOR);
       }


       /**
        * Cast an IBinder object into an com.jw.code.IMyService interface,
        * generating a proxy if needed.
        */
       public static com.jw.code.IMyService asInterface(android.os.IBinder obj) {
           if ((obj == null)) {
               return null;
           }
           android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
           if (((iin != null) && (iin instanceof com.jw.code.IMyService))) {
               return ((com.jw.code.IMyService) iin);
           }
           return new com.jw.code.IMyService.Stub.Proxy(obj);
       }


       @Override
       public android.os.IBinder asBinder() {
           return this;
       }


       @Override
       public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
           switch (code) {
               case INTERFACE_TRANSACTION: {
                   reply.writeString(DESCRIPTOR);
                   return true;
               }
               case TRANSACTION_getStudent: {
                   data.enforceInterface(DESCRIPTOR);
                   java.util.List<com.jw.code.Student> _result = this.getStudent();
                   reply.writeNoException();
                   reply.writeTypedList(_result);
                   return true;
               }
               case TRANSACTION_addStudent: {
                   data.enforceInterface(DESCRIPTOR);
                   com.jw.code.Student _arg0;
                   if ((0 != data.readInt())) {
                       _arg0 = com.jw.code.Student.CREATOR.createFromParcel(data);
                   } else {
                       _arg0 = null;
                   }
                   this.addStudent(_arg0);
                   reply.writeNoException();
                   return true;
               }
           }
           return super.onTransact(code, data, reply, flags);
       }


       private static class Proxy implements com.jw.code.IMyService {
           private android.os.IBinder mRemote;


           Proxy(android.os.IBinder remote) {
               mRemote = remote;
           }


           @Override
           public android.os.IBinder asBinder() {
               return mRemote;
           }


           public java.lang.String getInterfaceDescriptor() {
               return DESCRIPTOR;
           }


           /**
            * Demonstrates some basic types that you can use as parameters
            * and return values in AIDL.
            */
           @Override
           public java.util.List<com.jw.code.Student> getStudent() throws android.os.RemoteException {
               android.os.Parcel _data = android.os.Parcel.obtain();
               android.os.Parcel _reply = android.os.Parcel.obtain();
               java.util.List<com.jw.code.Student> _result;
               try {
                   _data.writeInterfaceToken(DESCRIPTOR);
                   mRemote.transact(Stub.TRANSACTION_getStudent, _data, _reply, 0);
                   _reply.readException();
                   _result = _reply.createTypedArrayList(com.jw.code.Student.CREATOR);
               } finally {
                   _reply.recycle();
                   _data.recycle();
               }
               return _result;
           }


           @Override
           public void addStudent(com.jw.code.Student student) throws android.os.RemoteException {
               android.os.Parcel _data = android.os.Parcel.obtain();
               android.os.Parcel _reply = android.os.Parcel.obtain();
               try {
                   _data.writeInterfaceToken(DESCRIPTOR);
                   if ((student != null)) {
                       _data.writeInt(1);
                       student.writeToParcel(_data, 0);
                   } else {
                       _data.writeInt(0);
                   }
                   mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
                   _reply.readException();
               } finally {
                   _reply.recycle();
                   _data.recycle();
               }
           }
       }

       static final int TRANSACTION_getStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
       static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
   }

   /**
    * Demonstrates some basic types that you can use as parameters
    * and return values in AIDL.
    */
   public java.util.List<com.jw.code.Student> getStudent() throws android.os.RemoteException;

   public void addStudent(com.jw.code.Student student) throws android.os.RemoteException;
}

可以看到自動實現了定義的介面

在java包下面需要建立一個實現Parcelable介面的實體類,IPC通訊傳遞的class必須是可以序列化的。


1930388-b1b4b34656efc363.png
 public class Student implements Parcelable {

     public static final int SEX_MALE = 1;
     public static final int SEX_FEMALE = 2;

     public int sno;
     public String name;
     public int sex;
     public int age;

     public Student() {
     }

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

                 public Student createFromParcel(Parcel in) {
                     return new Student(in);
                 }

                 public Student[] newArray(int size) {
                     return new Student[size];
                 }

             };

     private Student(Parcel in) {
         readFromParcel(in);
     }

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

     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(sno);
         dest.writeString(name);
         dest.writeInt(sex);
         dest.writeInt(age);
     }

     public void readFromParcel(Parcel in) {
         sno = in.readInt();
         name = in.readString();
         sex = in.readInt();
         age = in.readInt();
     }

     @Override
     public String toString() {
         return String.format(Locale.ENGLISH, "Student[ %d, %s, %d, %d ]", sno, name, sex, age);
     }

 }

建立服務端的Service類

    public class MyServer extends Service {

     private final static String TAG = "MyService";
     private static final String PACKAGE_SAYHI = "com.example.test";

     private NotificationManager mNotificationManager;
     private boolean mCanRun = true;
     private List<Student> mStudents = new ArrayList<Student>();


     @Override
     public void onCreate() {
         Thread thr = new Thread(null, new ServiceWorker(), "BackgroundService");
         thr.start();

         synchronized (mStudents) {
             for (int i = 1; i < 6; i++) {
                 Student student = new Student();
                 student.name = "student#" + i;
                 student.age = i * 5;
                 mStudents.add(student);
             }
         }

         mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
         super.onCreate();
     }


     @Override
     public IBinder onBind(Intent intent) {
         Log.d(TAG, String.format("on bind,intent = %s", intent.toString()));
         displayNotificationMessage("服務已啟動");
         return mBinder;
     }


     //這裡實現了aidl中的抽象函式
     private final IMyService.Stub mBinder = new IMyService.Stub() {

         @Override
         public List<Student> getStudent() throws RemoteException {
             synchronized (mStudents) {
                 return mStudents;
             }
         }


         @Override
         public void addStudent(Student student) throws RemoteException {
             synchronized (mStudents) {
                 if (!mStudents.contains(student)) {
                     mStudents.add(student);
                 }
             }
         }


         //在這裡可以做許可權認證,return false意味著客戶端的呼叫就會失敗,比如下面,只允許包名為com.example.test的客戶端通過,
         //其他apk將無法完成呼叫過程
         public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
             String packageName = null;
             String[] packages = MyServer.this.getPackageManager().
                     getPackagesForUid(getCallingUid());
             if (packages != null && packages.length > 0) {
                 packageName = packages[0];
             }
             Log.d(TAG, "onTransact: " + packageName);
             if (!PACKAGE_SAYHI.equals(packageName)) {
                 return false;
             }

             return super.onTransact(code, data, reply, flags);
         }
     };


     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         return super.onStartCommand(intent, flags, startId);
     }


     @Override
     public void onDestroy() {
         mCanRun = false;
         super.onDestroy();
     }


     private void displayNotificationMessage(String message) {
         //Notification notification = new Notification(R.drawable.icon, message, System
         //        .currentTimeMillis());
         //notification.flags = Notification.FLAG_AUTO_CANCEL;
         //notification.defaults |= Notification.DEFAULT_ALL;
         //PendingIntent contentIntent = PendingIntent
         //        .getActivity(this, 0, new Intent(this, MyActivity.class), 0);
         //notification.setLatestEventInfo(this, "我的通知", message, contentIntent);
         //mNotificationManager.notify(R.id.app_notification_id + 1, notification);
     }


     class ServiceWorker implements Runnable {
         long counter = 0;


         @Override
         public void run() {
             // do background processing here.....
             while (mCanRun) {
                 Log.d("scott", "" + counter);
                 counter++;
                 try {
                     Thread.sleep(2000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
     }
 }

onTransact在這裡可以做許可權認證,return false意味著客戶端的呼叫就會失敗,比如下面,只允許包名為com.example.test的客戶端通過,其他apk將無法完成呼叫過程onBind提供定義的Stub,返回給遠端應用使用就是靠這個bind來操作介面。

xml註冊service


 <service android:name=".MyServer">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="com.test.aidl"></action>
            </intent-filter>

        </service>

遠端應用呼叫

如果其他應用想使用這套介面,就必須把定義好的aidl和實體類複製一份到自己專案中。

繫結服務setAction setPackage設定目標引數

    @Override
            public void onClick(View v) {
                Intent intentService = new Intent();
                intentService.setAction(ACTION_BIND_SERVICE);
                intentService.setPackage("com.example.administrator.myapplication");
                MainActivity.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE);
            }

獲取連線&呼叫介面,正確的話可以看到成功的呼叫了遠端應用的介面。

 private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIMyService = null;
        }


        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //通過服務端onBind方法返回的binder物件得到IMyService的例項,得到例項就可以呼叫它的方法了
            mIMyService = IMyService.Stub.asInterface(service);
            try {
                Student student = mIMyService.getStudent().get(0);
                showDialog(student.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };



 public void showDialog(String message) {
        new AlertDialog.Builder(MainActivity.this).setTitle("scott").setMessage(message)
                                                  .setPositiveButton("確定", null).show();
    }

Messenager呼叫

Messenager是一個簡化版的通訊方式客戶端不需要複製服務端的那一套aidl檔案,但是它內部也是通過Hander+Message+aidl來實現通訊的。

服務端返回Messenger

 private Handler mHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            String string = (String) msg.obj;
            Log.i("jinweiaaa", "msg " + string);
        }
    };

把建立的Messenger返回給繫結者

 @Override
    public IBinder onBind(Intent intent) {
         mMessenger = new Messenger(mHandler);
        return mMessenger.getBinder();
    }

客戶端定義Messenager,根據遠端Binder建立一個Messenger。

 Messenger mService = null;

  @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             mService = new Messenger(service);
         }

和伺服器進行通訊,傳送一個msg給服務端。

  public void onClick(View v) {
                  Message obtain = Message.obtain(null, 1, 0, 0);
                  //obtain.obj = "IPC msg  send succ";
                  try {
                      mService.send(obtain);
                  } catch (RemoteException e) {
                      e.printStackTrace();
                  }
              }

Messenger與AIDL的比較

首先,在實現的難度上,肯定是Messenger要簡單的多——至少不需要寫AIDL檔案了(雖然如果認真的究其本質,會發現它的底層實現還是AIDL)。另外,使用Messenger還有一個顯著的好處是它會把所有的請求排入佇列,因此你幾乎可以不用擔心多執行緒可能會帶來的問題。

但是這樣說來,難道AIDL進行IPC就一無是處了麼?當然不是,如果是那樣的話它早就被淘汰了。一方面是如果專案中有併發處理問題的需求,或者會有大量的併發請求,這個時候Messenger就不適用了——它的特性讓它只能序列的解決請求。另外,我們在使用Messenger的時候只能通過Message來傳遞資訊實現互動,但是在有些時候也許我們需要直接跨程式呼叫服務端的方法,這個時候又怎麼辦呢?只能使用AIDL。

所以,這兩種IPC方式各有各的優點和缺點,具體使用哪種就看具體的需要了——當然,能使用簡單的就儘量使用簡單的吧。

原文地址

相關文章