1. 服务的概念
2. 为什么使用服务
3. 服务的使用
4. Android跨进程通信AIDL
5. AIDL模拟支付宝支付
服务是一个可以长期在后台运行没有用户界面的应用组件。
服务可以由其他应用组件启动,比如activity,服务一
经启动,即使activity已销毁服务仍可正常运行于后台。
服务并没有漂亮的界面,但是某些操作需要去做,比如
耗时操作请求数据等异步工作,我们可以使用服务,放
置后台,增强用户体验。
服务属于四大组件之一,使用流程同其他组件如BroadcastReceiver很相似,
同样需要编写类继承组件相应的类或者实现接口,然后在Manifest声明注册
,通过在Activity中借助Intent进行开启相应的组件。
android:exported="true"
说明该Service可以被外部所调用。public class FirstService extends Service {public static final String TAG = "FirstService";@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();Log.d(TAG, "Service onCreate...");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d(TAG, "Service onStartCommand...");return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() {super.onDestroy();Log.d(TAG, "Service onDestroy...");}
}
startService(Intent intent);
stopService(Intent intent);
bindService(Intent intent, ServiceConnection conn, int flags)
flags一般为BIND_AUTO_CREATE,conn一般为匿名匿名内部类。unbindService(ServiceConnection conn)
;start和stop开启和关闭服务,服务可以一直在后台(对于android9.0版本,如果后台服务一直没有使用,那么它会自动调用destory方法使其service销毁,android.X
之后具体哪个版本就不知道了),但是这种方式组件与组件之间不能正常进行的通信,复用上面的FirstService,添加一个方法,加Toast提示框,才能看见为什么不能通信,单独的一个函数调用,函数体为空是可以正常执行的,但当 给上下文的时候,会报异常
public void innerMethod() {Log.d(TAG, "innerMethod...");Toast.makeText(this, "say hello!", Toast.LENGTH_SHORT).show();
}
在Activity中开启服务并调用服务内部方法
FirstService firstService = new FirstService();
firstService.innerMethod();
开启服务后调用方法抛出空指针异常原因就是Toast中this上下文并没有获取到,因为Service是间接继承自Context,服务相当于一个后台进程,服务进程对
像的分配是由系统分配的,而不是人为控制。
Caused by: java.lang.NullPointerException: Attempt to invoke virtual
method 'java.lang.String android.content.Context.getPackageName()' o
n a null object reference
绑定和解绑服务
以bind方式开启的service,组件与组件之间可以通过extends Binder生成一个IBinder对象,activity和service组件之间就可以通过ServiceConnection建立连接进行通信,原理相当于C/S架构,Activity相当于客户端,Service相当于服务器。bindService(…)开启的服务,服务进程并不会一直运行于后台,当Activity销毁时要释放服务资源即unbind否则会导致泄露问题,也就是说service的与activity不求同生但求同死。
Android沙箱技术:
Android“沙箱”的本质是为了实现不同应用程序和进程之间的互相隔离,即在默认情况 下,应用程序没有权限访问系统资源或其它应用程序的资源。
每个APP和系统进程都被分配唯一并且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。
每个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。
运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,因此Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序如果想要访问系统资源或者其它应用程序的资源必须在自己的manifest文件中进行声明权限或者共享uid。
通常,暴露接口方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。
public void onServiceConnected(ComponentName name, IBinder service)
方法中的IBinder对象就是Server通过调用onBind方法返回的一个间接继承Binder类的对象。私有内部类ThirdPartPayImpl
继承ThirdPartPayAction.Stub
.Stub类实现了AIDL接口并且继承了Binder类(AIDL通信的本质)。PayAction支付动作类,因为app绑定第三方支付后,当调用requestPay时,service会拉起一个支付的PayActivity
,这个Activity也需要与该支付服务做绑定因为支付操作都是在该界面进行的,与服务通信的IBinder对象就是return new PayAction()所给。public class PayService extends Service {public static final String TAG = "PayService";private ThirdPartPayImpl mThirdPartPay;@Overridepublic IBinder onBind(Intent intent) {String action = intent.getAction();Log.d(TAG, "onBind -- > action:" +action);if (action != null && "cn.wjx.alipay.THIRD_PART_PAY".equals(action)) {// 第三方应用调起支付服务mThirdPartPay = new ThirdPartPayImpl();return mThirdPartPay;}return new PayAction();}public class PayAction extends Binder {public void pay(float money) {Log.d(TAG, "pay--->" + money);if (mThirdPartPay != null) {try {mThirdPartPay.callBack.paySuccess();} catch (RemoteException e) {e.printStackTrace();}}}public void onUserCancel() {Log.d(TAG, "onUserCancel");if (mThirdPartPay != null) {try {mThirdPartPay.callBack.payFailed(0, "用户取消");} catch (RemoteException e) {e.printStackTrace();}}}}private class ThirdPartPayImpl extends ThirdPartPayAction.Stub {private ThirdPartPayResult callBack;@Overridepublic void requestPay(String orderInfo, float money, ThirdPartPayResult result) throws RemoteException {this.callBack = result;// 第三方应用发起请求打开一个支付界面Intent intent = new Intent(PayService.this, PayActivity.class);intent.putExtra(Constants.PAY_ORDER_INFO, orderInfo);intent.putExtra(Constants.PAY_MONEY, money);// 服务通过intent调用activity,分开任务栈intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}}
}
package cn.wjx.alipay;
PayActivity支付交互界面
import cn.wjx.alipay.ThirdPartPayResult;
interface ThirdPartPayAction {
void requestPay(String orderInfo,float money,ThirdPartPayResult result);
}package cn.wjx.alipay;
interface ThirdPartPayResult {void paySuccess();void payFailed(in int errorCode, in String errMsg);
}
public class PayActivity extends Activity {
public static final String TAG = "PayActivity";
private Button mPayCommit;
private TextView mPayOrderInfo;
private TextView mPayMoney;
private EditText mPayPassword;
private PayService.PayAction mPayAction;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_pay);initView();doPay();
}
private void doPay() {mPayCommit.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (mPayAction != null) {String password = mPayPassword.getText().toString();if ("123".equals(password)) {mPayAction.pay(100f);Toast.makeText(PayActivity.this, "支付成功 ", Toast.LENGTH_SHORT).show();Log.d(TAG, "pay finish");finish();} else {Toast.makeText(PayActivity.this, "密码错误", Toast.LENGTH_SHORT).show();}}}});}
@Override
public void onBackPressed() {super.onBackPressed();mPayAction.onUserCancel();Toast.makeText(PayActivity.this, "取消支付", Toast.LENGTH_LONG).show();
}
/*** 初始化组件展示订单信息信息*/
private void initView() {Intent intent = getIntent();String payOrderInfo = intent.getStringExtra(Constants.PAY_ORDER_INFO);float payMoney = intent.getFloatExtra(Constants.PAY_MONEY, 0f);mPayCommit = findViewById(R.id.pay_commit);mPayOrderInfo = findViewById(R.id.pay_order_info);mPayOrderInfo.setText("账单:" + payOrderInfo);mPayMoney = findViewById(R.id.pay_money);mPayMoney.setText("支付金额:" + payMoney + "元");mPayPassword = findViewById(R.id.pay_password);
}
@Override
protected void onStart() {super.onStart();Intent intent = new Intent(this, PayService.class);bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {if (service != null) {mPayAction = (PayService.PayAction) service;}}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};/*** 解绑服务释放资源*/
@Override
protected void onDestroy() {super.onDestroy();if (mServiceConnection != null) {unbindService(mServiceConnection);mServiceConnection = null;}
}
}
ThirdPartPay.Stub.asInterface
得到第三方服务对象,继承ThirdPartPayResult.Stub
重写回调接口方法,然后绑定服务的将相应对象传入即可。public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
private ThirdPartPayAction mPayAction;
private TextView mUserAccount;@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();
}private void initView() {mUserAccount = findViewById(R.id.user_account);
}@Override
protected void onStart() {super.onStart();Intent intent = new Intent();// android5.0以上必须显示启动服务intent.setAction("cn.wjx.alipay.THIRD_PART_PAY");intent.setPackage("cn.wjx.alipay");bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}public void chargeBitCoin(View view) {try {mPayAction.requestPay("充值BitCoin", 9999F, new PayResult());} catch (RemoteException e) {e.printStackTrace();}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d(TAG, "onServiceConnected...");// 使用.Stub.asInterface转化对象mPayAction = ThirdPartPayAction.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};private class PayResult extends ThirdPartPayResult.Stub {@Overridepublic void paySuccess() throws RemoteException {// 支付成功 渲染界面 实际开发中是要请求服务器mUserAccount.setText("100BitCoin");}@Overridepublic void payFailed(int errorCode, String errMsg) throws RemoteException {}
}
@Override
protected void onDestroy() {super.onDestroy();if (mServiceConnection != null) {unbindService(mServiceConnection);mServiceConnection = null;}
}
}
2020-03-30 14:14:54.928 8626-8642/cn.wjx.payclient E/JavaBinder:
子线程更新UI的问题,我将toast放在Activit中更新UI,而不是在client调用之后,在client app下中AIDL回调接口中更新UI。
*** Uncaught remote exception! (Exceptions are not yet supported across processes.)
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()