Android學習之遠程服務的使用與開發

先前學習安卓偏向於界面的繪製,對於其他方面的知識比較薄弱,在逐步查缺補漏(就是填大坑,自己真的太菜了。)本學期開設了Android課程,其中一章爲遠程服務,自覺收穫頗多,特寫此文。本文爲課程實驗的學習與整理,若有錯誤請煩請指正。

一、知識點

(知識點有空再補全)

二、問題描述

假設一個APP“支付寶寶”提供支付的服務(Service),另一個APP“迪迪打車”可以調用該服務。要求
(1) 爲“支付寶寶”設計遠程服務接口IPayService,該接口包含:

  • (a)方法pay,用於模擬支付。id_payer是付款人“支付寶寶”的賬號。id_payee 是收款人的賬號。amount_to_pay是要付款的數額。requester_app是發起支付的APP名稱,本題指“迪迪打車”。
    “支付寶寶”餘額以及支付歷史等信息,要求用SharedPreferences保存。
    boolean pay (int id_payer, int id_payee, float amount_to_pay, String requester_app)
  • (b)方法getPayHistory,用於返回某APP發起的支付記錄。PayHistory是自定義的數據類型,用來存放支付歷史。
    PayHistory getPayHistory(int id_payer, String requester_app)

(2) 爲“迪迪打車”設計支付頁面,調用“支付寶寶”IPayService接口中的pay方法進行支付,並將返回的支付結果顯示。

(3) 爲“迪迪打車”設計“查詢支付歷史”的功能,調用“支付寶寶”IPayService接口中的getPayHistory方法,得到“迪迪打車”的支付歷史,並顯示在界面上。

三、支付寶寶(ALiBaby)

(一) 需求分析

  1. 登錄,因本次實驗重點在於遠程服務以及數據的簡單存儲與訪問,故登錄過程不做驗證,僅作爲餘額的唯一id標識。
  2. 餘額的顯示與修改,使用TextView顯示賬號和餘額,並提供修改餘額的按鈕。
  3. 遠程服務接口IPayService,提供兩個功能:
    A. 支付功能
    B. 歷史查詢功能
    (根據題目所提供的函數格式,同一id僅保存一個最新的歷史記錄)

(二)界面繪製

  1. 登錄
    ALiBaby登錄
  2. 信息顯示
    ALiBaby顯示

(三)關鍵代碼

  1. 登錄(LoginActivity.java)
//獲取用戶輸入的賬號
String account = accountET.getText().toString();
//將賬號存入Intent,傳入MainActivity中,以獲取相應賬號的餘額信息
Intent intent = new Intent(LoginActivity.this,MainActivity.class);
intent.putExtra("ACCOUNT",account);
startActivity(intent);
finish();
  1. 信息顯示與修改(MainActivity.java)
//信息顯示
float money = MoneyData.loadBalance(MainActivity.this,account);
mTextView.setText(account+",餘額:"+money+" 元");
//信息修改
//獲取想要修改成的金額
String money = mEditText.getText().toString();
//判斷是否輸入了金額,若輸入則保存本地SharedPreferences,否則提示用戶輸入相關信息
if (money.equals("")){
	Toast.makeText(MainActivity.this,"請輸入修改金額!",Toast.LENGTH_SHORT).show();
    return;
}else {
	MoneyData.saveBalance(MainActivity.this,Float.parseFloat(money),account);
    float m = MoneyData.loadBalance(MainActivity.this,account);
    mTextView.setText(account+",餘額:"+m+" 元");
}
  1. 歷史信息類(PayHistory.java)
/**
 * 支付歷史信息類
 * 對於自定義的數據類型,用戶需要實現Parcelable接口
 */
public class PayHistory implements Parcelable {
    private int id_payer;//付款人id
    private int id_payee;//收款人id
    private String requester_app;//收款方APP
    private float amount_to_pay;//金額
    private int is_success; //0 失敗 1 成功

    //滿參構造函數
    public PayHistory(int id_payer, int id_payee, String requester_app, float amount_to_pay,int is_success) {
        this.id_payer = id_payer;
        this.id_payee = id_payee;
        this.requester_app = requester_app;
        this.amount_to_pay = amount_to_pay;
        this.is_success = is_success;
    }

    //以Parcel對象爲輸入的構造函數
    public PayHistory(Parcel parcel) {
        this.id_payer = parcel.readInt();
        this.id_payee = parcel.readInt();
        this.requester_app = parcel.readString();
        this.amount_to_pay = parcel.readFloat();
        this.is_success = parcel.readInt();
    }

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

    //重載打包函數,將本類內部的數據,按照特定的順序寫入Parcel對象
    //寫入的順序必須與構造函數的讀取順序一致
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.id_payer);
        dest.writeInt(this.id_payee);
        dest.writeString(this.requester_app);
        dest.writeFloat(this.amount_to_pay);
        dest.writeInt(this.is_success);
    }

    //實現靜態公共字段CREATOR,用來使用Parcel對象構造PayHistory對象
    public static final Creator<PayHistory> CREATOR = new Creator<PayHistory>(){

        @Override
        public PayHistory createFromParcel(Parcel parcel){
            return new PayHistory(parcel);
        }

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

    //get&&set函數
	...
}
  1. 信息的讀取與保存(MoneyData.java)
