Android四大组件之Service全解析

1. 简介

这篇文章会从Service的一些小知识点,延伸到Android中几种经常使用进程间通讯方法。html

2. 进程

       Service是一种不提供用户交互页面可是能够在后台长时间运行的组件,能够经过在AndroidManifest.xml设置Service的android:process=":remote"属性,让Service运行另外一个进程中,也就是说,虽然你是在当前应用启动的这个Service,可是这个Service和这个应用并非同一个进程。android

四大组件都支持android:process=":remote"这个属性。缓存

由于Service能够运行在不一样的进程,这里说一下Android中几种进程的优先级,当系统内存不足时候,系统会从优先级低的进程开始回收,下面根据优先级由高到低列出Android中几种进程。安全

  • 前台进程,当前用户操做所须要的进程网络

    • 用户正在交互的Activity(Activity执行了onResume方法)
    • 与正在交互的Activity绑定的Service
    • 设置为前台权限的Service(Service调用startForeground()方法)
    • 正在执行某些生命周期回调的Service,onCreate()、onStart()、onDestroy()
    • 正在执行onReceive()的BroadcastReceiver

    这种进程基本不会被回收,只有当内存不足以支持前台进程同时运行时候,系统才回回收它们,主要关注前三个。app

  • 可见进程,没有与用户交互所必须的组件,可是在屏幕上仍然可见其内容的进程ide

    • 调用了onPause()方法但仍对用户可见的Activity
    • 与上面这种Activity绑定的Service
  • 服务进程,使用startService()启动的Service且不属于上面两种类别进程的进程,虽然这个进程与用户交互没有直接关系,可是通常会在后台执行一些耗时操做,因此,只有当内存不足以维持全部前台进程和可见进程同时运行,系统才回回收这个类别的进程。
  • 后台进程,对用户不可见的Activity进程,已调用了onStop()方法的Activity
  • 空进程,不包含任何活动应用组件的进程,保留这种进程惟一目的是做为缓存,缩短引用组件下次启动时间。一般系统会最优先回收这类进程。

此外,一个进程的级别可能会由于其余进程对它的依赖而有所提升,即进程A服务于进程B(B依赖A),那么A的进程级别至少是和B同样高的。oop

3. Service配置

和其余组件(Activity/ContentProvider/BroadcastReceiver)同样,Service须要在Androidmanifest.xml中声明。ui

<manifest ... >
  ...
  <application ... >
      <service android:name=".DemoService" />
      ...
  </application>
</manifest>

Service是运行在主线程中的,若是有什么耗时的操做,建议新建子线程去处理,避免阻塞主线程,下降ANR的风险。this

       在另一篇文章中Intent以及IntentFilter详解提到过,为了确保应用的安全,不要为Service设置intent-filter,由于若是使用隐式Intent去启动Service时候,手机里面那么多应用,并不能肯定哪个Service响应了这个Intent,因此在项目中尽可能使用显式Intent去启动Service。在Android 5.0(API LEVEL 21)版本后的,若是传入隐式Intent去调用bindService()方法,系统会抛出异常。

能够经过设置android:exported=false来确保这个Service仅能在本应用中使用。

4. 服务启动方式

服务能够由其余组件启动,并且若是用户切换到其余应用,这个服务可能会继续在后台执行。到目前为止,Android中Service总共有三种启动方式。

  • Scheduled,可定时执行的Service,是Android 5.0(API LEVEL 21)版本中新添加的一个Service,名为JobService,继承Service类,使用JobScheduler类调度它而且设置JobService运行的一些配置。具体文档能够参考JobScheduler,若是你的应用最低支持版本是21,官方建议使用JobService。
  • Started,经过startService()启动的Service。经过这种方式启动的Service会独立的运行在后台,即便启动它的组件已经销毁了。例如Activity A使用startService()启动了Service B,过了会儿,Activity A执行onDestroy()被销毁了,若是Service B任务没有执行完毕,它仍然会在后台执行。这种启动方式启动的Service须要主动调用StopService()中止服务。
  • Bound,经过bindService()启动的Service。经过这种方式启动Service时候,会返回一个客户端交互接口,用户能够经过这个接口与服务进行交互,若是这个服务是在另外一个进程中,那么就实现了进程间通讯,也就是Messenger和AIDL,这个会是下篇文章的重点。多个组件能够同时绑定同一个Service,若是全部的组件都调用unbindService()解绑后,Service会被销毁。

