Android 進程間通信Service 兩種實現方式 AIDL 、Messenger

這篇我們講下如何使用service實現進程間通信 。通常講到這裏大家都會想到aidl,其實不然,aidl只是其中的一種方式,並且使用起來,我個人感覺太不靈活了,相對Messenger更加靈活。

整個過程我們還是藉助於 Activity 跨進程通信  中使用的項目

1.AIDL 跨進程通訊 

   aidl 是個簡寫,全拼android interface difine language(android自定義接口語言)

  服務端:

    1.1先從新建開始,選中main 右鍵選擇aidl file 新建,會在main 下生成aidl文件夾,我的新建默認名字叫

IMyAidlInterface,也就是IMyAidlInterface.aidl文件。這個就是我們要定義的接口類,默認會有
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
        double aDouble, String aString);接口,也就是默認支持六種基本數據類型

先寫兩個基本數據類型,再自定義一個自定義接口

package com.example.intentmode;
import com.example.intentmode.Person;

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

interface IRemoteService {
            void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
            int getPid();
            String getName(int id);
            Person getPerson(int id);
}

先彆着急,

1.2 Person getPerson(int id);是會報錯的。現在定義Person類,在和IRemoteService類平級目錄下新建Person類,並且必須實現Parcelable接口,爲啥Serializable 接口不行呢,在android 中都使用Parcelable接口因爲Serializable的序列化效率相對較低。

Person 如下:

public class Person implements Parcelable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

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

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

    public Person() {
    }

    protected Person(Parcel in) {
        this.name = in.readString();
        this.age = in.readInt();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel source) {
            return new Person(source);
        }

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

1.3 新建Person.aidl類 :

// Person.aidl
package com.example.intentmode;

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

parcelable Person;

1.4 新建服務類 ServiceAIDL ,繼承自Service

 

public class ServiceAIDL extends Service {
    private String [] names={"呂布","關羽","趙子龍","張飛"};
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    private final IMyAidlInterface.Stub mBinder=new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("basicTypes aDouble: " + aDouble +" anInt: " + anInt+" aBoolean " + aBoolean+" aString " + aString);
        }

        @Override
        public int getPid() throws RemoteException {
            System.out.println("Thread: " + Thread.currentThread().getName());
            System.out.println("RemoteService getPid ");
            return android.os.Process.myPid();
        }

        @Override
        public String getName(int id) throws RemoteException {
            return names[id];
        }

        @Override
        public Person getPerson(int id) throws RemoteException {
            Person person=new Person();
            try {
                person.setAge(20);
                person.setName(names[id]);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return person;
        }
    };
}

 

在這裏我們定義了接口的返回數據,用於客戶端調用時返回想要的數據,其實叫他服務端另一端客戶端是有問題的,他們是可以相互調用的,我們暫且這麼叫吧。這裏有的朋友可能會報attempting to use incompatible return type ,這是因爲使用自定義類型要在接口類中導入包名,

package com.example.csdnactivity;

// Declare any non-default types here with import statements
//這個要手動導入下
import com.example.csdnactivity.Person;
interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
               double aDouble, String aString);
               int getPid();
               String getName(int id);
               Person getPerson(int id);
}

1.5 註冊service 並且設置標籤 action

  <service android:name=".ServiceAIDL">
            <intent-filter>
                <action android:name="com.example.intentmode"/>
            </intent-filter>
        </service>

需要注意的是,aidl寫完後需要重新構建下,會在build --> generated--> source --> aidl --> debug--> 接口類

如果沒有,有可能是代碼有錯誤了。

服務端結束,下面看客戶端

客戶端:

1.1 將服務端的aidl文件夾粘貼到客戶端的main文件下。

1.2 綁定客戶端service

        
//服務端設置的action
        intent.setAction("com.example.intentmode");
        //服務端的包名
        intent.setPackage("com.example.csdnactivity");
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
package com.example.csdnactivity1;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.example.csdnactivity.IMyAidlInterface;
import com.example.csdnactivity.Person;

/**
 * Created by LCT
 * Time:2018/12/24 13:49.
 * Annotation:
 */
