Android Service詳解(二)

Lebens發表於2019-02-23

上一篇我們瞭解了Service的一些概念以及使用方式,這篇著重講解使用Service實現IPC通訊的2中方式。

藉助AIDL實現IPC通訊

一、程式碼實操---與遠端程式的Service繫結

上面的程式碼都是在當前程式內跟Service通訊,現在我們來實現一下,不同程式內Service如何繫結。

AIDL:Android Interface Definition Language,即Android介面定義語言。

Service跨程式傳遞資料需要藉助aidl,主要步驟是這樣的:

  1. 編寫aidl檔案,AS自動生成的java類實現IPC通訊的代理
  2. 繼承自己的aidl類,實現裡面的方法
  3. 在onBind()中返回我們的實現類,暴露給外界
  4. 需要跟Service通訊的物件通過bindService與Service繫結,並在ServiceConnection接收資料。

我們通過程式碼來實現一下:

  1. 首先我們需要新建一個Service

    public class MyRemoteService extends Service {
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId());
        return null;
    	}
    }
    複製程式碼
  2. 在manifest檔案中宣告我們的Service同時指定執行的程式名,這裡並是不只能寫remote程式名,你想要程式名都可以

    <service
            android:name=".service.MyRemoteService"
            android:process=":remote" />
    
    複製程式碼
  3. 新建一個aidl檔案使用者程式間傳遞資料。

    AIDL支援的型別:八大基本資料型別、String型別、CharSequence、List、Map、自定義型別。List、Map、自定義型別放到下文講解。

    新建aidl檔案

    裡面會有一個預設的實現方法,刪除即可,這裡我們新建的檔案如下:

    package xxxx;//aidl所在的包名
    //interface之前不能有修飾符
    interface IProcessInfo {
    	//你想要的通訊用的方法都可以在這裡新增
    	int getProcessId();
    }
    複製程式碼
  4. 實現我們的aidl類

    public class IProcessInfoImpl extends IProcessInfo.Stub {
    	@Override
    	public int getProcessId() throws RemoteException {
      		return android.os.Process.myPid();
    	}
    }
    複製程式碼
  5. 在Service的onBind()中返回

    public class MyRemoteService extends Service {
    	IProcessInfoImpl mProcessInfo = new IProcessInfoImpl();
    	@Nullable
    	@Override
    	public IBinder onBind(Intent intent) {
      		Log.e("MyRemoteService", "MyRemoteService thread id = " + Thread.currentThread().getId());
        return mProcessInfo;
    	}
    }
    複製程式碼
  6. 繫結Service

     mTvRemoteBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyRemoteService.class);
                bindService(intent, mRemoteServiceConnection, BIND_AUTO_CREATE);
            }
        });
    
    
    mRemoteServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
    
                Log.e("MainActivity", "MyRemoteService onServiceConnected");
    			// 通過aidl取出資料
                IProcessInfo processInfo = IProcessInfo.Stub.asInterface(service);
                try {
                    Log.e("MainActivity", "MyRemoteService process id = " + processInfo.getProcessId());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity", "MyRemoteService onServiceDisconnected");
            }
        };
    複製程式碼

只要繫結成功就能在有log列印成MyRemoteService所在程式的程式id。這樣我們就完成了跟不同程式的Service通訊的過程。

二、程式碼實操---呼叫其他app的Service

跟調同app下不同程式下的Service相比,呼叫其他的app定義的Service有一些細微的差別

  1. 由於需要其他app訪問,所以之前的bindService()使用的隱式呼叫不在合適,需要在Service定義時定義action

    我們在定義的執行緒的App A 中定義如下Service:

    <service android:name=".service.ServerService">
    	<intent-filter>
    		//這裡的action自定義
        	<action android:name="com.jxx.server.service.bind" />
          <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
    複製程式碼
  2. 我們在需要bindService的App B 中需要做這些處理

    • 首先要將A中定義的aidl檔案複製到B中,比如我們在上面定義的IProcessInfo.aidl這個檔案,包括路徑在內需要原封不動的複製過來。

    • 在B中呼叫Service通過顯式呼叫

      mTvServerBind.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              Intent intent = new Intent();
              intent.setAction("com.jxx.server.service.bind");//Service的action
              intent.setPackage("com.jxx.server");//App A的包名
              bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE);
          }
      });
      複製程式碼
aidl中自定義物件的傳遞

主要步驟如下:

  1. 定義自定物件,需要實現Parcelable介面
  2. 新建自定義物件的aidl檔案
  3. 在傳遞資料的aidl檔案中引用自定義物件
  4. 將自定義物件以及aidl檔案拷貝到需要bindService的app中,主要路徑也要原封不動