startService和bindService能够同时使用

5. 主要方法

Service是一个抽象类,使用须要咱们去实现它的抽象方法onBind(),Service有且仅有这一个抽象方法,还有一些其余的生命周期回调方法须要复写帮助咱们实现具体的功能。

  • onCreate(),在建立服务时候,能够在这个方法中执行一些的初始化操做,它在onStartCommand()onBind()以前被调用。若是服务已经存在,调用startService()启动服务时候这个方法不会调用,只会调用onStartCommand()方法。
  • onStartCommand(),其余组件经过startService()启动服务时候会回调这个方法,这个方法执行后,服务会启动被在后台运行,须要调用stopSelf()或者stopService()中止服务。
  • onBind(),其余组件经过bindService()绑定服务时候会回调的方法,这是Service的一个抽象方法,若是客户端须要与服务交互,须要在这个方法中返回一个IBinder实现类实例化对象,若是不想其余客户端与服务绑定,直接返回null。
  • onDestroy(),当服务不在仍是用且即将被销毁时,会回调这个方法,能够在这个方法中作一些释放资源操做,这是服务生命周期的最后一个回调。

若是组件仅经过startService()启动服务,不论服务是否已经启动,都会回调onStartCommand()方法,并且服务会一直运行,须要调用stopSelfstopService方法关闭服务。

若是组件仅经过bindService()绑定服务,则服务只有在与组件绑定时候运行,一旦全部的客户端所有取消绑定unbindService,系统才会销毁该服务。

屡次启动同一个服务,只有在服务初次启动时候会回调onCreate方法,可是每次都会回调onStartCommand,能够利用这个向服务传递一些信息。

onStartCommand()的回调是在UI主线程,若是有什么耗时的操做,建议新启线程去处理。

6. 启动和关闭服务

启动服务:

  • JobScheduler.schedule()
  • startService(Intent)
  • bindService(Intent service, ServiceConnection conn, int flags)

关闭服务:

  • JobScheduler.cancel()或者JobScheduler.cancelAll(),对应JobScheduler.schedule()
  • Service自身的stopSelf()方法,组件的stopService(Intent)方法,对应startService启动方法
  • unbindService(ServiceConnection conn),对应bindService

示例:

// 启动服务
Intent intent = new Intent(this, DemoService.class);
startService(intent);

// 中止服务
stopService(intent)

