Service概念
Service
是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
需要注意的是:
- Service 不是线程,它运行于其托管进程的main线程之上(Local or Remote)。
- 因为Service仍旧运行在main线程上,如果程序要进行某些耗时操作,则需另开线程,否则就有可能出现主线程阻塞,轻则影响用户体验,重则导致ANR(Application Not Responding)的产生。
Service or Thread?
单从Service的概念来看,似乎Service可以做的所有工作Thread也能做,那么Service和Thread到底该如何选择呢?
简单的说可以通过以下两个原则进行选择:
- 如果在用户与应用程序进行界面交互时需要在主线程之外执行后台工作,则可以选择使用线程(比如:处于Activity运行状态,需要加载一些大图片用于界面显示,页面BGM的播放等)
- 如果希望在Activity不可见时,应用程序仍旧可以在后台执行一些工作则最好使用Service来实现(比如:用户在Activity显示状态时开启了网络下载的工作并且希望在应用程序后台时下载工作仍然进行)。
可能有些人会想:使用线程也可以在应用程序后台时执行下载工作啊。
针对后台下载,现在来设想一下以下情况:
在Activity中开启下载线程之后,由于某些情况该Activity被销毁或重启了(配置发生改变、界面不可见后内存过低被系统回收),此时我们将不在持有该下载线程的引用!如果用户此时想要暂停下载,我们是没有办法对前面创建的下载线程执行该操作的!而且也不利于系统资源的管理。
使用Service还有其他几个好处:
- 如果在应用程序中启动了服务,则应用程序的优先级将被提高。仅当内存过低且必须回收系统资源时进程才会被杀死。
- 如果服务被系统杀死,我们可以通过指定onStartCommand的返回值的方式来促使系统在资源可用时重启服务。
Service的两种用法的不同
- 首先我们需要自定义一个Service类,代码如下
public class MyService extends Service {
public MyService() {
}
/**
* 该方法是Service类中的唯一一个abstract方法,必须重写
* 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。
* 在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。
* 请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//系统默认返回START_STICKY
//当服务被系统kill之后执行重启服务的操作
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
onCreate():当服务首次被使用时,该方法将会首先被调用。如果服务已经处于使用状态则该方法不会被调用。
onStartCommand():当其他组件通过startService()启动服务时,该方法将被调用(onCreate()之后)。可以通过该方法的返回值控制Service在被系统kill之后的重启行为:
START_NOT_STICKY:当Service被系统kill之后,将不会执行重启操作。
START_STICKY:当Service被系统kill之后,将会通过调用onStartCommand()来执行重启操作。但是传递给该方法的Intent将是一个空Intent。适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。
START_REDELIVER_INTENT:当Service被系统kill之后,将会通过调用onStartCommand()来执行重启操作。传递给该方法的Intent是最后一个传递给服务的Intent.这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。onBind():当其他组件通过bindService的方式绑定服务时,该方法将被调用。需要返回一个借口用来与其他组件之间进行通信。该方法必须重写,如果不希望Service通过绑定的形式被使用则返回null。
onDestroy():当服务被销毁时将调用此方法。
启动服务:如果仅通过该方式使用服务,则在希望销毁服务时只需调用一次stopService()或stopSelf()(无论调用多少次startService),服务自动进入销毁流程。
绑定服务:如果仅通过该方式使用服务,则在希望销毁服务时必须调用多次unBindService()(与调用bindService()次数相对应)之后,服务才会进入销毁流程。
启动+绑定:当通过该方式使用服务时,不能单纯的通过stop和unBind的方式停止服务。必须同时满足Service stop并且没有和任何组件bind的条件才会进入销毁流程。
通过bindService使用服务的简单实现代码:
MainActivity.java
package com.example.myservice;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private MyService.Download mDownload;
private ServiceConnection mConnection = new ServiceConnection() {
//iBinder与onBind()返回值对应
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mDownload = (MyService.Download)iBinder;
mDownload.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mDownload.stopDownload();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MyService.class);
bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
MyService.java
package com.example.myservice;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
public class MyService extends Service {
private final String TAG = "MyService";
private Download mDownload = new Download();
public MyService() {
}
class Download extends Binder {
private int mProgress = 0;
private boolean isRun;
private Thread downThread = new Thread(new Runnable() {
@Override
public void run() {
while (mProgress < 100 && isRun) {
SystemClock.sleep(50);
mProgress++;
Log.d(TAG,"down progress is:"+ mProgress);
}
}
});
public void startDownload() {
isRun = true;
downThread.start();
}
public void stopDownload(){
isRun = false;
}
public int getDownloadProgress(){
return mProgress;
}
}
/**
* 该方法是Service类中的唯一一个abstract方法,必须重写
* 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。
* 在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。
* 请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mDownload;
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//系统默认返回START_STICKY
//当服务被系统kill之后执行重启服务的操作
Log.d(TAG, "onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
super.onDestroy();
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myservice">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true"></service>
</application>
</manifest>