Android Service学习(二)

本篇主要讲述service的绑定状态及如何设计前台service。


一、综述


绑定服务允许本APP内的activity与service进行绑定从而去执行具体的任务,也允许其他的应用程序APP绑定并且与之交互的service类实现。

当应用程序通过bindService()方法绑定到service时,service处于绑定状态。绑定服务可以为client-server提供接口,允许组件与service进行交互,发送请求,获得结果等操作,甚至还可以进行IPC(进程间通信)。

在activity采用bindService()与service进行绑定,会调用到service中onBind()函数,此函数返回一个bind实例。但是由于绑定是异步的,bindService()方法立即返回并且不返回Ibinder到客户端,因此需要在activity或者fragment中创建一个ServiceConnection的匿名类,并在这个类中重写onServiceConnected()和onServiceDisconnected(),用来监视客户端与服务端的连接。ServiceConnection中包含系统调用发送Ibinder的回调方法。

注意:有多个client可以连接到service,但是只有第一个客户端绑定后,系统会调用service的onBind()函数,来获取一个binder对象。然后系统会将获取的对象发送给其他的client,但是不会再次调用onBind(),简单滴说,就是只调用一次onBind(),多次使用binder对象。


二、绑定的几种方法


2.1 继承Binder类

这种方法最常见,仅应用于私有APP所创建的service。此时service和APP运行于相同的进程。继承Binder类实现的步骤:

  • 在服务中创建继承于Binder的类,在这个类中包含activity或者fragment可以调用的公共方法;
  • 从onBind()返回可以在其他activity中使用到的Binder类实例;
  • 客户端的Activity或者Fragment创建一个ServiceConnection的匿名类来监视client-service的连接;

如在MyService.class中建立继承Binder的类MyBinder,并且实例化。

private MyBinder myBinder = new MyBinder();

@Override
public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    return myBinder;
}

public class MyBinder extends Binder {
    public void startDownLoad(){
        Log.e(TAG,"开始下载");
    }
}

然后在MainActivity.class中,就可以通过ServiceConnection来监听service和client的连接。

private MyService.MyBinder myBinder;
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //连接成功后需要执行的任务
    myBinder = (MyService.MyBinder) service;
        myBinder.startDownLoad();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.e("MyService","解除链接");
    }
};

2.2 使用Messenger

当需要跨进程工作时,可以使用Messenger来为服务创建接口。Messenger,可被称作“信使”。service可以定义不同的Handler对象来响应不同类型的message。而Handler是Messenger的基础,可以与client分享binder对象,允许client使用Message对象向service发送命令。因此,通过Messenger返回的binder对象可以不同考虑client-service是否属于同一个进程的问题。此外,Messenger将所有的请求队列化到单独的线程,因此也不必考虑线程的安全问题。在使用Messenger需要注意:

  • Handler用于创建Messenger,以及通过Handler的handleMessage()方法来接收message来决定如何操作;
  • Messenger创建IBinder,并且service从onBind()返回这个实例给客户端;
  • 客户端使用IBinder来实例化Messenger,然后在通过Messenger将message发送给service端;

例,新建一个MyMessengerService.class

public class MyMessengerService extends Service {
    public static final int TEST = 1;
    private final Messenger messenger = new Messenger(new MyHandler());

    public MyMessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        //通过messenger创建binder对象
        return messenger.getBinder();
    }

    /**
     * MyHanler用于创建messenger对象,并且通过handleMessage()接收来自客户端的message来觉得如何操作
     */
    public class MyHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch(msg.what)
            {
                case TEST:
                    ToastMgr.show("这是一个测试的service");
                    break;
                default:
                    break;
            }
        }
    };
}

客户端的主要操作代码:

private Messenger messenger = null;
private boolean bound = false;
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        messenger = new Messenger(service);
        bound = true;
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        messenger = null;
        bound = false;
    }
};

执行绑定按钮:

Intent bindIntent = new Intent(mActivity,MyMessengerService.class);
mActivity.bindService(bindIntent,connection,Context.BIND_AUTO_CREATE);
if (!bound){
    return;
}else {
    Message msg = Message.obtain(null, MyMessengerService.TEST,0,0);
    try {
        messenger.send(msg);
    }catch (RemoteException e){
        Log.e(TAG, "onClick: ", e);
    }
}