// 绑定服务
ServiceConnection mConnection = ServiceConnection() { ... };
Intent intent = new Intent(this, DemoService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

// 解除绑定
unbindService(mConnection);

绑定服务bindService()第三个参数数值:

  • 0,若是不想设置任何值,就设置成0
  • Context.BIND_AUTO_CREATE,绑定服务时候,若是服务还没有建立,服务会自动建立,在API LEVEL 14之前的版本不支持这个标志,使用Context.BIND_WAIVE_PRIORITY能够达到一样效果
  • Context.BIND_DEBUG_UNBIND,一般用于Debug,在unbindService时候,会将服务信息保存并打印出来,这个标记很容易形成内存泄漏。
  • Context.BIND_NOT_FOREGROUND,不会将被绑定的服务提高到前台优先级,可是这个服务也至少会和客户端在内存中优先级是相同的。
  • Context.BIND_ABOVE_CLIENT,设置服务的进程优先级高于客户端的优先级,只有当须要服务晚于客户端被销毁这种状况才这样设置。
  • Context.BIND_ALLOW_OOM_MANAGEMENT,保持服务受默认的服务管理器管理,当内存不足时候,会销毁服务
  • Context.BIND_WAIVE_PRIORITY,不会影响服务的进程优先级,像通用的应用进程同样将服务放在一个LRU表中
  • Context.BIND_IMPORTANT,标识服务对客户端是很是重要的,会将服务提高至前台进程优先级,一般状况下,即时客户端是前台优先级,服务最多也只能被提高至可见进程优先级,
  • BIND_ADJUST_WITH_ACTIVITY,若是客户端是Activity,服务优先级的提升取决于Activity的进程优先级,使用这个标识后,会无视其余标识。

7. onStartCommand()返回值

onStartCommand()方法有一个int的返回值,这个返回值标识服务关闭后系统的后续操做。

返回值有如下几种:

  • Service.START_STICKY,启动后的服务被杀死,系统会自动重建服务并调用on onStartCommand(),可是不会传入最后一个Intent(Service可能屡次执行onStartCommand),会传入一个空的Intent,使用这个标记要注意对Intent的判空处理。这个标记适用于太依靠外界数据Intent,在特定的时间,有明确的启动和关闭的服务,例如后台运行的音乐播放。
  • Service.START_NOT_STICKY,启动后的服务被杀死,系统不会自动从新建立服务。这个标记是最安全的,适用于依赖外界数据Intent的服务,须要彻底执行的服务。
  • Service.START_REDELIVER_INTENT,启动后的服务被杀死,系统会从新建立服务并调用onStartCommand(),同时会传入最后一个Intent。这个标记适用于可恢复继续执行的任务,好比说下载文件。
  • Service.START_STICKY_COMPATIBILITY,启动后的服务被杀死,不能保证系统必定会从新建立Service。

8. Service生命周期

Service生命周期(从建立到销毁)跟它被启动的方式有关系,这里只介绍startServicebindService两种启动方法时候Service的生命周期。

  • startService启动方式,其余组件用这种方式启动服务,服务会在后台一直运行,只有服务调用自己的stopSelf()方法或者其余组件调用stopService()才能中止服务。
  • bindService启动方式,其余组件用这种方法绑定服务,服务经过IBinder与客户端通讯,客户端经过unbindService接触对服务的绑定,当没有客户端绑定到服务,服务会被系统销毁。

这两种生命周期不是独立的,组件能够同时用startService启动服务同时用bindService绑定服务,例如跨页面的音乐播放器,就能够在多个页面同时绑定同一个服务,这种状况下须要调用stopService()或者服务自己的stopSelf()而且没有客户端绑定到服务,服务才会被销毁。

<div align="center">
图-1Service生命周期图</div>

左图是使用startService()所建立的服务的生命周期,右图是使用bindService()所建立的服务的生命周期。

9. 在前台运行服务

服务能够经过startForeground来使服务变成前台优先级。

public final void startForeground(int id, Notification notification) {
    try {
        mActivityManager.setServiceForeground(
                new ComponentName(this, mClassName), mToken, id,
                notification, true);
    } catch (RemoteException ex) {
    }
}

第一个参数用于标识你应用中惟一的通知标识id,不能设为0,最终会传入NotificationManager.notify(int id, Notification notification)取消通知须要用到,第二个参数是通知具体内容。

前台服务须要在状态栏中添加通知,例如,将音乐播放器的服务设置为前台服务,状态栏通知显示正在播放的歌曲,并容许其余组件与其交互。

// 设置Notification属性
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

要将服务从前台移除,须要调用stopForeground(boolean removeNotification),参数是一个布尔值,用来标识服务从前台服务移除时候,是否须要移除状态栏的通知。若是服务在前台运行时候被中止,状态栏的通知也会被移除。

10. 与服务通讯

10.1 广播

很少说,万能的通讯。

10.2 本地数据共享

很少说,万能的通讯,例如ContentProvider/SharePreference等等。

10.3 startService()

       使用这个方法启动的服务,再次调用startService()传入Intent便可与服务通讯,由于这种方式启动的服务在完整的生命周期内onCreate()只会执行一次,而onStartCommand()会执行屡次,咱们再次调用startService()时候,能够在oonStartCommand()去处理。

10.4 bindService()

使用这种方法启动的服务,组件有三种与服务通讯的方式。

  • Service中实现IBinder
  • Messenger(AIDL的简化版)
  • AIDL

下一篇文章具体介绍Messenger、AIDL,由于它们是属于Android进程间通讯。

若是一个服务Service只须要在本应用的进程中使用,不提供给其余进程,推荐使用第一种方法。

使用示例:

Service:

/**
 * 本地服务
 * <br/>
 * 和启动应用属于同一进程
 */
public class LocalService extends Service {
    /**
     * 自定的IBinder
     */
    private final IBinder mBinder = new LocalBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 提供给客户端的方法
     *
     * @return
     */
    public String getServiceInfo() {
        return this.getPackageName() + " " + this.getClass().getSimpleName();
    }

    /**
     * 自定义的IBinder
     */
    public class LocalBinder extends Binder {
        public LocalService getService() {
            return LocalService.this;
        }
    }
}

Activity:

/**
 * 绑定本地服务的组件
 *
 * Created by KyoWang.
 */
public class BindLocalServiceActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mShowServiceNameBtn;

    private LocalService mService;

    private boolean mBound = false;

    public ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LocalService.LocalBinder binder = (LocalService.LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBound = false;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.a_bind_local_service);
        mShowServiceNameBtn = (Button) findViewById(R.id.bind_local_service_btn);
        mShowServiceNameBtn.setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if(id == R.id.bind_local_service_btn) {
            if(mBound) {
                String info = mService.getServiceInfo();
                Toast.makeText(BindLocalServiceActivity.this, info, Toast.LENGTH_SHORT).show();
            }
        }
    }
}

