1. 程式人生 > >Android Service 遠端服務

Android Service 遠端服務

遠端服務的建立和呼叫需要使用AIDL語言,步驟如下:

  1. 使用AIDL語言定義遠端服務的介面

  2. 通過繼承Service類實現介面中定義的方法和屬性

  3. 繫結和使用遠端服務

以下為一個簡單Demo ,RemoteMathCallerDemo介面如下:
這裡寫圖片描述

繫結遠端服務後,呼叫RemoteMathServiceDemo中的MathService服務進行加法運算。

1.使用AIDL語言定義遠端服務的介面

以Android Studio為例,首先需要建立對應目錄及aidl檔案,如下:
這裡寫圖片描述

(比如直接在java目錄下的包上右鍵新建aidl檔案 IDE會自動生成aidl目錄及該目錄下的包和檔案這樣的小技巧我可不會隨便告訴別人)
IMathService.aidl檔案內容如下:

// IMathService.aidl
package com.example.remotemathservicedemo;

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

interface IMathService {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     long Add(long a,long b);
}

然後在build目錄下會自動生成與該aidl檔案對應的java介面檔案,(若沒有生成則重新make project) 如下:
這裡寫圖片描述

在看IMathService.java內容之前呢,不知道你有沒有注意到,我前兩張截圖都截到了上面的一個Module:remotemathcallerdemo,這個就是呼叫端,目前我們編輯的remotemathservicedemo是服務端。

下面為IMathService.java的完整程式碼,加上了我自己的理解和註釋:

/*
 * 這個檔案是自動生成的。不要修改
 */
package com.example.remotemathservicedemo;
/*   在這裡宣告任何非預設型別
     所有使用AIDL建立的介面都必須繼承 android.os.IInterface 基類介面
     這個基類介面中定義了 asBinder()方法 用來獲取Binder物件
 */
public interface IMathService extends android.os.IInterface { /** * 本地IPC實現stub類 */ public static abstract class Stub extends android.os.Binder implements com.example.remotemathservicedemo.IMathService { private static final java.lang.String DESCRIPTOR = "com.example.remotemathservicedemo.IMathService"; public Stub() { this.attachInterface(this, DESCRIPTOR); } //asInterface(IBinder) 是Stub內部的遠端服務介面,呼叫者可以通過該方法獲得遠端服務的例項 public static com.example.remotemathservicedemo.IMathService asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //判斷android.os.IInterface例項是否為本地服務 若是返回android.os.IInterface //若不是本地服務 構造Proxy物件並返回之 if (((iin != null) && (iin instanceof com.example.remotemathservicedemo.IMathService))) { return ((com.example.remotemathservicedemo.IMathService) iin); } return new com.example.remotemathservicedemo.IMathService.Stub.Proxy(obj); } //實現了android.os.IInterface介面定義的asBinder()方法 @Override public android.os.IBinder asBinder() { return this; } /* Parcel是Android系統應用程式間傳遞資料的容器,能夠在兩個程序中完成打包和拆包的工作 但Parcel不同於通用意義上的序列化 Parcel的設計目的是用於高效能IPC傳輸 不能將其儲存在持久儲存裝置上 */ //接收Parcel物件,並從中逐一讀取每個引數,然後呼叫Service內部制定的方法,將結果寫進另一個Parcel物件, // 準備將這個Parcel物件返回給遠端的呼叫者 @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_Add: { data.enforceInterface(DESCRIPTOR); long _arg0; _arg0 = data.readLong(); long _arg1; _arg1 = data.readLong(); long _result = this.Add(_arg0, _arg1); reply.writeNoException(); reply.writeLong(_result); return true; } } return super.onTransact(code, data, reply, flags); } //用來實現遠端服務呼叫 private static class Proxy implements com.example.remotemathservicedemo.IMathService { 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; } //以一定順序將所有引數寫入Parcel物件,以供Stub內部的onTransact()方法獲取引數 @Override public long Add(long a, long b) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); long _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeLong(a); _data.writeLong(b); mRemote.transact(Stub.TRANSACTION_Add, _data, _reply, 0); _reply.readException(); _result = _reply.readLong(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_Add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public long Add(long a, long b) throws android.os.RemoteException; }

IMathService.aidl是對遠端服務介面的定義,自動生成的IMathService.java內部實現了遠端服務資料傳遞的相關方法,下一步介紹如何實現遠端服務,這需要建立一個Service類,並在該類中通過onBind()方法返回IBinder物件,這樣呼叫者使用獲取的IBinder物件就可以訪問遠端服務。

下面是MathService.java的完整程式碼:

package com.example.remotemathservicedemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.widget.Toast;

/**
 * Created by yinghao on 2016/5/7.
 */
public class MathService extends Service {
    /*
           1. 建立 IMathService.Stub的例項mBinder並實現AIDL檔案定義的遠端服務介面
           2. 在onBind()方法中將mBinder返回給遠端呼叫者
     */