public class MoneyData {

	//基礎名+id名(+app名)以此確定唯一id對應的唯一SharedPreferences
    public static final String PREFERENCE_BALANCE="Balance";
    public static final String PREFERENCE_PAYHISTORY="PayHistory";
    public static int MODE = Context.MODE_PRIVATE;

    //加載餘額
    public static float loadBalance(Context context,String account){
        String name = PREFERENCE_BALANCE + account;
        SharedPreferences sharedPreferences = context.getSharedPreferences(name,MODE);
        float money = sharedPreferences.getFloat("Money",0);
        return money;
    }

    //保存餘額
    public static void saveBalance(Context context,float money,String account){
        String name = PREFERENCE_BALANCE + account;
        SharedPreferences sharedPreferences = context.getSharedPreferences(name,MODE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putFloat("Money",money);
        editor.commit();
    }

    //讀取支付歷史信息
    public static PayHistory loadPayHistory(Context context,int payer,String app){
        String name = PREFERENCE_PAYHISTORY+payer+app;
        SharedPreferences sharedPreferences = context.getSharedPreferences(name,MODE);
        int id_payer = sharedPreferences.getInt("ID_PAYER",0);
        int id_payee = sharedPreferences.getInt("ID_PAYEE",0);
        String requester_app = sharedPreferences.getString("REQUESTER_APP","DiDiTaxi");
        float amount_to_pay = sharedPreferences.getFloat("AMOUNT_TO_PAY",0);
        int is_success = sharedPreferences.getInt("IS_SUCCESS",0);
        PayHistory payHistory = new PayHistory(id_payer,id_payee,requester_app,amount_to_pay,is_success);
        return payHistory;
    }

    //保存支付歷史
    public static void savePayHistory(Context context,PayHistory payHistory,int payer,String app){
        String name = PREFERENCE_PAYHISTORY+payer+app;
        SharedPreferences sharedPreferences = context.getSharedPreferences(name,MODE);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        int id_payer = payHistory.getId_payer();
        editor.putInt("ID_PAYER",id_payer);
        int id_payee = payHistory.getId_payee();
        editor.putInt("ID_PAYEE",id_payee);
        String requester_app = payHistory.getRequester_app();
        editor.putString("REQUESTER_APP",requester_app);
        float amount_to_pay = payHistory.getAmount_to_pay();
        editor.putFloat("AMOUNT_TO_PAY",amount_to_pay);
        int is_success = payHistory.getIs_success();
        editor.putInt("IS_SUCCESS",is_success);
        editor.commit();
    }
}
  1. 遠程服務函數(PayService.java)
//通過使用系統生成的IPayService.java內部的Stub類實現IBinder對象的建立
//逐一實現在IPayService.aidl接口文件定義的函數
private final IPayService.Stub mBinder = new IPayService.Stub() {
	//支付函數
    @Override
    public boolean pay(int id_payer, int id_payee, float amount_to_pay, String requester_app) throws RemoteException{
    	//獲取當前賬戶的餘額
    	float balance = MoneyData.loadBalance(PayService.this,id_payer+"");
    	//根據參數構造支付歷史
    	PayHistory payHistory = new PayHistory(id_payer,id_payee,requester_app,amount_to_pay,1);
    	//若餘額大於等於所要求支付的金額,則扣款,將is_success置1並保存新餘額和支付歷史,返回true表示支付成功
    	//若小於,則將is_success置0,並保存支付歷史,返回false表示支付失敗,餘額不變
    	if (balance >= amount_to_pay){
    		float newBalance = balance - amount_to_pay;
        	payHistory.setIs_success(1);
        	MoneyData.saveBalance(PayService.this,newBalance,id_payer+"");
			MoneyData.savePayHistory(PayService.this,payHistory,id_payer,requester_app);
        	return true;
    	}else {
        	payHistory.setIs_success(0);
        	MoneyData.savePayHistory(PayService.this,payHistory,id_payer,requester_app);
        	return false;
    	}
	}

    //讀取支付歷史函數
    @Override
    public PayHistory getPayHistory(int id_payer, String requester_app) throws RemoteException{
    	PayHistory payHistory = MoneyData.loadPayHistory(PayService.this,id_payer,requester_app);
        return payHistory;
    }
};

四、迪迪打車(DiDiTaxi)

(一)需求分析

  1. 提供“查詢支付歷史”和“支付租車費用”兩個功能的按鈕
  2. 在查詢支付歷史功能中可以根據輸入的付款方id查詢並顯示它最近一次的支付歷史記錄(包括付款方id,收款方id,收款方APP名稱,付款金額以及付款狀態)
  3. 在支付租車費用中可以根據輸入的付款方id,收款方id以及付款金額調用ALiBaby提供的遠程服務接口完成支付功能,並用Toast顯示支付是否成功。

(二)界面繪製