我們來看一下具體的程式碼:

  1. 定義自定義物件,並實現Parcelable介面

    public class ServerInfo implements Parcelable {
    
    public ServerInfo() {
    
    }
    
    String mPackageName;
    
    public String getPackageName() {
        return mPackageName;
    }
    
    public void setPackageName(String packageName) {
        mPackageName = packageName;
    }
    
    protected ServerInfo(Parcel in) {
        mPackageName = in.readString();
    }
    
    public static final Creator<ServerInfo> CREATOR = new Creator<ServerInfo>() {
        @Override
        public ServerInfo createFromParcel(Parcel in) {
            return new ServerInfo(in);
        }
    
        @Override
        public ServerInfo[] newArray(int size) {
            return new ServerInfo[size];
        }
    };
    
    @Override
    public int describeContents() {
        return 0;
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mPackageName);
    }
    
    //使用out或者inout修飾時需要自己新增這個方法
    public void readFromParcel(Parcel dest) {
        mPackageName = dest.readString();
    }
    }
    複製程式碼
  2. 新建自定義物件的aidl檔案

    package com.jxx.server.aidl;
    //注意parcelable 是小寫的
    parcelable ServerInfo;
    複製程式碼
  3. 引用自定義物件

    package com.jxx.server.aidl;
    //就算在同一包下,這裡也要導包
    import com.jxx.server.aidl.ServerInfo;
    interface IServerServiceInfo {
    	ServerInfo getServerInfo();
    	void setServerInfo(inout ServerInfo serverinfo);
    }
    複製程式碼

    注意這裡的set方法,這裡用了inout,一共有3種修飾符

    - in:客戶端寫入,服務端的修改不會通知到客戶端
    - out:服務端修改同步到客戶端,但是服務端獲取到的物件可能為空
    - inout:修改都收同步的
    複製程式碼

    當使用out和inout時,除了要實現Parcelable外還要手動新增readFromParcel(Parcel dest)

  4. 拷貝自定義物件以及aidl檔案到在要引用的App中即可。

  5. 引用

    mServerServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                IServerServiceInfo serverServiceInfo = IServerServiceInfo.Stub.asInterface(service);
                try {
                    ServerInfo serviceInfo = serverServiceInfo.getServerInfo();
                    Log.e("MainActivity", "ServerService packageName = " + serviceInfo.getPackageName());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.e("MainActivity", "ServerService onServiceDisconnected");
            }
        };
    複製程式碼

List、Map中引用的物件也應該是符合上面要求的自定義物件,或者其他的幾種資料型別。

使用Messenger實現IPC通訊

步驟是這樣的:

  1. 在Server端新建一個Messenger物件,用於響應Client端的註冊操作,並在onBind()中傳遞出去
  2. 在Client端的ServiceConnection中,將Server端傳遞過來的Messenger物件進行儲存
  3. 同時Client端也新建一個Messenger物件,通過Server傳遞過來的Messenger註冊到Server端,保持通訊用。
  4. 不管是否進行unbindService()操作,只要Client保有Server端的Messenger物件,仍然能和Server端進行通訊。

一、Server端程式碼

public class MessengerService extends Service {

    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_VALUE = 3;

    //這個是給client端接收引數用的
    static final int MSG_CLIENT_SET_VALUE = 4;

    static class ServiceHandler extends Handler {

        private final List<Messenger> mMessengerList = new ArrayList<>();

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_REGISTER_CLIENT:
                    mMessengerList.add(msg.replyTo);
                    break;
                case MSG_UNREGISTER_CLIENT:
                    mMessengerList.remove(msg.replyTo);
                    break;
                case MSG_SET_VALUE:
                    int value = msg.arg1;
                    for (Messenger messenger : mMessengerList) {
                        try {
                            messenger.send(Message.obtain(null, MSG_CLIENT_SET_VALUE, value, 0));
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private Messenger mMessenger = new Messenger(new ServiceHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}
複製程式碼

二、Client端程式碼

public class MessengerClientActivity extends AppCompatActivity {
  //這些型別要和Server端想對應
    static final int MSG_REGISTER_CLIENT = 1;
    static final int MSG_UNREGISTER_CLIENT = 2;
    static final int MSG_SET_VALUE = 3;
    static final int MSG_CLIENT_SET_VALUE = 4;

    class ClientHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {

            if (msg.what == MSG_CLIENT_SET_VALUE) {
                mTvValue.setText(msg.arg1 + "");
            } else {
                super.handleMessage(msg);
            }
        }
    }

    TextView mTvServerBind;
    TextView mTvServerUnbind;
    TextView mTvValue;
    TextView mTvSend;

    ServiceConnection mServerServiceConnection;
    Messenger mServerMessenger;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_messenger);

        mTvServerBind = findViewById(R.id.tv_server_bind);
        mTvServerUnbind = findViewById(R.id.tv_server_unbind);
        mTvValue = findViewById(R.id.tv_value);
        mTvSend = findViewById(R.id.tv_send);

        mTvServerBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setAction("jxx.com.server.service.messenger");
                intent.setPackage("jxx.com.server");
                bindService(intent, mServerServiceConnection, BIND_AUTO_CREATE);
            }
        });

        mTvServerUnbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //就算這裡我們unbindService,只要我們還保留有mServerMessenger物件,
                //我們就能繼續與Server通訊
                unbindService(mServerServiceConnection);
            }
        });

        mTvSend.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mServerMessenger != null) {
                    try {
                        //測試一下能否設定資料
                        Message test = Message.obtain(null, MSG_SET_VALUE, new Random().nextInt(100), 0);
                        mServerMessenger.send(test);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        mServerServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //服務端的messenger
                mServerMessenger = new Messenger(service);

                //現在開始構client用來傳遞和接收訊息的messenger
                Messenger clientMessenger = new Messenger(new ClientHandler());

                try {
                    //將client註冊到server端
                    Message register = Message.obtain(null, MSG_REGISTER_CLIENT);
                    register.replyTo = clientMessenger;//這是註冊的操作,我們可以在上面的Server程式碼看到這個物件被取出
                    mServerMessenger.send(register);

                    Toast.makeText(MessengerClientActivity.this, "繫結成功", Toast.LENGTH_SHORT).show();

                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        };
    }
複製程式碼

相關文章