    private final IMathService.Stub mBinder = new IMathService.Stub(){
        @Override
        public long Add(long a, long b) throws RemoteException {
            return a + b;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(MathService.this, "遠端繫結:MathService", Toast.LENGTH_SHORT).show();
        return mBinder;
    }

    //Return true if you would like to have the service's onRebind method later called when new clients bind to it.
    @Override
    public boolean onUnbind(Intent intent) {
        Toast.makeText(MathService.this, "取消遠端繫結", Toast.LENGTH_SHORT).show();
        return false;
    }
}

最後一步,註冊service:

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service android:name=".MathService"
            android:process=":remote">
            <intent-filter>
                <action android:name="com.example.remote.MathService" />
            </intent-filter>
        </service>
    </application>

</manifest>

到這裡我們的服務端Module : remotemathservicedemo 就完成了。

這完成了文章開頭所列舉的三個步驟的前兩步,最後一步,讓我們來看看怎麼繫結和使用遠端服務吧。

首先,我們需要引入與服務端相同的aidl檔案並確保自動生成對應的IMathService.java介面檔案

那麼為什麼要這樣做呢? 這就需要我們瞭解aidl檔案和對應介面檔案的用處到底是什麼,為了時資料能穿越程序邊界,所有資料都必須是“打包”,而自動生成的IMathService.java內部實現了遠端服務資料傳遞的相關方法,那麼服務端就有了將資料打包、拆包的能力。而呼叫端也需要發出資料和接收資料,也需要有將資料打包、拆包的能力,所以它也需要IMathService.java這個類。

然後對遠端服務的繫結與呼叫,其實與本地服務的繫結區別不大,不同之處主要包括兩處:

  1. 使用IMathService生命遠端服務例項

  2. 通過IMathService.Stub的asInterface()方法獲取服務例項

下面為remotemathcallerdemo Module中MainActivity.java的完整程式碼:

package com.example.remotemathcallerdemo;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import com.example.remotemathservicedemo.IMathService;

import org.w3c.dom.Text;

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Button bind;
    private Button unbind;
    private Button add;

    private boolean isBound = false;
    private IMathService mathService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mathService = IMathService.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mathService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.textView);
        bind = (Button) findViewById(R.id.bind);
        unbind = (Button) findViewById(R.id.unbind);
        add = (Button) findViewById(R.id.add);

        bind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isBound) {
                    final Intent serviceIntent = new Intent();
                    serviceIntent.setAction("com.example.remote.MathService");
                    bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
                    isBound = true;
                }
            }
        });

        unbind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isBound) {
                    unbindService(mConnection);
                    isBound = false;
                    mathService = null;
                }
            }
        });

        add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mathService == null) {
                    textView.setText("未繫結遠端服務");
                    return;
                }
                long a = Math.round(Math.random() * 100);
                long b = Math.round(Math.random() * 100);
                long result = 0;
                try {
                    result = mathService.Add(a, b);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                String msg = String.valueOf(a) + "+" + String.valueOf(b) + "=" + String.valueOf(result);
                textView.setText(msg);
            }
        });
    }
}

在此例中傳遞的資料型別為基本資料型別,打包過程是自動完成的,但對於自定義的資料型別,使用者則需要實現Parcelable介面,使自定義的資料型別能夠轉換為系統級原語儲存在Parcel物件中,穿越程序邊界後可再轉換為初始格式,關於自定義資料型別的傳遞,在下一篇文章中歸納總結。