public class AIDLActivity extends Activity implements View.OnClickListener {
    private static final String TAG = "AIDLActivity";
    /**
     * 獲取遠aidl程數據
     */
    private Button mGetData;
    IMyAidlInterface iMyAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.aidl_activity);
        initView();
        bindService();
    }

    private void bindService() {
        Intent intent = new Intent();
        //服務端設置的action
        intent.setAction("com.example.intentmode");
        //服務端的包名
        intent.setPackage("com.example.csdnactivity");
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * IBinder 轉爲IRemoteService接口
             */
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void initView() {
        mGetData = (Button) findViewById(R.id.get_Data);
        mGetData.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            default:
                break;
            case R.id.get_Data:
                /**
                 * 通過iMyAidlInterface 獲取數據
                 */
                try {
                    if (iMyAidlInterface != null) {
                        String name = iMyAidlInterface.getName(0);
                        iMyAidlInterface.basicTypes(12, 1223, true, 12.2f, 12.3, "有夢就要去追,加油!");
                        Toast.makeText(this, name, Toast.LENGTH_SHORT).show();
                        Log.d(TAG, "onClick: name:"+ name);
                        Person person = iMyAidlInterface.getPerson(0);
                        String str=person.getName() + "_" +person.getAge();
                        Log.d(TAG, "onClick: person:"+ str);
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
        }
    }
}

客戶端代碼較少,現在可以運行服務端,如果你使用的是studio 使用了默人新建的build.gradle 文件,那麼現在項目可能運行不起來,會提示你:錯誤:找不到符號符號:類 Person 我用的3.2的編輯器,即使這樣還是不太智能需要手動寫點東西

在build.gradle  添加如下代碼

 sourceSets {
        main {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }

這樣問題就解決了。

現在可以運行服務端,然後啓動客戶端,獲取數據了。

1.Messenger跨進程通訊

  原理與實現

      這個相對aidl相對比較簡單,並不是代碼實現少,而是比較好理解,首先在服務端會有一個Messenger 它綁定了一個Hander ,客戶端同樣有一個Messenger 同樣綁定了一個Hander ,然後客戶端綁定服務通過service onBind返回服務端的Messenger對象,然後將客戶端的Messenger    Message.replyTo=cMessenger到服務端的messenger中,然後發送消息,服務端在Hander收到消息,得到客戶端的Messenger ,這樣就各自持有對方messenger對象,互相發送消息了。接着我們看看具體實現。

服務端

1.1 新建Messenger對象

  Messenger有兩個構造函數分別是

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

handler用於綁定Messenger,接收消息。

IBinder用於將服務端的IBinder構造爲Messenger對象

 @Override
    public void onCreate() {
        super.onCreate();
        sMessenger=new Messenger(handler);
    }

1.2獲取Messenger 的IBinder通過onBind返回

 public IBinder onBind(Intent intent) {
        return sMessenger.getBinder();
    }

1.3Handler 接收數據處理,將收到的客戶端Messenger保存,並處理相關邏輯

 Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg!=null) {
                Log.d(TAG, "handleMessage:  收到客戶端發來的消息");
                /**
                 * 保存客戶端Messenger,用於向客戶端發送消息
                 */
                if (msg.replyTo!=null) {
                    cMessenger=msg.replyTo;
                }
                Bundle bundle=msg.getData();
                String ms;
                if (bundle !=null) {
                    ms=bundle.getString("serviceData");
                }else {
                    Log.d(TAG, "handleMessage: null bundle");
                    return;
                }
                if (ms==null) {
                    Toast.makeText(ServiceMessenger.this,"客戶端收到空消息" ,Toast.LENGTH_LONG).show();
                    return;
                }
                /**
                 * 如果收到消息1 就往客戶端發送一條消息
                 */
                if (ms.equals("1")) {
                    Message message=Message.obtain();
                    Bundle bundle1=new Bundle();
                    bundle1.putString("clientData","2");
                    message.setData(bundle1);
                    try {
                        cMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else {
                    Toast.makeText(ServiceMessenger.this,"收到客戶端發來的消息"+ms,Toast.LENGTH_LONG).show();
                }
            }

        }
    };

服務端的完整代碼

public class ServiceMessenger extends Service {
    private static final String TAG = "ServiceMessenger";
    /**
     * 服務端Messenger
     */
    Messenger sMessenger;
    /**
     * 客戶端 Messenger
     */
    Messenger cMessenger=null;
    @Override
    public void onCreate() {
        super.onCreate();
        sMessenger=new Messenger(handler);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return sMessenger.getBinder();
    }
    Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg!=null) {
                Log.d(TAG, "handleMessage:  收到客戶端發來的消息");
                /**
                 * 保存客戶端Messenger,用於向客戶端發送消息
                 */
                if (msg.replyTo!=null) {
                    cMessenger=msg.replyTo;
                }
                Bundle bundle=msg.getData();
                String ms;
                if (bundle !=null) {
                    ms=bundle.getString("serviceData");
                }else {
                    Log.d(TAG, "handleMessage: null bundle");
                    return;
                }
                if (ms==null) {
                    Toast.makeText(ServiceMessenger.this,"客戶端收到空消息" ,Toast.LENGTH_LONG).show();
                    return;
                }
                /**
                 * 如果收到消息1 就往客戶端發送一條消息
                 */
                if (ms.equals("1")) {
                    Message message=Message.obtain();
                    Bundle bundle1=new Bundle();
                    bundle1.putString("clientData","2");
                    message.setData(bundle1);
                    try {
                        cMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }else {
                    Toast.makeText(ServiceMessenger.this,"收到客戶端發來的消息"+ms,Toast.LENGTH_LONG).show();
                }
            }

        }
    };
}

1.4.註冊service 並設置action,action用來客戶端綁定服務時使用

 <service android:name=".ServiceMessenger">
            <intent-filter>
                <action android:name="com.android.serviceMessenger.action" />
            </intent-filter>
        </service>

服務端結束。

 

客戶端

1.1 創建客戶端Messenger ,綁定handler,用於接收消息

cMessenger = new Messenger(handler);

1.2 綁定服務並監聽綁定狀態,獲取服務端的Messenger對象。

 private void bindMessengerService() {
        Intent intent = new Intent();
        //服務端設置的action
        intent.setAction("com.android.serviceMessenger.action");
        //服務端的包名
        intent.setPackage("com.example.csdnactivity");
        bindService(intent, connMessenger, Context.BIND_AUTO_CREATE);
    }

獲取服務端Messenger,並將客戶端的Messenger發送到服務端

 private ServiceConnection connMessenger = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * 得到服務端的messenger
             */
            sMessenger = new Messenger(service);
            if (sMessenger == null) {
                Log.d(TAG, "onServiceConnected:  messenger is null");
                return;
            }
            /**
             * 發送客戶端的messenger到服務端
             */
            Message message = Message.obtain();
            message.replyTo = cMessenger;
            try {
                sMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

 

1.3 發送消息到服務端

 String str = mSendDataToServiceEdit.getText().toString();
                if (str != null) {
                    Message message = Message.obtain();
                    Bundle bundle = new Bundle();
                    bundle.putString("serviceData",str);
                    message.setData(bundle);
                    try {
                        sMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }

完整代碼類

 

public class MessengerActivity extends Activity implements View.OnClickListener {
    private static final String TAG = "MessengerActivity";
    /**
     * 輸入發送的信息 通過Messenger 發送
     */
    private EditText mSendDataToService;
    /**
     * SENd
     */
    private Button mSendData;
    /**
     * 服務端 Messenger
     */
    Messenger sMessenger;
    /**
     * 客戶端 Messenger
     */
    Messenger cMessenger;
    /**
     * messenger
     */
    private Button mMessengerButton;
    /**
     * 輸入發送的信息 通過Messenger 發送
     */
    private EditText mSendDataToServiceEdit;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.messenger_activity);
        initView();
        initMessenger();
    }

    private void initMessenger() {
        cMessenger = new Messenger(handler);
        bindMessengerService();

    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Bundle bundle = msg.getData();
            if (bundle != null) {
                String ms = bundle.getString("clientData");
                if (ms.equals("2")) {
                    Toast.makeText(MessengerActivity.this, "收到服務端發來的數據了" + ms + "", Toast.LENGTH_LONG).show();
                }
            }
        }

        ;
    };

    private void bindMessengerService() {
        Intent intent = new Intent();
        //服務端設置的action
        intent.setAction("com.android.serviceMessenger.action");
        //服務端的包名
        intent.setPackage("com.example.csdnactivity");
        bindService(intent, connMessenger, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection connMessenger = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            /**
             * 得到服務端的messenger
             */
            sMessenger = new Messenger(service);
            if (sMessenger == null) {
                Log.d(TAG, "onServiceConnected:  messenger is null");
                return;
            }
            /**
             * 發送客戶端的messenger到服務端
             */
            Message message = Message.obtain();
            message.replyTo = cMessenger;
            try {
                sMessenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private void initView() {
        mSendDataToServiceEdit = (EditText) findViewById(R.id.send_data_to_service_edit);
        mSendDataToServiceEdit.setOnClickListener(this);
        mSendData = (Button) findViewById(R.id.send_data);
        mSendData.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            default:
                break;
            case R.id.send_data:
                String str = mSendDataToServiceEdit.getText().toString();
                if (str != null) {
                    Message message = Message.obtain();
                    Bundle bundle = new Bundle();
                    bundle.putString("serviceData",str);
                    message.setData(bundle);
                    try {
                        sMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
        }
    }
}

xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <EditText
            android:id="@+id/send_data_to_service_edit"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_toLeftOf="@+id/send_data"
            android:hint="輸入發送的信息 通過Messenger 發送"
            android:textSize="14sp" />

        <Button
            android:id="@+id/send_data"
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:layout_alignParentRight="true"
            android:text="SENd" />
    </RelativeLayout>

</LinearLayout>

 

客戶端完畢。這裏注意一點,在調試時記得將兩端都啓動起來,客戶端可以向服務端通過輸入框發送消息,服務端收到後會toast出來,如果發送的數據是1,那麼服務端會給客戶端發送一條消息內容爲 2,客戶端會toast出來,整個service 跨進程通信就這麼多了。

 

 

service 跨進程通信源碼

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章