为什么要使用服务呢?
从上面的文字说,我们知道这个服务是用于执行长期后台运行的操作。有些时候,我们没有界面,但是程序仍然需要工作。比如说,我们播放音乐,在后台播放音乐。比如说,我们下载任务,在后台下载文件。这些都是没有界面 的后台运行程序,这些都是用服务做的。
第二个原因是什么呢?先给大家讲几个概念:0
1、前台进程:可以理解为是最顶部的,直接跟用户交互的。比如说我们操作的Activity界面.
2、可见进程:可以见的,但是不操作的,比如说我们在一个Activity的顶部弹出一个Dialog,这个Dialog就是前台进程,但是这个Activity则是可见进程。(操作的只是Dialog,而不是Activity但可见)
3、服务进程:服务可以理解为是忙碌的后台进程,虽然是在后台,但是它很忙碌。
4、后台进程:后台进程就是退隐到后台,不做事的进程。(比如按home键程序在后台但没有被干掉/)
5、空进程:空进程是不做事的,没有任何东西在上面跑着,仅作缓存作用。(比如按返回键退出此时就是空进程,销毁掉的进程就是空进程)
假设,内存不够用了,会先杀谁呢?
首先杀的是空进程,要是还不够就杀后台进程,要是还不够,那么就杀服务,但是服务被杀死以后,等内存够用了,服务又会跑起来了。
所以:如果我们需要长期后台操作的任务,使用Service就对了!其实Framework里多数是服务。如果我们进行音乐播放,即使退到了后台,也可以播放,我们使用服务完成吧!如果我们下载东西,退到后台也能下载,那么我们就使用服务吧!如果我们在不停地记录日志,那我们就用服务吧!
服务是Android中实现程序后台运行的方案,他非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
每一个服务都需要在配置文件AndroidManifest.xml文件里进行生命,怎么生命呢?
使用标签,其实跟前面的activity,广播接收者receiver一样生命。
通过Context.startService()来开启服务,通过Context.stop()来停止服务。当然啦,还有一种启动形式就是通过Context.bindService()的方法。
如果面试问到:服务用于执行耗时操作,这是对的吗?
如时服务直接执行耗时操作,也会出现anr。首先ANR的意思是android no response,也就是无响应或者理解为操作超时。
如果在服务中直接做耗时操作,也是会出现ANR异常的。服务可以长期在后台运行,所以你可以这么做:如果要做耗时操作,比如说网络的访问,数据库的读写之类的,可以开线程去做。
首先,创建一个类,继承Service,就像我们之前写Activity要继承自Activity,而广播则继承自BroadcastReceiver。
package com.example.servicetest;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable
public class MyService extends Service {
//onCreate()方法再服务创建的时候调用
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
}
//onStartCommand()方法在每次服务启动的时候调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand executed");
return super.onStartCommand(intent, flags, startId);
}
//onDestroy()方法在服务销毁的时候调用
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyService", "onDestroy executed");
}
}
接着,我们写一个Activity去控制服务:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService=(Button) findViewById(R.id.start_service);
Button stopService=(Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.start_service:
Intent startIntent=new Intent(this,MyService.class);
startService(startIntent);//启动服务
break;
case R.id.stop_service:
Intent stopIntent=new Intent(this,MyService.class);
stopService(stopIntent);//停止服务
break;
}
onCreate()方法是在服务第一次创建的时候调用的,而onStartCommand()方法则在每次启动服务的时候都会调用,由于是我们第一次点击Start Service按钮,服务此时还未创建过,所以两个方法都会执行,之后如果你再连续多点击几次Start Service按钮,你会发现只有onStartCommand()方法可以得到执行。
前面的开启服务方式,有一个弊端。就是没法进行通讯。所以我们接直来呢会学习另外一种启动服务的方式–通过绑定服务的形式来启动服务。
绑定服务,对应的停止服务则是解绑服务了!
活动和服务进行通信(绑定服务)
例子:通过创建一个专门的Binder对象来对下载功能进行管理
public class MyService extends Service {
private DownloadBinder mBinder=new DownloadBinder();
class DownloadBinder extends Binder
{
public void startDownload()
{
Log.d("MyService", "startDownload: executed");
}
public int getProgress()
{
Log.d("MyService", "getProgress: executed");
return 0;
}
}
public MyService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection=new ServiceConnection() {
//分别是绑定成功后和不成功后调用的方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//多态 IBinder a=new MyService.DownloadBinder();
//多态的类型判断service对象是MyService.DownloadBinder类型的实例
if(service instanceof MyService.DownloadBinder) {
//这里的downloadBinder就是MyService返回的mBinder
//向下转型得到DownloadBinder的实例,可以调用DownloadBinder的任何方法
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bindService=(Button) findViewById(R.id.bind_service);
Button unbindService=(Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.bind_service:
//点击后回调onBind()方法
Intent bindIntent=new Intent(this,MyService.class);
//BIND_AUTO_CREATE表示绑定服务的时候就会没有创建服务就会去创建服务,如果已经创建就不会再创建
//绑定后就返回Binder给onServiceConnected
bindService(bindIntent,connection,BIND_AUTO_CREATE);//绑定服务
break;
case R.id.unbind_service:
unbindService(connection);//解绑服务
break;
}
首先创建一个ServiceConnection的匿名类,在里面重写了onServiceConnected和onServiceDisconnected方法,这两个方法分别会在绑定成功后以及活动与服务的连接断开的时候调用。我们可以根据活动的具体场景来调用DownloadBinder中的任何public方法,从而实现指挥服务干什么服务就去干什么的功能。做了个简单的测试,在onServiceConnected()方法中调用了用DownloadBinder中startDownload和getProgress方法。
绑定服务在Bind Service按钮的点击事件里完成,构建一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数是刚构建的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
当需要解除活动和服务之间的绑定需要调用一下unbindService()方法。
首先是MyService的onCreate()方法得到了执行,然后startDownload()和getProgress()方法都得到了执行,说明我们确实已经在活动里成功调用了服务里提供的方法。
任何一个服务在整个应用程序的范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以任何一个其他活动进行绑定,而且绑定完成后他们可以获取相同的DownloadBinder实例。
这样子我们就可以控制服务了,假设说我们有一个服务在后台跑着,用它来播放音乐的,因为我们音乐可以后台播放呀,对吧!这个时间 ,我们需要控制音乐的播放和暂停了,就可以通过这种形式去控制音乐了
总结一下绑定服务的特点:
1、绑定服务在系统设置里是没有显进服务正在跑着的;
2、如果onBind方法返回的是null,那么onServiceConnected方法不会被调用;
3、绑定服务的生命周期跟Activity是不求同时生,但求同时死,Activity没了,服务也要解绑;
4、服务在解除绑定以后会停止运行,执行unBind方法—>onDestroy方法;
5、绑定服务开启的服务,只可以解绑一次,多次解绑会抛异常;
6、绑定的connection要跟解绑的connection要对应着,否则没法解绑。
绑定服务和开启服务的区别
绑定服务:可以间接调用服务里面的方法;如果绑定的Activity被销毁了,服务也会跟着销毁。当Activity销毁时要释放服务资源即unbind否则会导致泄露问题,也就是说service的与activity不求同生但求同死。
开启服务:不可以调用服务里面的方法;如果开启服务的Activity销毁,服务还可以长期的在后台运行。
既要保证服务长期在后台运行,又想去调用服务里面的方法:混合使用服务
混合服务
start->bind->unbind查看效果
unbindService之后service并没有destroy,所以说以start开启服务之后,只有stop才可以正常的销毁服务。
start->bind->stop bind之后未unbind,stop是不会起作用的,也就是说当bind之后必须解绑才可以正常的释放资源。
推荐的混合开发服务模式:
- 开启服务->为了确保服务可以长期于后台运行
- 绑定服务->为了可以进行通讯
- 调用服务内部的方法,该干嘛就干嘛,比如说,我们控制音乐的播放/暂停 /停止/快进
- 退出Activity,要记得解绑服务->释放资源
- 如果不使用服务了,要让服务停止,那么就调用stopService
服务生命周期
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand方法。如果这个服务之前未创建过,onCreate()方法会先于onStartCommand方法执行 。服务启动后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。虽然每次调用一次startService()方法,onStartCommand方法就会执行一次,但实际上每个服务都只会存在一个实例。不管你调用多少次startService(),只需调用一次stopService()或stopSelf()方法,服务就会停止下来。
还可以调用Context的bindService()获取一个服务的持久连接,这时就会回调服务的onBind()方法。类似的如果这个服务之前未创建过,onCreate()方法会先于onBind方法执行 。之后调用方就可以获取到onBind()方法返回的IBinder对象的实例,这样就能自由地和服务进行通信(比如绑定成功后,活动调用服务的相关方法)
当调用了startService()方法后,又去调用stopService()方法,这是服务中的onDestory()方法就会执行,表示服务已经销毁。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestory()方法也会执行。但是注意当我调用startService()又调用bindService(),这个时候需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
前台服务
发送通知
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate executed");
String ID="com.example.servicetest";
String NAME="Channel One";
Intent intent=new Intent(MyService.this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
NotificationCompat.Builder notification;
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
{
NotificationChannel channel=new NotificationChannel(ID,NAME,manager.IMPORTANCE_HIGH);
channel.enableLights(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
manager.createNotificationChannel(channel);
notification=new NotificationCompat.Builder(MyService.this).setChannelId(ID);
}else {
notification=new NotificationCompat.Builder(MyService.this);
}
notification.setContentTitle("标题")
.setContentText("内容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pendingIntent)
.build();
Notification notification1=notification.build();
startForeground(1,notification1);
}
使用IntentService
如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR的情况,这个时候需要用Android多线程技术。我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。
Android提供了一个IntentService类,首先提供一个无参的构造函数,并且需要在其内部调用父类的有参构造。然后要在子类中去实现onHandleIntent这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR问题,因为这个方法是在子线程中运行的。这个服务在运行结束之后会自动停止的
package com.example.servicetest;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.Nullable;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");//调用父类的有参构造函数
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy: executed");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//打印当前线程的id
Log.d("MyIntentService", "Thread id is: "+Thread.currentThread().getId());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startIntentService=(Button) findViewById(R.id.start_intent_service);
startIntentService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.start_intent_service:
//打印主线程的id
Log.d("MainActivity", "Thread id is"+Thread.currentThread().getId());
Intent intentService=new Intent(this,MyIntentService.class);
startService(intentService);
break;
default:
break;
}
}
隐式意图服务(银行例子)
绑定服务
package com.example.bankservicedemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import com.example.bankservicedemo.interfaces.INormalUserAction;
public class NormalUserActivity extends AppCompatActivity {
private static final String TAG = "NormalUserActivity";
private INormalUserAction mNormalUserAction;
private NormalUserConnection mNormalUserConnection;
private boolean mIsBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_normal_user);
doBindService();
}
//绑定服务
private void doBindService() {
Log.d(TAG, "doBindService: ");
//由于service那边是返回action,所以需要用隐式意图来进行绑定
Intent intent = new Intent();
intent.setAction("com.example.ACTION_NORMAL_USER");
intent.addCategory(Intent.CATEGORY_DEFAULT);
//Android5.0后,要在服务使用隐式意图,需要加上目标绑定包名packName,应用的包名
intent.setPackage("com.example.bankservicedemo");
mNormalUserConnection = new NormalUserConnection();
mIsBind = bindService(intent, mNormalUserConnection, BIND_AUTO_CREATE);
}
private class NormalUserConnection implements ServiceConnection{
//服务连接成功时
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: "+name);
mNormalUserAction = (INormalUserAction) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: "+name);
}
}
//下面三个是按钮点击事件
public void saveMoneyClick(View view){
Log.d(TAG, "saveMoneyClick: ");
mNormalUserAction.saveMoney(10000);
}
public void getMoneyClick(View view){
Log.d(TAG, "getMoneyClick: ");
mNormalUserAction.getMoney();
}
public void loadMoneyClick(View view){
Log.d(TAG, "loadMoneyClick: ");
mNormalUserAction.loadMoney();
}
//销毁时记得解开服务
@Override
protected void onDestroy() {
super.onDestroy();
if(mIsBind&&mNormalUserConnection!=null){
unbindService(mNormalUserConnection);
Log.d(TAG, "unbind service.. ");
mNormalUserConnection=null;
mIsBind=false;
}
}
}
服务创建
package com.example.bankservicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.example.bankservicedemo.impl.BankBossActionImpl;
import com.example.bankservicedemo.impl.BankWorkerActionImpl;
import com.example.bankservicedemo.impl.NormalUserActionImpl;
public class BankService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
String action=intent.getAction();
if(!TextUtils.isEmpty(action)){
if("com.example.ACTION_NORMAL_USER".equals(action)){
return new NormalUserActionImpl();
}else if("com.example.ACTION_BANK_WORKER".equals(action)){
return new BankWorkerActionImpl();
}else if("com.example.ACTION_BANK_BOSS".equals(action)){
return new BankBossActionImpl();
}
}
return null;
}
}
隐式服务声明
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.bankservicedemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ServiceTest">
<activity android:name=".bankWorkerActivity"></activity>
<activity android:name=".bank_bossActivity" />
<activity android:name=".NormalUserActivity" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 服务隐式意图 -->
<!-- android:exported="true"可以在外部的第三方应用拉起服务-->
<service
android:name=".BankService"
android:exported="true">
<intent-filter>
<action android:name="com.example.ACTION_NORMAL_USER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.example.ACTION_BANK_WORKER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<action android:name="com.example.ACTION_BANK_BOSS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
</manifest>
Android跨进程通信AIDL
- AIDL概述
AIDL意思即Android Interface Definition Language(安卓接口定义语言),用于定义服务器与客户端进行通信的一种描述语言,本质是AIDL其实是android端为我们定义的一个模板文件.aidl,最终还是会编译为.java文件。
在Android中,默认每个应用(application)执行在它自己的进程中,无法直接调用到其他应用的资源,这也符合沙箱(SandBox)的理念。所谓沙箱原理,一般来说用在移动电话业务中,简单地说旨在部分地或全部地隔离应用程序。
Android沙箱技术: Android“沙箱”的本质是为了实现不同应用程序和进程之间的互相隔离,即在默认情况 下,应用程序没有权限访问系统资源或其它应用程序的资源。 每个APP和系统进程都被分配唯一并且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。 每个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。 运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,因此Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序如果想要访问系统资源或者其它应用程序的资源必须在自己的manifest文件中进行声明权限或者共享uid。
- AIDL的引入
android开发中一项任务可能需要多个进程相互协作,相互委托,比如支付服务,某app需要进行支付,那么他需要调起第三方支付,进程之间需要通信,当支付完毕,第三方返回支付数据给app进程之间也需要通信,所以说如果一个应用只是单单的一个UI主进程而不涉及多个进程间的通信,那么这个app是不完美的!通过AIDL就可以满足进程间通信的需求,本质上也是通过Binder对象来进行传递数据。
通常,暴露接口方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。
AIDL模拟支付宝支付
1.明确需求
某app需要进行SQ币充值,需要调用第三方支付服务,然后第三方支付服务拉起一个新的Activity提供用户账单信息
2.项目结构
服务端(Server):
客户端(Client):
3. 代码实现
- 编写支付服务和支付界面(Server端)
客户端(Client)通过bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);绑定服务端的服务后执行服务端的onBind() 会返回return mThirdPartPlay;然后执行 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()所给。
整个程序中PayActivity绑定了两次,第一次是客户端(return mThirdPartPlay),第二次是拉起的第三方支付(return new PayAction())。
package com.example.aliplay;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class PayService extends Service {
private static final String TAG = "PayService";
private ThirdPartPlayImpl mThirdPartPlay;
public PayService() {
}
@Override
public IBinder onBind(Intent intent) {
String action = intent.getAction();
Log.d(TAG, "onBind: ->action"+action);
if(action!=null&&"com.example.aliplay.THIRD_PART_PAY".equals(action)){
//说明这个是第三方要求我们自付宝进行支付
mThirdPartPlay = new ThirdPartPlayImpl();
return mThirdPartPlay;
}
return new PayAction();
}
public class PayAction extends Binder{
public void Play(float payMoney){
Log.d(TAG, "Play money is-->"+payMoney);
//实际的支付是比较复杂的,比如加密,比如向服务器发起请求,等待服务器的结果
//支付方法
if(mThirdPartPlay!=null){
mThirdPartPlay.paySuccess();
}
}
public void onUserCancel(){
//用户点击界面上的取消/退出
if(mThirdPartPlay!=null){
mThirdPartPlay.payFailed(1,"user cancel pay");
}
}
}
private class ThirdPartPlayImpl extends ThirdPartPayAction.Stub{
private ThirdPartPayResult mCallback;
//ThirdPartPayResult 参数作为回调接口
@Override
public void requestPay(String orderInfo, float payMoney, ThirdPartPayResult callback) throws RemoteException {
Log.d(TAG, "oderInfo->"+orderInfo+"pay Money->"+payMoney);
this.mCallback=callback;
//第三方应用发起请求,打开一个支付界面
Intent intent = new Intent();
intent.setClass(PayService.this,PayActivity.class);
intent.putExtra(Constants.KEY_BILL_INFO,orderInfo);
intent.putExtra(Constants.KEY_PAY_MONEY,payMoney);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//表示Service和Activity跑在不同的任务上
startActivity(intent);
}
public void paySuccess(){
if(mCallback!=null) {
try {
mCallback.onPaySuccess();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void payFailed(int errorCode,String errorMsg) {
if (mCallback != null) {
try {
mCallback.onPayFailed(errorCode, errorMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
AIDL文件
requestPay方法中ThirdPartPayResult参数作为回调接口供客户端使用。说到这提下接口,接口可以隐藏内部细节,对调用者来说使用方便,对开发者来说接口使代码的健壮性增强,对调用者所使用的权限做了一定的限定。接口提高了开发效率,各端人员各司其职(面向接口编程)。
// ThirdPartPayAction.aidl
package com.example.aliplay;
import com.example.aliplay.ThirdPartPayResult;
// Declare any non-default types here with import statements
interface ThirdPartPayAction {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//发起支付
void requestPay(String orderInfo,float payMoney,ThirdPartPayResult callback);
}
// ThirdPartPayResult.aidl
package com.example.aliplay;
// Declare any non-default types here with import statements
interface ThirdPartPayResult {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void onPaySuccess();
void onPayFailed(in int errorcode,in String msg);
}
PayActivity支付交互界面
package com.example.aliplay;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class PayActivity extends AppCompatActivity {
private boolean mMIsBind;
private EditText mMPasswordBox;
private PayService.PayAction mPlayAction;
private static final String TAG="PayActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pay);
Log.d(TAG, "onCreate: 重新启动");
//因为我们的Activity也要跟服务进行通讯,告诉服务支付结果,所以的话我们也要绑定服务
doBindService();
initView();
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (mPlayAction!=null){
mPlayAction.onUserCancel();
}
}
private void initView() {
Intent intent = getIntent();
String orderInfo = intent.getStringExtra(Constants.KEY_BILL_INFO);
float payMoney = intent.getFloatExtra(Constants.KEY_PAY_MONEY, 0f);
TextView orderInfoTv = this.findViewById(R.id.order_info_tv);
orderInfoTv.setText("支付信息:"+orderInfo);
TextView payMoneyTv = this.findViewById(R.id.pay_money);
payMoneyTv.setText("支付金额:"+payMoney);
mMPasswordBox = this.findViewById(R.id.pay_password_input);
Button commitButton = this.findViewById(R.id.pay_commit);
commitButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//提交点击了
String trim=mMPasswordBox.getText().toString().trim();
if("123456".equals(trim)&&mPlayAction!=null){
mPlayAction.Play(payMoney);
Toast.makeText(PayActivity.this,"支付成功",Toast.LENGTH_SHORT).show();
finish();
Log.d(TAG, "onClick: pay finish");
}else {
Toast.makeText(PayActivity.this,"密码错误",Toast.LENGTH_SHORT).show();
}
}
});
}
private void doBindService() {
Intent intent = new Intent(this, PayService.class);
mMIsBind = bindService(intent,mConnection , BIND_AUTO_CREATE);
}
private ServiceConnection mConnection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected: '"+iBinder);
mPlayAction = (PayService.PayAction) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "onServiceDisconnected: "+componentName);
mPlayAction=null;
}
};
@Override
protected void onDestroy() {
super.onDestroy();
if(mMIsBind&&mConnection!=null){
unbindService(mConnection);
Log.d(TAG, "onDestroy: unbindService");
mMIsBind=false;
mConnection=null;
}
}
}
编写某App,导入AIDL接口文件
通过ThirdPartPay.Stub.asInterface得到第三方服务对象,继承ThirdPartPayResult.Stub重写回调接口方法,然后调用服务端的requestPay方法的时候将相应对象传入即可。
package com.example.sobclient;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.example.aliplay.ThirdPartPayAction;
import com.example.aliplay.ThirdPartPayResult;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button mBuySobBtn;
private TextView mSobCountTv;
private AlipayConnection mAlipayConnection;
private boolean mIsBind;
private ThirdPartPayAction mThirdPartPayAction;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "onCreate: 重新启动");
bindAlipayService();
//找到控件
initView();
setListener();
}
private void bindAlipayService() {
Intent intent = new Intent();
intent.setAction("com.example.aliplay.THIRD_PART_PAY");
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setPackage("com.example.aliplay");
mAlipayConnection = new AlipayConnection();
mIsBind = bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);
}
private class AlipayConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: "+mIsBind);
mThirdPartPayAction = ThirdPartPayAction.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected: "+name);
}
}
private void setListener() {
mBuySobBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//进行充值
try {
if(mThirdPartPayAction!=null) {
mThirdPartPayAction.requestPay("充值100SO币", 100.00f, new PayCallback());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
//回调接口
private class PayCallback extends ThirdPartPayResult.Stub{
@Override
public void onPaySuccess() {
//支付成功,那么我们就去修改UI上的内容
//实际上失去修改数据库,其实,支付宝是通过回调URL地址,通知我们的服务器
//点击支付回调方法
/*
跨进程通信的时候,走的是子线程,请不要在子线程操作ui,比如更新text,toast等等
*/
runOnUiThread(new Runnable() {
@Override
public void run() {
mSobCountTv.setText("100");
Toast.makeText(MainActivity.this,"充值成功!",Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onPayFailed(int errorcode, String msg) {
Log.d(TAG, "error code is: "+errorcode+"error Msg-"+msg);
Toast.makeText(MainActivity.this,"充值失败!",Toast.LENGTH_SHORT).show();
}
}
private void initView() {
mSobCountTv=findViewById(R.id.sob_count_tv);
mBuySobBtn=findViewById(R.id.buy_sob_btn);
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mIsBind&&mAlipayConnection!=null){
Log.d(TAG, "unbind service... ");
unbindService(mAlipayConnection);
mAlipayConnection=null;
mIsBind=false;
}
}
}
服务例子音乐播放器
Activity负责UI界面,Service负责逻辑,之间通过接口进行通讯
两个接口
package com.example.musicplayer.Interfaces;
/**
* 控制音乐的接口
*/
public interface IPlayerControl {
//播放状态
//播放
int PLAY_STATE_PLAY=1;
int PLAY_STATE_PAUSE=2;
int PLAY_STATE_STOP=3;
/**
* 把UI的控制接口设置给逻辑层(让逻辑层通知更新UI界面)
*
*/
void setViewController(IPlayerViewControl viewController);
/**
* 取消接口通知的注册(防止设置过来内存泄露)
*/
void unRegisterViewController();
/**
* 播放音乐
*/
void playOrPause();
/**
* 停止播放
*/
void stopPlay();
/**
* 设置播放进度
*/
void seekTo(int seek);
}
package com.example.musicplayer.Interfaces;
/**
* 通知UI更新的接口
*/
public interface IPlayerViewControl {
/**
* 播放状态改变的通知
* @param state
*/
void onPlayerStateChange(int state);
/**
* 播放进度的改变
* @param seek
*/
void onSeekChange(int seek);
}
主界面也就是UI层
package com.example.musicplayer;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Toast;
import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PAUSE;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PLAY;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_STOP;
public class MainActivity extends AppCompatActivity {
private SeekBar mSeekBar;
private Button mPlayOrPause;
private Button mClose;
private PlayerConnection mPlayerConnection;
private IPlayerControl mIPlayerControl;
private boolean isUserTouchProgressBar=false;
private final static String TAG="MainActivity" ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//动态申请权限
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
}else {
}
initView();
//设置相关的事件
initEvent();
//启动播放的服务
initService();
//绑定服务
initBindService();
Log.d(TAG, "onCreate: "+ Environment.getExternalStorageDirectory());
}
/**
* 开启播放的服务
*/
private void initService() {
Log.d(TAG, "initService: ");
startService(new Intent(this,PlayService.class));
}
/**
* 绑定服务
*/
private void initBindService() {
Log.d(TAG, "initBindService");
Intent intent = new Intent(this, PlayService.class);
mPlayerConnection = new PlayerConnection();
bindService(intent,mPlayerConnection,BIND_AUTO_CREATE);
}
private class PlayerConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected: "+iBinder);
mIPlayerControl = (IPlayerControl) iBinder;
mIPlayerControl.setViewController(mIPlayerViewControl);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "onServiceDisconnected");
mIPlayerControl=null;
}
}
private void initEvent() {
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//进度条发生改变
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//我的手已经触摸上去拖动
isUserTouchProgressBar=true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int touchProgress=seekBar.getProgress();
Log.d(TAG, "onStopTrackingTouch: "+touchProgress);
//停止拖动
if (mIPlayerControl != null) {
mIPlayerControl.seekTo(seekBar.getProgress());
}
isUserTouchProgressBar=false;
}
});
mPlayOrPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "onClick: ");
//播放或暂停
if(mIPlayerControl!=null) {
//如果绑定不成功为null就会崩,需要判空一下。在一些接口回调的时候就需要对其判空
mIPlayerControl.playOrPause();
}
}
});
mClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//关闭按钮被点击了
Log.d(TAG, "unonClick: ");
if(mIPlayerControl!=null){
mIPlayerControl.stopPlay();
}
}
});
}
private void initView() {
mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
mPlayOrPause = (Button) findViewById(R.id.play_or_pause_btn);
mClose = (Button) findViewById(R.id.close_bt);
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mPlayerConnection!=null){
Log.d(TAG, "unbindService onDestroy: ");
unbindService(mPlayerConnection);
}
}
//实现通知UI更新的接口
private IPlayerViewControl mIPlayerViewControl=new IPlayerViewControl() {
@Override
public void onPlayerStateChange(int state) {
switch (state){
case PLAY_STATE_PLAY:
//播放中的话,我们要修改按钮显示成暂停
mPlayOrPause.setText("暂停");
break;
case PLAY_STATE_PAUSE:
case PLAY_STATE_STOP:
mPlayOrPause.setText("播放");
break;
}
}
@Override
public void onSeekChange(int seek) {
//从上面的Log我们可以发现,这个不是主线程,所以不可以用于更新UI
//为什么更新进度不会崩溃呢?
//因为在android里面,有两个控件是可以用子线程去更新
//一个是ProgressBar,另外一个是surfaceView
runOnUiThread(new Runnable() {
@Override
public void run() {
if(!isUserTouchProgressBar){
mSeekBar.setProgress(seek);
}
}
});
//改变播放进度,有一个条件,什么->当用户的手触摸到进度条的时候,就不更新(不然会抖动)
}
};
/**
* 动态申请权限
* @param context 上下文
* @param permission 要申请的一个权限,列如写的权限:Manifest.permission.WRITE_EXTERNAL_STORAGE
* @return 是否有当前权限
*/
private boolean RequestPermissions(Context context, String permission) {
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
Log.i("requestMyPermissions",": 【 " + permission + " 】没有授权,申请权限");
ActivityCompat.requestPermissions((Activity) context, new String[]{permission}, 100);
return false;
} else {
Log.i("requestMyPermissions",": 【 " + permission + " 】有权限");
return true;
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case 1:
if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED)
{
mIPlayerControl.playOrPause();
}else {
Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
}
逻辑层
package com.example.musicplayer.presenter;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingDeque;
public class PlayerPresenter extends Binder implements IPlayerControl {
private static final String TAG="PlayerPresenter";
//控制UI
private IPlayerViewControl mViewControl;
private int mCurrentState=PLAY_STATE_STOP;
private MediaPlayer mMediaPlayer;
private Timer mTimer;
private SeekTimeTask mTimeTask;
@Override
public void setViewController(IPlayerViewControl viewController) {
this.mViewControl=viewController;
}
@Override
public void unRegisterViewController() {
mViewControl=null;
}
@Override
public void playOrPause() {
Log.d(TAG, "playOrPause: ..");
if(mCurrentState==PLAY_STATE_STOP){
//创建播放器
initPlayer();
//设置数据源
try {
mMediaPlayer.setDataSource("/storage/emulated/0/Music/my_look.mp3");
mMediaPlayer.prepare();
mMediaPlayer.start();
mCurrentState=PLAY_STATE_PLAY;
startTimer();
} catch (IOException e) {
e.printStackTrace();
}
}else if(mCurrentState==PLAY_STATE_PLAY){
//如果当前的状态是播放的,那么我们就暂停
if (mMediaPlayer!=null) {
mMediaPlayer.pause();
mCurrentState=PLAY_STATE_PAUSE;
stopTimer();
}
}else if(mCurrentState==PLAY_STATE_PAUSE){
//如果当前的状态是暂停,那么我们就继续播放
if(mMediaPlayer!=null){
mMediaPlayer.start();
mCurrentState=PLAY_STATE_PLAY;
startTimer();
}
}
//逻辑层通知UI更新界面
if(mViewControl!=null){
mViewControl.onPlayerStateChange(mCurrentState);
}
}
/**
* 初始化播放器
*/
private void initPlayer() {
if(mMediaPlayer==null){
mMediaPlayer = new MediaPlayer();
}
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
@Override
public void stopPlay() {
Log.d(TAG, "stopPlay: ");
if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
//停止的话再次开始会重头再来
mMediaPlayer.reset();
mCurrentState=PLAY_STATE_STOP;
stopTimer();
//更新播放状态
if(mViewControl!=null){
mViewControl.onPlayerStateChange(mCurrentState);
}
//释放资源
mMediaPlayer.release();
mMediaPlayer=null;
}
}
@Override
public void seekTo(int seek) {
Log.d(TAG, "seekTo: .."+seek);
//0-100之间
//需要做一个转换,得到的seek其实是一个百分比
if(mMediaPlayer!=null){
int tarSeek=(int) (seek*1.0f/100*mMediaPlayer.getDuration());
//设置进度
mMediaPlayer.seekTo(tarSeek);
}
}
/**
* 开启一个timeTask
*/
private void startTimer(){
if(mTimer==null){
mTimer = new Timer();
}
if(mTimeTask==null) {
mTimeTask = new SeekTimeTask();
}
//每隔500ms更新进度条
mTimer.schedule(mTimeTask,0,500);
}
private void stopTimer(){
if(mTimeTask!=null){
mTimeTask.cancel();
mTimeTask=null;
}
if(mTimer!=null){
mTimer.cancel();
mTimer=null;
}
}
private class SeekTimeTask extends TimerTask{
@Override
public void run() {
//获取当前的播放进度
if(mMediaPlayer!=null&&mViewControl!=null){
//currentPosition表示当前进度getDuration表示总的进度
int currentPosition=mMediaPlayer.getCurrentPosition();
//需要浮点数不然整除为0
Log.d(TAG, "current play position... "+currentPosition*1.0f);
//最后除出来是百分比所以需要乘100
Log.d(TAG, "getDuration... "+mMediaPlayer.getDuration()*100);
int curPosition= (int) (currentPosition*1.0f/mMediaPlayer.getDuration()*100);
mViewControl.onSeekChange(curPosition);
}
}
}
}
服务
package com.example.musicplayer.services;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import androidx.annotation.Nullable;
import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.presenter.PlayerPresenter;
public class PlayService extends Service {
private PlayerPresenter mPlayerPresenter;
@Override
public void onCreate() {
super.onCreate();
//进行判空,防止重复创建
if (mPlayerPresenter == null) {
mPlayerPresenter = new PlayerPresenter();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mPlayerPresenter;
}
@Override
public void onDestroy() {
super.onDestroy();
//释放资源(主要用于防止内存泄露)
mPlayerPresenter=null;
}
/*
这一部分逻辑层可以抽取出来建个包presenter
private class InnerBinder extends Binder implements IPlayerControl{
@Override
public void setViewController(IPlayerViewControl viewController) {
}
@Override
public void unRegisterViewController() {
}
@Override
public void playOrPause() {
}
@Override
public void stopPlay() {
}
@Override
public void seekTo(int seek) {
}
}
*/
}
服务与线程的区别
service:服务是运行在主线程中的,所以不能用来执行耗时操作,必须在服务中新开线程,服务没有Activity界面,即使Activity被销毁,但只要进程还在,服务就会在后台继续执行,服务一般用来在后台执行操作,而线程一般写在服务中,这样activity就能通过服务来控制线程。服务有分为前台服务和后台服务,后台服务优先级比较低,当系统不足时会被系统回收,前台服务优先级比较高,会以通知的形式显示在界面上,不会被系统回收。
线程:线程可以用来执行耗时操作,它与服务最大的区别在于服务是运行在主线程中,线程一般不会直接在activity中创建,因为这不利于Activity对线程的控制,线程一般都在服务中创建。
如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity
没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的
Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread