Service
安卓的四大组件之一,支持后台运行。
流程
首先服务被创建的时候我们会调用onCreate
方法,启动服务的时候会调用onStartCommand
,在停止服务的时候我们会调用onDestroy
,需要关注的是我们第一次启动服务的时候才呼调用onCreate
这个方法。
播放音乐实例
首先创建一个类,这个类继承于Service,我们先进行服务的配置:
1
| <service android:name="com.example.android_study19.MyService"></service>
|
服务配置完毕之后,我们就需要写启动和停止服务的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void onClick(View view) { Intent intent = new Intent(); intent.setClass(this, MyService.class); switch(view.getId()) { case R.id.button1: startService(intent); break; case R.id.button2: stopService(intent); break; } }
|
可以看到也就是通过intent进行数据的传递,将我们的类传给intent对象,然后就正常启动和停止就可以了。
然后写服务类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| package com.example.android_study19;
import android.app.Service; import android.content.Intent; import android.media.MediaPlayer; import android.media.MediaPlayer.OnPreparedListener; import android.os.IBinder;
public class MyService extends Service {
private MediaPlayer player=null; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { player = new MediaPlayer(); try { player.setDataSource("/data/a.mp3"); } catch (Exception e) { e.printStackTrace(); } }
@Override public int onStartCommand(Intent intent, int flags, int startId) { try { player.setOnPreparedListener(new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { player.start();
} }); player.prepareAsync(); } catch (Exception e) { e.printStackTrace(); } return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { player.stop(); player.release(); super.onDestroy(); } }
|
大多是关键的注释写在了上面,就是一个音乐的播放,我们需要将资源文件传递上去,这个资源文件在播放前需要准备,因为可能耗时很长,所以我们用异步的方式进行传递,使用了异步就需要回调函数,setOnPreparedListener
这个就是回调设置,回调的时候我们启动播放音乐。
本地绑定
意思就是,我们只能在同一个进程进行服务的绑定,如果不在同一个进程就会绑定失败。
我们想实现暂停的按钮,也就是想实现Service这个类中的暂停方法,我们需要用到绑定,首先我们先写一个内部的binder类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class MyServiceBinder extends Binder { public void zanting() { Log.d("wker", "zanting"); MyService.this.zanting(); } public void jixu() { Log.d("wker", "jixu"); MyService.this.jixu(); } }
|
注意的是我这里继承了Binder而不是实现IBinder接口,其实我们是要返回这个接口的实现对象,为什么不实现,是因为接口太多方法要重写,我们继承的这个实现类帮我们做了,我们只需要写上我们的方法就可以了。
然后我们在服务的onBind
这个方法中返回我们实例化好的一个绑定对象:
1 2 3 4 5
| @Override public IBinder onBind(Intent intent) { return new MyServiceBinder(); }
|
服务类写的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } @Override public void onRebind(Intent intent) { super.onRebind(intent); } public void zanting() { player.pause(); } public void jixu() { player.start(); }
|
需要注意的是,在我测试的过程中发现,我们绑定上服务的时候,我们貌似不能直接进行stopService
,这个结束不了。而只有我们在unbindService
方法调用之后服务会自动停止。
绑定服务的代码:
1
| bindService(intent, conn, Context.BIND_AUTO_CREATE);
|
这句代码第一个也是intent对象,第二个是一个服务链接对象,第三个是一个flag,这里用的是我们自动进行服务的创建。
1 2
| MyServiceBinder binder; private ServiceConnection conn;
|
我们先要有一个我们自己写的binder对象,还有一个服务连接对象,我们实现ServiceConnection
的内部类,在我们窗口被创建之后我们就要进行服务链接对象的实例化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| conn = new ServiceConnection() {
@Override public void onServiceDisconnected(ComponentName name) { }
@Override public void onServiceConnected(ComponentName name, IBinder service) { binder = (MyServiceBinder)service; Log.d("wker", "bind end"); } };
|
第一个重载不是太重要,第二个重载就是说,当我们被绑定上之后,我们就会回调这个方法,对应的我们也就获取到了我们在服务中创建的那个Binder对象,通过参数的方式传递给了我们,我们接受之后,强转成我们的内部类对象就好了,之后我们就能通过这个Binder进行暂停和继续了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public void onClick(View view) { Intent intent = new Intent(); intent.setClass(this, MyService.class); switch(view.getId()) { case R.id.button1: startService(intent); break; case R.id.button2: stopService(intent); break; case R.id.button3: bindService(intent, conn, Context.BIND_AUTO_CREATE); break; case R.id.button4: unbindService(conn); break; case R.id.button5: binder.zanting(); break; case R.id.button6: binder.jixu(); break; } }
|
暂停继续就这样子实现了,但是需要注意的是,由于我们绑定了服务,我们绑定服务的生命周期和程序一样,当程序退的时候绑定对象也就停止了,但是在我测试的过程中发现两个问题:
- 退出之后只是报了异常,而我们的服务并没有停止。
- 我发现bindService这个第三个参数并没有帮我们自动创建服务。
暂时解决了第二个,因为我们只是create(创建服务),而并不是启动服务。
第一个也解决了,下面启动和绑定一起使用的时候提到。
AIDL远程绑定
应用场景:也就是在不同进程之间进行数据的传递,和管道、邮槽不太像。
这个东西有点复杂,操作起来也有点绕。
首先我们需要有一个服务端,提供这个服务的,一般不是咱写,但是我们至少也要会,首先我们常规的写一个服务,这个服务要使用隐式意图:
1 2 3 4 5
| <service android:name=".MyService"> <intent-filter > <action android:name="com.example.android_study20.myservice"/> </intent-filter> </service>
|
这个就是隐式意图,action就是我们要使用对外的intent名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.example.android_study20;
import com.example.aidl.MyserviceAidl;
import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException;
public class MyService extends Service {
@Override public IBinder onBind(Intent intent) { return new MyBinder(); } public int sum(int a,int b) { return a+b; } private class MyBinder extends MyserviceAidl.Stub {
@Override public int sum(int a, int b) throws RemoteException { return MyService.this.sum(a, b); } } }
|
我们也是常规的一个服务,要对外使用的就是sum方法,但是我们需要指定一个内部类,继承的是一个比较特别的内部类,这个内部类是eclipse生成的,何时生成,看下面。
我们新建一个包,最好新建,然后包中生成一个类或者接口,改为一个接口,然后将这个java文件更名为aidl文件,刷新一下,你的包的长相在eclipse会变成这个样子:
然后我们写一个接口,这个接口要和文件名一样,然后写上你要导出的方法:
1 2 3 4 5 6
| package com.example.aidl;
interface MyserviceAidl { int sum(int a,int b); }
|
感觉有点def的味道,嘻嘻。
这个时候我们就会发现,eclipse给我们生成了一个这样子的东西:
这个时候操作就完成了。
这里需要注意的是,我们对外操作的方法是有一定限制的,类型来说的话呢,我们如果要使用常规安卓自带的一般不需要导包,但是如果自定义的需要,并且我们这个自定义的类需要实现Parcelable接口,并且我们如果在参数中使用自定义的话呢需要指明:in,out,inout表示我们是要对这个参数做的操作,和C++差不多,并且接口前面是没有修饰符的。
服务端就编写完毕了,我们要写客户端了。
首先客户端我们需要先将我们的那个AIDL包复制过来,和服务端一样了就,然后我们就需要绑定服务,使用隐式意图的话呢,设置action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void onClick(View view) { Intent service = new Intent(); service.setAction("com.example.android_study20.myservice"); switch (view.getId()) { case R.id.button1: bindService(service, conn, Context.BIND_AUTO_CREATE); break; case R.id.button2: unbindService(conn); break; case R.id.button3: if(binder != null) { try { int c = binder.sum(2, 3); Log.d("wker", c+""); } catch (RemoteException e) { e.printStackTrace(); } } break; } }
|
正常的绑定服务,然后我们需要注意的是,我们这里用的服务连接对象返回的binder是有点不同。
1 2
| private ServiceConnection conn; private MyserviceAidl binder;
|
这个binder是我们AIDL中的接口,然后在服务连对象匿名内部类中的onServiceConnected
方法我们返回的binder是这样子获得的:
1 2 3 4 5 6 7 8 9 10 11 12
| conn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { } @Override public void onServiceConnected(ComponentName name, IBinder service) { binder = MyserviceAidl.Stub.asInterface(service); } };
|
有那么一点奇怪,但是安卓就是这个样子规定的,没有办法,就是这么写,也需要注意的是,我们需要将我们的导出的方法进行异常的捕获。
启动绑定服务流程
上面有一点忘记了,就是说我们接触服务的时候返回值代表的是是否可以重新绑定。
在绑定服务之后,如果我们解除绑定,那么服务也是会停止的,正确的解决办法就是,我们绑定服务之后再启动服务(顺序可变),当我们在解绑服务的时候我们才会不会停止服,当我们再次绑定的时候我们其实调用的是重新绑定。
在我们绑定启动服务之后,我们点击停止服务,我们并不会立即停止,而是惠济路,只有等到解除绑定服务的时候才会真正的停止服务。
解决上面的第一个问题,这种情况是正确的,因为我们混合使用了,混合调用之后仅仅是解绑。
异步服务
为了防止在主线程中操作过多的大型数据交互的资源,我们也想在子线程中操作,所以就有了这个东西,和普通的服务差不多,但是是异步的,继承IntentService
类,我们需要重写的是:onHandleIntent
方法。
配置文件:
1 2
| <service android:name=".MyIntentService"> </service>
|
点击事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void onClick(View view) { Intent service = new Intent(); service.setClass(this, MyIntentService.class); switch (view.getId()) { case R.id.button1: Log.d("wker", "test"); startService(service); break; case R.id.button2: stopService(service); break; } }
|
也就是正常的服务,因为这个异步也就是继承那个原先的服务类,这里我发现,当我们没有编写按钮点击的方法的时候,会爆出nosuchmethodexception异常
,我写错了点击的回调函数。
服务类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.example.android_study21;
import android.app.IntentService; import android.content.Intent; import android.os.Looper; import android.util.Log;
public class MyIntentService extends IntentService {
public MyIntentService() { super(MyIntentService.class.getName()); }
@Override public void onCreate() { Log.d("wker", "create"); super.onCreate(); } @Override public void onDestroy() { Log.d("wker", "destory"); super.onDestroy(); }
@Override protected void onHandleIntent(Intent intent) { Log.d("wker", "test"); }
}
|
这里刚才由于把Create方法的父类调用没写,也会出错,运行时异常。
这里的执行流程是这样子的,我们一绑定服务,服务就会先创建,然后执行我们的onHandleIntent
方法,这个方法是一个异步的(子线程中运行),当这个方法一执行完毕,就会自动的关闭服务,也就是说我们不需要手动停止了!