什么是Service?
Service作为构建应用的四大组件之一,它用来运行长时间的后台操作且不使用用户界面,比如网络事务处理、文件I/O的读写、音乐的播放。但是你仔细考虑这个定义可能会产生一个疑问:在Java中并没有提供所谓的Service组件,一样要完成这些功能,为什么不直接在其它组件,如Activity中直接添加这些后台代码呢?
这就要提到Service的其它的特性:
1. 服务可以由其它组件启动,如Activity,在该活动销毁或者应用关闭的情况,服务依旧可以在后台运行。而如果在Activity开启一个子线程来处理这些后台代码,那么在该活动销毁后,所在进程变为空进程,系统在内存不足时会优先终止该进程,该进程中所有子线程就会被终止。假如你在一个app中下载文件,在关闭该app时候,如果下载任务关闭,你可能不希望的这样,更多是在手机状态栏下开启一个独立的下载任务,即使在该app关闭后,下载工作依旧继续;再以音乐播放器为例,你会发现通过back键退出应用时,音乐播放并没有停止,也就是说并不是真正的关闭程序,而这正是Service组件在后台运行的效果,虽然这种方式很容易实现一些隐蔽性的工作,但是通过良好的代码设计,能够很好的避免这些问题,而且实际开发中它确实有很多应用场景。
2. Android的应用框架鼓励共享应用的功能及数据,而组件是实现该目标的重要载体,服务作为一个组件,可以被其它组件启动或者是相互通讯,甚至是跨应用组件之间的通信;而如果是在Activity中定义子线程的方式,其它组件很难获取到它的实例并且控制它,特别是在创建该子线程的组件被销毁的情况,无法再获取它的实例。而Service却可以做到,比如音乐播放器就首先开启一个后台播放的功能,然后其它组件可以绑定到该实例,并且与之通讯,实现播放功能的控制。
当然你可能会想既然Service有怎么多的优点,那么以后所有多进程的工作都通过Service组件去处理,这有点矫枉过正,你需要自己去判断需要执行的工作是否需要独立在后台运行或实现组件通信,如果只需要在该组件运行期间才进行,就可以在该组件中使用多线程,这样该组件销毁时下载中止或者停止音乐播放。
Service的误区.
服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。 这意味着,如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。
Service分为两种形式:
- 启动服务
当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方。 - 绑定服务
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。 仅当与另一个应用组件绑定时,绑定服务才会运行。 多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
Service的基本用法
- 创建启动服务
和Activity类似,要创建服务,必须创建 Service 的子类(或使用它的一个现有子类)。在实现中,您需要重写一些回调方法,包括onCreate()、onStartCommand()、onDestroy(),代码示例如下:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy() executed");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在回调方法中添加了一条打印语句,另外它们都调用了超类实现,这其实不是硬性规定,不同于Activity,继承Service可以不调用超类实现,因为以上代码是Android Studio自动生成的,代码也可以如下编写:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public void onCreate() {
Log.d(TAG, "onCreate() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
return START_STICKY;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy() executed");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
其实你查看Service类的源码,发现onCreate()和onDestroy()中没有任何执行代码,所以super.onCreate()和super.onDestroy()是可有可无的,而onStartCommmand()方法需要返回一个整型数值,Service类中提供了几个常量,分别表示了不同含义,后面具体介绍,所以你可以调用父类的方法返回一个常量值,也可以自己直接返回一个常量,比如上面代码的中Service.START_STICKY。而onBind()虽然不是启动服务时的回调方法,但是它是一个抽象方法(Service是一个抽象类),所以必须提供方法体的实现,如果没有具体实现,可以返回null。
方法介绍:
onCreate()
首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。onStartCommand()
当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止服务。onBind()
当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null。onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。
接着在AndroidManifest.xml中声明该服务组件,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication" >
<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"></service>
</application>
</manifest>
启动服务:您可以通过将 Intent(指定要启动的服务)传递给 startService(),从 Activity 或其他应用组件启动服务。Android 系统调用服务的 onStartCommand() 方法,并向其传递 Intent。
打开布局文件activity_main.xml,添加两个按钮分别于启动服务、停止服务,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start MyService" />
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop MyService" />
</LinearLayout>
在活动文件MainActivity.class中分别为两个按钮注册监听事件,分别执行启动服务和停止服务操作,代码如下:
package com.example.myapplication;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
private Button start;
private Button stop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.start);
stop = (Button) findViewById(R.id.stop);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
startService(intent);
}
});
stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
stopService(intent);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy() executed");
}
}
运行程序,点击Start MyService按钮,可以看到LogCat的打印日志如下:
它会执行onCreate()、onStartCommand()方法,如果再多次点击Start MyService按钮,结果如下:
它不会再执行onCreate(),因为通过第一次的启动,服务已经处于运行状态,所以以后每次执行startService()都只回调onStartCommand()方法。
停止服务:启动服务必须管理自己的生命周期。也就是说,除非系统必须回收内存资源,否则系统不会停止或销毁服务,而且服务在 onStartCommand() 返回后会继续运行。因此,服务必须通过调用 stopSelf() 自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。这里我们通过点击Stop MyService按钮来停止服务,Logcat的打印日志如下:
执行了onDestory()表示服务销毁。接着我们测试在关闭MainActivity活动时,服务是否会销毁:首先再次点击Start MyService按钮启动服务,按back键退出MainActivity活动,查看Logcat的打印日志如下:
你可能会看到“onDestroy() executed”字样,但是你要注意到前面的TAG是“MainActivity”,而不是“MyService”,它表示MainActivity已经被销毁了(这部分代码在上面MainActivity.class末尾部分),但是MyService并没有执行onDestroy()方法,也就表示它可以在启动它的活动被销毁的情况下继续运行,所以我们应该主动停止服务。
但是在实际开发过程中,我们不会编写上面的代码,因为上面的服务是在主线程(也就是所谓的UI线程)中运行,而服务往往需要执行长时间的操作,这会导致主线程的阻塞,应用出现ANR(应用无响应)错误,测试代码如下:
在MainActivity.class的onCreate()方法中添加一行代码,打印当前线程的id:
Log.d(TAG, "MainActivity Thread id is " + Thread.currentThread().getId());
在MyService.class的onCreate()方法也添加一行代码,打印当前线程的id:
Log.d(TAG, "MyService Thread id is " + Thread.currentThread().getId());
重新运行程序,并点击Start MyService按钮,Logcat打印日志如下:
结果表示它们在同一个线程。
在MyService的onStartCommand()添加一行,让当前线程睡20S,代码如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return START_STICKY;
}
重复以上操作,结果如下:
页面出现ANR错误,所以一般都需要在onStartCommand中开启子线程来处理,代码格式如下:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
/*
* 实际的业务操作,比如上传下载等
*/
stopSelf();//主动关闭服务,而不是通过stopService()的方式
}
}).start();
return START_STICKY;
}
但是你可能会提出的一个疑问:既然这类代码的编写都有固定的格式,能不能提供一个类,封装这些基本操作,减少编写的重复性,Android很好的考虑到这一点,它提供一个IntentService类:这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果不要求服务同时处理多个请求,这是最好的选择。 只需实现 onHandleIntent() 方法即可,该方法会接收每个启动请求的 Intent,使你能够执行后台工作。它封装了如下操作:
- 提供 onStartCommand() 的默认实现。
- 提供 onBind() 的默认实现(返回 null)。
- 创建工作队列,通过onStartCommand()方法接收到所有客户端发送的Intent对象。
- 创建单独的工作线程来执行onHandleIntent()方法,并处理工作队列逐一发送的Intent对象,所以开发者无须处理多线程问题。
- 在处理完所有启动请求后停止服务,因此开发者永远不必调用 stopSelf()。
综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,您还需要为服务提供小型构造函数。),代码示例如下:
package com.example.myapplication;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
public static final String TAG = "MyIntentService";
//必须提供一个构造函数,调用父类IntentService(String)构造方法,该字符串用来标识创建的线程名称
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
//打印线程的id
Log.d(TAG, "MyIntentService Thread id is " + Thread.currentThread().getId());
try {
Thread.sleep(20*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "Finished");
}
}
按照上面普通Service的操作方式,包括注册服务、启动服务,结果如下:
程序没有出现ANR错误,因为它默认创建新的线程来执行onHandleIntent()方法。
需要说明的是,这与上面普通Service开启多线程的方式还是有所区别,它是通过异步消息机制来处理每一个Intent请求,同一时刻只能有一个Intent请求会被处理;而在上面普通Service中,则每次Intent请求都会创建一个线程来处理,是同时处理多个请求,但是这种多线程情况处理可能比较危险,具体可以查看Service源码和Google API Guide。
onStartCommand()的返回值
onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务,从 onStartCommand() 返回的值必须是以下常量之一:
START_NOT_STICKY
如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。START_STICKY
如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
上面的内容来于官网的摘录,但是关于里面的终止服务是什么意思?首先它不是指正常stopService()和stopSelf()方法来中止服务。而是一些非正常的方式终止服务,比如服务有可能在内存过低的情况被Android系统强制停止并供有用户焦点的Activity使用,因为它跟用户的联系更加直接和密切,所以它的优先级是最高的。而服务之间的优先级分别是:前台服务>绑定服务>启动服务,前台服务几乎永不终止,关于前台服务和绑定服务后面介绍。但是终止的服务,一旦资源变得再次可用,系统就有可能会重启服务,而是否启动及启动后的动作就取决于onStartCommand() 返回的值。而有什么方式可以测试这种行为呢,我发现在Overview Screen,即查看最近打开程序的界面,如下图:
通过关闭该任务界面时,可以触发以上行为。
关于MainActivity的代码如下:
package com.example.myapplication;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
public static final String TAG = "MainActivity";
private Button start;
private Button stop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.start);
stop = (Button) findViewById(R.id.stop);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
//通过intent传递一个字符串数据
intent.putExtra("data","This is data");
startService(intent);
}
});
stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
stopService(intent);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG,"onDestroy() executed");
}
}
相对于前面的代码只添加了一行代码,通过Intent的putExtra()方法来传递一个字符串信息。
而MyService的代码如下:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
public static final String TAG = "MyService";
@Override
public void onCreate() {
Log.d(TAG, "onCreate() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
//读取intent中的数据
String data = intent.getStringExtra("data");
Log.d(TAG, data);
//返回值设置为START_NOT_STICKY
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy() executed");
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
在MyService中添加两行代码,读取信息,并打印出来。
启动程序,点击Start MyService按钮,然后打开Overview Screen,通过右上角的“x”关闭该应用的页面,LogCat的打印日志如下:
这似乎没有任何特别的地方,实际也上确实如此,但是你如果把MyService中onStartCommand()的返回值改为“START_STICKY“,重复以上操作,LogCat的打印日志如下:
并且手机弹出如下页面:
把值该为”START_REDELIVER_INTENT“,重复以上操作,注意:在Overview Screen中关闭该应用页面后需要等待一会,LogCat的打印日志如下:
我想你已经发现问题了:在第二张截图和第四张截图中,MyService又执行了onCreate()和onStartCommand()方法,这表示MyService服务又重新创建了,而从第一张截图中可知它并没有重启,如果你再对照上面的关于不同返回值的含义就能清楚其中的道理。另外,通过模拟器或者是手机你可以查看应用的进程是否关闭:
个人理解:首先在Overview Screen中关闭应用的页面,会关闭应用所属的进程,从而导致寄宿在该进程上的服务也会被关闭,但是Android系统内存足够,所以会根据onStartCommand的返回值来判断是否需要重启Service,如为START_NOT_STICKY,在没有挂起Intent要传递的情况,它是不会重建服务的,所谓的挂起,是指如果由队列来接收和分发Intent时,队列还存在没有分发的Intent时,服务就被系统终止了。而上面的代码只发送了一个Intent对象,且已经处理结束,所以它并不会重建服务。而值为START_STICKY和START_REDELIVER_INTENT,则事先不管是否存在挂起的Intent,都会重建服务,它们的区别就是在没有挂起的Intent时,前者会执行onStartCommand()方法,但把空Intent传递给方法,后者会把最后一次的Intent传递给方法。如有误,希望大神指点。
启动服务的反馈方式
服务可以在后台默默的运行而不打扰用户,但是你既可以把它看作优点也可以看作缺点,很多手机用户都会以为关闭应用就是关闭了一切,如果让他了解到服务的机制,以后会对安装一个应用显得很谨慎。而很多软件正是利用了服务的特点,在后台偷偷开启了很多用户难以察觉的程序,比如与服务器建立永久连接,实时检测软件版本,一旦版本更新,推送通知给用户。
虽然服务的运行不需要用户界面,但是有时候我们为了服务在内存不足的情况下,不被系统终止或者主动让用户意识到服务的存在,会让服务成为前台服务。前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。就比如常见的音乐播放、天气预报、文件下载,它们通过状态栏区域,以通知的形式让用户时刻感受到该程序在运行,而且相对于打开应用,通过状态栏来打开应用似乎更加便捷,最重要的是你并不希望音乐突然停止播放。
除了这种重量级的方式,我们可能会需要在后台服务进行到某个节点,发送信息给用户,这是可以使用 Toast 通知或状态栏通知来通知用户所发生的事件。
Toast 通知是指出现在当前窗口的表面、片刻随即消失不见的消息,而状态栏通知则在状态栏提供内含消息的图标,用户可以选择该图标来采取操作(例如启动 Activity)。
通常,当某些后台工作已经完成(例如文件下载完成)且用户现在可以对其进行操作时,状态栏通知是最佳方法。 当用户从展开视图中选定通知时,通知即可启动 Activity(例如查看已下载的文件)。
前台服务与通知的区别就是:一个是实时性的,另一个是临时性,虽然它们在外形来说,是完全一样的,都是通知的形式,但是前台服务所关联的通知,它的生命周期是与服务休戚与共的,而通知发出后,就是任人宰割的单身汉。
第三种情况就是当一个活动启动服务后,服务是无法直接返回数据给活动的,如果需要在服务进行了某个操作后,能够反应到活动界面上怎么去实现,这就需要通过广播机制来实现了,可以在服务中发送一个广播,在活动中动态注册一个广播接收器,接受到服务发送的广播即对用户界面进行更新。
下面分别以代码的形式,演示这三种反馈方式:
1、 通知的方式.
在MyService中修该的代码如下
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopSelf();
}
}).start();
return START_STICKY;
}
@Override
public void onDestroy() {
Toast.makeText(this, "Download is complete", Toast.LENGTH_SHORT).show();
Log.d(TAG, "onDestroy() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopSelf();
}
}).start();
return START_STICKY;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onDestroy() {
//获取通知管理器对象,它属于系统服务
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//通过构建器来创建通知对象,构建器原理见Effective Java第2条.
Notification.Builder builder = new Notification.Builder(this);
builder.setTicker("有新消息");
builder.setContentTitle("下载通知");
builder.setContentText("下载任务已完成,请查看!");
builder.setSmallIcon(R.mipmap.ic_launcher);
//把声音、震动、闪光设置为默认
builder.setDefaults(Notification.DEFAULT_ALL);
//通过pendingIntent设置通知的打开动作
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
builder.setContentIntent(pi);
//设置打开通知后,通知自动消失
builder.setAutoCancel(true);
//创建通知对象
Notification notification = builder.build();
//通过系统通知服务发送自定义通知
manager.notify(1, notification);
Log.d(TAG, "onDestroy() executed");
}
运行程序,点击Start MyService按钮,系统在2s后,分别以Toast和通知的形式发送信息给手机。
2、前台服务
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onCreate() {
//通过构建器来创建通知对象,构建器原理见Effective Java第2条.
Notification.Builder builder = new Notification.Builder(this);
builder.setTicker("有新消息");
builder.setContentTitle("下载通知");
builder.setContentText("下载任务已完成,请查看!");
builder.setSmallIcon(R.mipmap.ic_launcher);
//把声音、震动、闪光设置为默认
builder.setDefaults(Notification.DEFAULT_ALL);
//通过pendingIntent设置通知的打开动作
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
builder.setContentIntent(pi);
//创建通知对象
Notification notification = builder.build();
startForeground(1,notification);
}
在onCreate()方法中,首先创建通知对象,但是与发送通知不同,前台服务代码中并没有获取系统通知管理服务,而是通过最后一个方法startForeground(id,notification)来把当前服务对象与通知绑定到一起,它与通知的最大区别就是在服务终止后,系统会关闭该通知,而通知则是独立的,没有关联性的。
3、广播的方式
MyService代码如下:
package com.example.myapplication;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public static final String UPDATE_ACTION = "com.example.myapplication.action.UPDATE_ACTION";
@Override
public void onCreate() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopSelf();
}
}).start();
return START_STICKY;
}
@Override
public void onDestroy() {
Intent intent = new Intent();
intent.setAction(UPDATE_ACTION);
intent.putExtra("isCompleted",true);
//发送广播
sendBroadcast(intent);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
activity_main.xml代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/start"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start MyService"/>
<TextView
android:id="@+id/text_view"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
MainActivity代码如下:
package com.example.myapplication;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
private Button start;
private TextView textView;
private MyReceiver receiver;
private IntentFilter filter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.start);
start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,MyService.class);
startService(intent);
}
});
textView = (TextView) findViewById(R.id.text_view);
//动态注册广播接收器
receiver = new MyReceiver();
filter = new IntentFilter();
filter.addAction(MyService.UPDATE_ACTION);
registerReceiver(receiver, filter);
}
//广播接收器必须要定义在Activity内部:利用内部类可以访问外部类属性和方法的特性。
class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
boolean isCompleted = intent.getBooleanExtra("isCompleted",false);
if(isCompleted){
textView.setText("下载已完成!");
}
}
}
@Override
protected void onDestroy() {
//在活动关闭时,注销广播接收器
unregisterReceiver(receiver);
super.onDestroy();
}
}
运行程序,点击Start MyService按钮,大约过了5S以后,界面下方空白处显示“下载已完成”,如下图:
参考内容:
1. Google API Guide - Services
2. 郭霖:Android Service完全解析,关于服务你所需知道的一切(上)
3. Android 中的 Service 全面总结
文章内容主要来源于对官方文档的梳理,第三条是在该文完成以后才看到,但是个人认为该文章全面又详细,且几年前就总结出来的文章,非常值得学习,所以在此做个标记。