执行解除绑定按钮:

if (bound) {
    mActivity.unbindService(connection);
    Log.e(TAG, "onClick: 解除绑定");
    bound = false;
}

在以上实例代码中,服务端主要完成的工作是通过Messenger实例化一个Ibinder对象,创建一个Handler用于创建Messenger,并且接收来自客户端的message。

客户端的全部工作是根据绑定服务所返回的binder对象创建一个Messenger对象,并且通过Messenger的send()方法将message发送给服务端。

2.3 使用AIDL

绝大多数的APP几乎都不采用这种方法来执行绑定服务,主要是因为需要多线程的能力而导致更加复杂的设计,本篇就不做介绍了。

注意:
在android的四大组件中,只有Activity、ContentProvider、Service能够绑定到服务,而BroadcastReceiver(简称BR)是不可以绑定到服务的。因为BR的生命周期很短,在执行完onReceive()函数后,BR的生命周期就结束了,而service本身的生命周期与client本身的生命周期有关,因此可以使用startService()来代替。


三、设置前台service


service几乎都被设定运行于后台,但是由于其优先级较低,在系统资源不足的情况下,有可能会kill后台运行的service。如果想让该service可以一直运行,而不用担心系统资源不足带来的问题,就可以针对此设计前台service。其特点在于会有一个一直运行的UI显示在系统的状态栏里,当用户下拉时,会看到更加详细的信息。

实例如下:

public class MyService extends Service {
    private static final String TAG = MyService.class.getSimpleName();
    private MyBinder myBinder = new MyBinder();
    public MyService(){}

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG,"on Create excused");
        Log.e(TAG,"Service的线程id = " + Thread.currentThread().getId());

        Intent resultIntent = new Intent(this, SecondFragment.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,resultIntent,0);

        NotificationCompat.Builder nfBuilder = new NotificationCompat.Builder(this);
        nfBuilder.setSmallIcon(R.mipmap.ic_launcher);//设置通知栏中的图标
        nfBuilder.setContentTitle("测试title");//设置通知栏中的标题
        nfBuilder.setContentText("这是一个测试的通知栏UI");//设置通知栏中的内容
        nfBuilder.setContentIntent(pendingIntent);//设定点击的意图
        startForeground(1,nfBuilder.build());
//        nfBuilder.setAutoCancel(true);//只有在通知的时候会自动取消该通知
        

        /*如何设置通知*/
        /*
        int nfID = 1;
        Notification notification = nfBuilder.build();
        NotificationManager manager = (NotificationManager) getSystemService(this.NOTIFICATION_SERVICE);
        manager.notify(nfID, notification);
        */
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG,"onStartCommand excused");        
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG,"onDestroy excused");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return myBinder;
    }

    public class MyBinder extends Binder {
        public void startDownLoad(){
            Log.e(TAG,"开始下载");
        }
    }
}

测试结果如下图示:


Android Service学习(二)_第1张图片
测试结果

在以上代码中给出了通过NotificationCopmat.Builder的方式建立一个通知栏来显示前台service。当然也可以使用Notification.Builder建立,效果一样,只不过前者在API上的兼容性更好。

BTW

可以通过上述的NotificationCopmat.Builder方法建立通知,然后根据通知信息的更新,只需要发送同一个nfID即可进行更新通知的操作。

此外,在上述示例代码中,有用到PendingIntent和Intent,简单的提及下这二者之间的区别。

Intent是及时启动,并且随activity的消失而消失,而PendingIntent(未执行的,即将发生的),顾名思义,不是马上就启动,用于即将发生的事情。

PendingIntent作为对Intent的包装,通常通过getActivity,getBroadCast等方法获得实例,通常用在Notification,延迟执行的intent。例如在Notification中,在下拉状态栏中点击某notification的时候才会发生activity的跳转,此时用到的就是如上示例中的pendingintent。

你可能感兴趣的:(Android Service学习(二))