0%

Android Service

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.prepare();//准备资源,需要消耗资源,服务是在主线程调用,所以可能会导致主线程阻塞
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;
}
}

暂停继续就这样子实现了,但是需要注意的是,由于我们绑定了服务,我们绑定服务的生命周期和程序一样,当程序退的时候绑定对象也就停止了,但是在我测试的过程中发现两个问题:

  1. 退出之后只是报了异常,而我们的服务并没有停止。
  2. 我发现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会变成这个样子:
AIDL包
然后我们写一个接口,这个接口要和文件名一样,然后写上你要导出的方法:

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) {
//boolean real = (Looper.myLooper()==Looper.getMainLooper());
Log.d("wker", "test");
}

}

这里刚才由于把Create方法的父类调用没写,也会出错,运行时异常。
这里的执行流程是这样子的,我们一绑定服务,服务就会先创建,然后执行我们的onHandleIntent方法,这个方法是一个异步的(子线程中运行),当这个方法一执行完毕,就会自动的关闭服务,也就是说我们不需要手动停止了!