Android Service 你应该掌握的东西

来源:互联网 发布:ant.jar 执行sql脚本 编辑:程序博客网 时间:2024/06/10 01:57

转载标明出处:http://blog.csdn.net/u012861467/article/details/51646935

Service 是Android四大组件之一,通常是在后台运行的,执行一些耗时的操作。

对于Service 我们需要掌握的知识点有:

1、Service 的生命周期

2、Service 的创建

3、远程服务的AIDL 跨进程通讯

4、提高 Service 的生存率的一些方法

下面我们来一步步学习。


一、Service 的生命周期

Service 的启动方式有两种,下面来分析一下这两种的区别:

(1)通过 startService 启动

这种方式启动的服务跟启动它的Activity是没有交互的,即使启动它的Activity被destory了,该服务还可以继续运行。那什么时候销毁呢?调用stopServive或该服务所在进程被销毁了,这个服务才可能销毁。

(2)通过 bindService 启动

这种方式启动的服务跟启动它的Activity是可以交互的,依靠的是Binder进程间通讯机制,启动它的Activity可以通过binder对象调用Service的一些方法,这个并不是直接调用Service内部的方法,只是先得到一个映射,再通过映射找Service内部的方法。因为这种方式启动的Service跟启动它的Activity 进行了绑定,所以它会受到Activity的生命周期的影响,在Activity销毁的时候,绑定的Service也会销毁。注意Activity 销毁的时候记得解除绑定,不然会报错。

我们来看看不解除绑定的情况下打印出来的日志信息:


可以看出MainActivity可以正常的回调onDestroy方法,MyService也能回调onDestroy方法,但是会报错。

错误信息如下:

E/ActivityThread: Activity com.example.servertest.MainActivity has leaked ServiceConnection com.example.servertest.MainActivity$1@b0ff6638 that was originally bound here

注意:

(1)无论使用那种方式启动的Service,Service 都会执行一次完整的逻辑,它不能被stopService调用强制停止当前进行的操作。

(2)Service 跟线程没有什么必然的联系,不要混淆。

(3)Service 默认是跟Activity在同一个进程里(主线程),所以在Service里执行耗时的操作需要另开新线程,避免ANR。


二、Service 的创建

创建一个Service 是比较简单的,我们需要继承Service这个类,重写里面的一些方法完成我们需要的逻辑。

下面是一个例子:

public class MyService extends Service {    public static final String TAG = "MyService";    private MyBinder mBinder = new MyBinder();    @Override    public void onCreate() {        super.onCreate();        Log.d(TAG, "onCreate() executed");        Log.d(TAG, "MyService thread id is " + Thread.currentThread().getId());    }    @Override    public void onDestroy() {        super.onDestroy();        // 销毁一些不再使用的资源        Log.d(TAG, "onDestroy() executed");    }    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.d(TAG, "onStartCommand() executed");        //因为当前服务跟Activity是在同一个进程里,耗时操作应该另起线程处理,避免ANR        new Thread(new Runnable() {            @Override            public void run() {                Log.d(TAG, "startDownload() executed");                Log.d(TAG, "Download thread id is " + Thread.currentThread().getId());                // 执行具体的下载任务            }        }).start();        return super.onStartCommand(intent, flags, startId);    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    class MyBinder extends Binder {        public void startDownload() {            new Thread(new Runnable() {                @Override                public void run() {                    Log.d(TAG, "startDownload() executed");                    Log.d(TAG, "Download thread id is " + Thread.currentThread().getId());                    // 执行具体的下载任务                }            }).start();        }    }}
Service里面创建了一个Binder子类对象,该对象有个startDownload方法,将作为onBind 方法返回对象。onBind 方法只有使用bindService开启服务的时候才会被调用,它返回的IBinder对象用于跟Service通讯用的。

(1) 使用startService开启服务

Intent intent = new Intent(MainActivity.this,MyService.class);startService(intent);
(2) 使用bindService 开启服务

Intent intent = new Intent(MainActivity.this,MyService.class);bindService(intent,serviceConnection,BIND_AUTO_CREATE);

bindService方法里面的参数serviceConnection是一个实现 ServiceConnection接口的对象,我们可以这样创建它:

serviceConnection = new ServiceConnection() {            @Override            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {                // 简单服务                myBinder = (MyService.MyBinder) iBinder;                myBinder.startDownload();            }            @Override            public void onServiceDisconnected(ComponentName componentName) {            } };

当Service绑定之后会回调 onServiceConnected 方法,传入的参数有一个IBinder 对象,我们就是通过这个对象跟Service通讯。这里的例子代码就是调用了服务的startDownload方法。


三、远程服务的跨进程通讯

  上面例子我们创建的是本地服务,本地服务依附的是主进程,它并没有运行在另外的进程,还有一种服务叫远程服务,它是运行在独立的进程。这里为什么要明确是运行在主进程还是独立进程呢?,因为本地服务依附的是主进程,并不需要使用aidl跨进程通讯,操作起来相对简单,而远程服务需要使用aidl暴露自己的接口供客户端调用。

  为什么要使用aidl呢?打个比方,有一台陌生的机器(远程服务对你来说是陌生的,你不知道它提供了什么给你),它会提供一些功能性的服务,而你对它并不了解不知怎样让它为你服务,这时候aidl就相当于是用户手册,它列举了这台机器的所有功能的操作指引,你(客户端)只需按照它的步骤(方法)就可以获得相应的服务功能,而不需要知道机器是怎么做的。

  把本地服务转成远程服务可以在清单文件中为Service添加 android:process=":remote" 属性,该服务就会运行在独立进程里。

下面来了解一下怎样使用AIDL实现跨进程通讯

(1)先定义一个aidl文件,在里面定义我们需要的功能声明,有点类似定义接口的语法。

  我使用的是Android studio,点击工程的根目录右键选择 “NEW”--> “AIDL” --> “AIDL File”,编辑aidl文件的名称,点击“OK”,软件自动帮你创建aidl文件,aidl文件放在跟java同级目录下的aidl文件夹下,路径跟工程的包名一致。


IMyAidlInterface.aidl 文件里面的内容:

package com.example.servertest;interface IMyAidlInterface {    int plus(int a, int b);    String toUpperCase(String str);}

自己声明了两个方法,一个是加法操作,另一个是字符串字母转大写。

定义好aidl文件之后,点击make,软件会自动生成aidl对应的java接口文件,不用去修改它和编辑它。


(2)创建一个远程服务 RemoteService,在服务里面实现定义好的接口(就是由aidl文件自动生成的接口文件)

private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {        @Override        public int plus(int a, int b) throws RemoteException {            return a+b;        }        @Override        public String toUpperCase(String str) throws RemoteException {            return str.toUpperCase();        }};

这个 mBinder 对象就作为Service 的onBind方法返回的IBinder对象。也就是我们在 onServiceConnected 方法参数中获取的IBinder对象,有了这个对象,Activity就可以跟远程服务通讯了。Stub 是Binder的子类。

(3)Activity 绑定远程服务

Intent intent = new Intent(MainActivity.this,RemoteService.class);bindService(intent,serviceConnection,BIND_AUTO_CREATE);

创建实现 ServiceConnection 接口的对象

serviceConnection = new ServiceConnection() {            @Override            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {                //AIDL 跨进程通讯                iMyAidlService = IMyAidlInterface.Stub.asInterface(iBinder);                try {                    int result = iMyAidlService.plus(3, 5);                    String upperStr = iMyAidlService.toUpperCase("hello world");                    Log.d("iMyAidlService", "result is " + result);                    Log.d("iMyAidlService", "upperStr is " + upperStr);                } catch (RemoteException e) {                    e.printStackTrace();                }            }            @Override            public void onServiceDisconnected(ComponentName componentName) {            }        };

通过IMyAidlInterface.Stub.asInterface(iBinder); 可以获取远程服务的Binder对象。有了这个Binder对象就可以调用远程服务的方法了。

AIDL对Java类型的支持:
1.AIDL支持Java原始数据类型。
2.AIDL支持String和CharSequence。
3.AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中。
4.AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句。


怎样在另外一个应用里调用这个服务呢?

在清单文件里为服务添加 intent-filter 标签,增加该服务可以处理的action,这里自定义了一个action。


把aidl文件拷贝到另外一个应用程序里,包括aidl所在的包目录结构。

在另外的应用Activity里使用隐式 Intent 绑定远程服务

Intent intent = new Intent("com.example.servicetest.MyAIDLService");bindService(intent, serviceConnection, BIND_AUTO_CREATE);

serviceConnection 创建跟上面一样。

四、提高 Service 的生存率的一些方法

要做杀不死服务,就要知道怎样提高Service存活率,因为在Android 的高版本中没有操作常驻后台的服务是会被系统杀掉的。

目前见过的方法有:

(1)提升server进程优先级

在AndroidManifest.xml文件中为 Service 的 intent-filter 添加android:priority = "1000"属性,1000是最高值,如果数字越小则优先级越低,同时适用于广播。


(2)前台服务

使用startForeground(int, Notification)方法来将service设置为前台服务

public class ForegroundService extends Service {    public static final String TAG = "ForegroundService";    @Override    public void onCreate() {        super.onCreate();        // 设置了点击通知后就打开MainActivity        Intent notificationIntent = new Intent(ForegroundService.this, MainActivity.class);        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);        Notification notification = null;//        // 低于 API 11 写法//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {//            notification = new Notification(R.mipmap.ic_launcher, "有通知到来", System.currentTimeMillis());//            notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容", pendingIntent);//        }        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){            // 高于 API 11 低于 API 16            Notification.Builder builder = new Notification.Builder(this)                        .setAutoCancel(true)                        .setContentTitle("有通知到来")                        .setContentText("describe")                        .setContentIntent(pendingIntent)                        .setSmallIcon(R.mipmap.ic_launcher)                        .setWhen(System.currentTimeMillis())                        .setOngoing(true);            notification = builder.getNotification();        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {            // 高于 API 16            notification = new Notification.Builder(this)                    .setAutoCancel(true)                    .setContentTitle("有通知到来")                    .setContentText("describe")                    .setContentIntent(pendingIntent)                    .setSmallIcon(R.mipmap.ic_launcher)                    .setWhen(System.currentTimeMillis())                    .setOngoing(true)                    .build();        }        // 调用startForeground()方法就可以让MyService变成一个前台Service        startForeground(1, notification);    }......}

(3)为 Service 中的onStartCommand方法指定START_STICKY标志

public class ForegroundService extends Service { ... @Override    public int onStartCommand(Intent intent, int flags, int startId) {        Log.d(TAG, "onStartCommand() executed");        flags = START_STICKY;        return super.onStartCommand(intent, flags, startId);    } ... }

onStartCommand 的标志有:

START_STICKY如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。START_NOT_STICKY“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。START_REDELIVER_INTENT重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。START_STICKY_COMPATIBILITYSTART_STICKY的兼容版本,但不保证服务被kill后一定能重启。










(4)service +broadcast 方式

新建一个广播接收器,接收各种广播,接收到广播之后检查服务是否开启,没有开启就开启服务。

<receiver android:name=".MyBroadcastReceiver" >     <intent-filter>           <action android:name="android.intent.action.BOOT_COMPLETED" />           <action android:name="android.intent.action.USER_PRESENT" />           <action android:name="com.ForegroundService.destroy" />     </intent-filter></receiver>
也可以在Service的onDestory 方法里通过发送自定义广播,由广播接收器重新启动服务或直接在onDestory方法里启动服务。

public class ForegroundService extends Service {    ...    @Override    public void onDestroy() {        super.onDestroy();        // 销毁一些不再使用的资源        Log.d(TAG, "onDestroy() executed");        // 注意在onDestroy里还需要stopForeground(true);        stopForeground(true);        // 销毁时发送广播,重新启动Server        Intent intent = new Intent("com.ForegroundService.destroy");        sendBroadcast(intent);        super.onDestroy();    }    ...}

(5)清单文件的 Application 标签添加 android:persistent="true" 属性

          在系统启动之时,AMS的systemReady()会加载所有persistent为true的应用,并不是设置persistent 属性为true就可以了,这种方式必须同时符合FLAG_SYSTEM(app需放在/system/app目录,即系统目录)及FLAG_PERSISTENT(android:persistent="true"),单设置该属性无效。

(6)AlarmManager 定时开启服务

使用 AlarmManager 的闹钟功能定时开启服务,可以添加在服务的onCreate 方法内

Intent intent = new Intent(getApplicationContext(), ForegroundService.class);mAlarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);//服务开启模式要设置为Intent.FLAG_ACTIVITY_NEW_TASK,避免重复创建ServicemPendingIntent = PendingIntent.getService(this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);long now = System.currentTimeMillis();//60 s 间隔mAlarmManager.setInexactRepeating(AlarmManager.RTC, now, 60000, mPendingIntent);

源码的地址放在我的Github地址上,里面有详细的解释

转载标明出处:http://blog.csdn.net/u012861467/article/details/51646935

例子源码地址:

点击打开链接



1 0