  1. 主界面
    主界面
  2. 查詢支付歷史
    查詢支付歷史
  3. 支付租車費用
    支付租車費用

(三)關鍵代碼

  1. 綁定服務(PayActivity.java & QueryActivity.java)
private IPayService mPayService;
private boolean isBind = false;

//服務鏈接
private ServiceConnection mServiceConnection = new ServiceConnection() {
	@Override
    public void onServiceConnected(ComponentName name, IBinder service) {
		mPayService = IPayService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mPayService = null;
    }
};
//服務綁定
Intent serviceIntent = new Intent("com.hzf.nicholas.alibaby.PayService");
Intent serviceIntent2 = createExplicitFromImplicitIntent(QueryActivity.this,serviceIntent);
bindService(serviceIntent2,mServiceConnection, Context.BIND_AUTO_CREATE);
isBind = true;
//將隱式啓動轉化爲顯示啓動
public Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
	// Retrieve all services that can match the given intent
    PackageManager pm = context.getPackageManager();
    List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);
    // Make sure only one match was found
    if (resolveInfo == null || resolveInfo.size() != 1) {
    	return null;
    }
    // Get component info and create ComponentName
    ResolveInfo serviceInfo = resolveInfo.get(0);
    String packageName = serviceInfo.serviceInfo.packageName;
    String className = serviceInfo.serviceInfo.name;
    ComponentName component = new ComponentName(packageName, className);
    // Create a new intent. Use the old one for extras and such reuse
    Intent explicitIntent = new Intent(implicitIntent);
    // Set the component to be explicit
    explicitIntent.setComponent(component);
    return explicitIntent;
}
  1. 支付租車費用
int payer = Integer.parseInt(payerET.getText().toString());
int payee = Integer.parseInt(payeeET.getText().toString());
float money = Float.parseFloat(amountET.getText().toString());
if (isBind){
	try {
		boolean flag = mPayService.pay(payer,payee,money,getString(R.string.app_name));
        if (flag){
			Toast.makeText(PayActivity.this,getString(R.string.s_activity_pay_success),Toast.LENGTH_SHORT).show();
        }else {
			Toast.makeText(PayActivity.this,getString(R.string.s_activity_pay_failure),Toast.LENGTH_SHORT).show();
        }
	} catch (RemoteException e) {
		e.printStackTrace();
    }
}else {
	Toast.makeText(PayActivity.this,getString(R.string.s_activity_start_service_first),Toast.LENGTH_SHORT).show();
}
  1. 查詢支付歷史
int payer = Integer.parseInt(mEditText.getText().toString());
if (isBind){
	try {
		PayHistory payHistory = mPayService.getPayHistory(payer,getString(R.string.app_name));
        int id_payer = payHistory.getId_payer();
        int id_payee = payHistory.getId_payee();
        String name = payHistory.getRequester_app();
        float money = payHistory.getAmount_to_pay();
        int is_success = payHistory.getIs_success();
        String data = getString(R.string.s_activity_pay_payer)+id_payer+'\n';
        data += getString(R.string.s_activity_pay_payee)+id_payee+'\n';
        data += getString(R.string.s_activity_query_get_app_name)+name+'\n';
        data += getString(R.string.s_activity_pay_amount_to_pay)+money+'\n';
        if (is_success == 0){
        	data += getString(R.string.s_activity_query_pay_state)+getString(R.string.s_activity_pay_failure);
        }else {
            data += getString(R.string.s_activity_query_pay_state)+getString(R.string.s_activity_pay_success);
        }
        mTextView.setText(data);
        mTextView.setVisibility(View.VISIBLE);
    } catch (RemoteException e) {
    	e.printStackTrace();
    }
}else {
	Toast.makeText(QueryActivity.this,getString(R.string.s_activity_start_service_first),Toast.LENGTH_SHORT).show();
}

五、遇到的BUG

(一)java.lang.SecurityException: MODE_WORLD_READABLE no longer supported

  • SharedPreferences的三種訪問模式中的MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE在高版本的Android中因爲安全性無法保證已經被廢棄,需要使用MODE_PRIVATE。

(二)Android AIDL 編譯錯誤,finished with non-zero exit value 1

  • 包名寫錯了。
  • 使用的自定義數據類型還沒實現Parcelable接口。

(三)ServiceIntent must be explicit

在Android5.0之後,爲了確保應用的安全性,啓動 Service 時,需要使用顯式 Intent。解決方案是:

  • A.intent.setAction(service的action名稱);intent.setPackage(service包名);
  • B.使用網上的方法,將隱式啓動的intent轉化爲顯式啓動的intent。

(四)app:compileDebugAidl : aidl is missing

  • 在創建好aidl後修改了包名,導致包名不一致。需要保持aidl的包名與對應Java文件本身一致。

(五)遠程綁定服務後,無法調用相關函數,報空指針錯誤。

  • 在綁定完服務之後不能立即調用服務中的方法,否則會出現空指針錯誤。

解決BUG過程中看到的一個博客,幫助我解決了一些問題。

關於安卓不能遠程綁定服務的問題

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