先前學習安卓偏向於界面的繪製,對於其他方面的知識比較薄弱,在逐步查缺補漏(就是填大坑,自己真的太菜了。)本學期開設了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)
(一) 需求分析
- 登錄,因本次實驗重點在於遠程服務以及數據的簡單存儲與訪問,故登錄過程不做驗證,僅作爲餘額的唯一id標識。
- 餘額的顯示與修改,使用TextView顯示賬號和餘額,並提供修改餘額的按鈕。
- 遠程服務接口IPayService,提供兩個功能:
A. 支付功能
B. 歷史查詢功能
(根據題目所提供的函數格式,同一id僅保存一個最新的歷史記錄)
(二)界面繪製
- 登錄
- 信息顯示
(三)關鍵代碼
- 登錄(LoginActivity.java)
//獲取用戶輸入的賬號
String account = accountET.getText().toString();
//將賬號存入Intent,傳入MainActivity中,以獲取相應賬號的餘額信息
Intent intent = new Intent(LoginActivity.this,MainActivity.class);
intent.putExtra("ACCOUNT",account);
startActivity(intent);
finish();
- 信息顯示與修改(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+" 元");
}
- 歷史信息類(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函數
...
}
- 信息的讀取與保存(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();
}
}
- 遠程服務函數(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)
(一)需求分析
- 提供“查詢支付歷史”和“支付租車費用”兩個功能的按鈕
- 在查詢支付歷史功能中可以根據輸入的付款方id查詢並顯示它最近一次的支付歷史記錄(包括付款方id,收款方id,收款方APP名稱,付款金額以及付款狀態)
- 在支付租車費用中可以根據輸入的付款方id,收款方id以及付款金額調用ALiBaby提供的遠程服務接口完成支付功能,並用Toast顯示支付是否成功。
(二)界面繪製
- 主界面
- 查詢支付歷史
- 支付租車費用
(三)關鍵代碼
- 綁定服務(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;
}
- 支付租車費用
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();
}
- 查詢支付歷史
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過程中看到的一個博客,幫助我解決了一些問題。
關於安卓不能遠程綁定服務的問題