11. 服务长存后台

关于网上通用的提高服务优先级以保证服务长存后台,即保证服务不轻易被系统杀死的方法有如下几种。

  • 设置android:persistent="true",这是application的一个属性,官方都不建议使用。

    Whether or not the application should remain running at all times,
    "true" if it should, and "false" if not. 
    The default value is "false". 
    Applications should not normally set this flag; 
    persistence mode is intended only for certain system applications.
  • 设置android:priority优先级,这个并非Service的属性。这个属性是在intent-filter中设置的。官方解释,这个属性只对活动和广播有用,并且这个是接受Intent的优先级,并非在内存中的优先级,呵呵。

    android:priority
    The priority that should be given to the parent component with regard to 
    handling intents of the type described by the filter. 
    This attribute has meaning for both activities and broadcast receivers。
  • 在Service的onDestroy中发送广播,而后重启服务,就目前我知道的,会出现Service的onDestroy不调用的状况。
  • startForeground,这个上面提到过,是经过Notification提高优先级。
  • 设置onStartCommand()返回值,让服务被杀死后,系统从新建立服务,上面提到过。

五个里面就两个能稍微有点用,因此啊,网络谣传害死人。

12. IntentService

敲黑板时间,重点来了,官方强力推荐。

前面提到两点。

  • 由于Service中几个方法的回调都是在主线程中,若是使用Service执行特别耗时的操做,建议单独新建线程去操做,避免阻塞主线程(UI线程)
  • 启动服务和中止服务是成对出现的,须要手动中止服务

       IntentService完美的帮咱们解决了这个问题,在内部帮咱们新建的线程,不须要咱们手动新建,执行完毕任务后会自动关闭。IntentService也是一个抽象类,里面有一个onHandleIntent(Intent intent)抽象方法,这个方法是在非UI线程调用的,在这里执行耗时的操做。

       IntentService使用非UI线程逐一处理全部的启动需求,它在内部使用Handler,将全部的请求放入队列中,依次处理,关于Handler能够看这篇文章,也就是说IntentService不能同时处理多个请求,若是不要求服务同时处理多个请求,能够考虑使用IntentService。

IntentService在内部使用HandlerThread配合Handler来处理耗时操做。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}

public int onStartCommand(Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

public void onStart(Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

       注意msg.arg1它是请求的惟一标识,每发送一个请求,会生成一个惟一标识,而后将请求放入Handler处理队列中,从源代码里面能够看见,在执行完毕onHandleIntent方法后,会执行stopSelf来关闭自己,同时IntentService中onBind()方法默认返回null,这说明启动IntetService的方式最好是用startService方法,这样在服务执行完毕后才会自动中止;若是使用bindService来启动服务,仍是须要调用unbindService来解绑服务的,也须要复写onBind()方法。

小盆宇:在ServiceHandler类的handleMessage方法中,执行onHandleIntent后紧接着执行stopSelf(int startId),把服务就给中止了,那第一个请求执行完毕服务就中止了,后续的请求怎么会执行?

       注意stopSelf(int startID)方法做用是在其参数startId跟最后启动该service时生成的id相等时才会执行中止服务,当有多个请求时候,若是发现当前请求的startId不是最后一个请求的id,那么不会中止服务,因此只有当最后一个请求执行完毕后,才会中止服务。

相关文章
相关标签/搜索