Android Service 你应该掌握的东西

转载标明出处: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,避免重复创建Service
mPendingIntent = 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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星点点-

请我喝杯咖啡呗

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值