Service 组件
- 可在后台执行长时间运行操作而不提供界面的应用组件
- 用户切换到其他应用Service也在后台运行
- 不能主动运行,需要调用方法来运行Service
创建
- 创建继承Service类的类
public class MyService1 extends Service {
private static final String TAG = MyService1.class.getName();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
}
- 在Mainifast中注册
<service android:name=".MyService1"/>
类别
-
Context.startService() -
Context.bindService()
不同启动方式对应不同生命周期
startService(Intent)
startService启动的服务默认无限期执行(可以通过Context的stopService或Service的stopSelf方法停止运行)
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
startService(intent);
如服务未被创建,会调用onCreate和OnStartCommand两个生命周期函数。若服务已被创建,则只调用onStartCommand
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
stopService(intent);
停止服务时,调用onDestroy生命周期函数,服务被销毁。
bindService(Intent service, ServiceConnection conn, int flag)
bindService启动的服务在调用者和服务之间是典型的client-server的接口,即调用者是客户端,service是服务端,service就一个,但是连接绑定到service上面的客户端client可以是一个或多个。
这里特别要说明的是,这里所提到的client指的是组件,比如某个Activity。
当client销毁的时候,client会自动与Service解除绑定,当然client也可以通过明确调用Context的unbindService方法与Service解除绑定。
当没有任何client与Service绑定的时候,Service会自行销毁
若有client与Service绑定,则无法使用stopService销毁
Intent intent = new Intent();
intent.setClass(this, MyService1.class);
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
bindService(intent, connection, BIND_AUTO_CREATE);
unbindService(connection);
使用Service组件模拟下载app
1.新建项目并创建MyService类继承自Service类
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return null;
}
@Override
public void onCreate() {
Log.i(TAG, "onCreate: ");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy: ");
super.onDestroy();
}
}
这里一开始最好在每个重要的周期函数中都添加一个Log.i打印语句,测试运行Service服务的各个生命周期是否正常。
2.在MainAcitvity的onCreate中调用bindService绑定服务
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
localBinder = (MyService.LocalBinder) service;
Log.i(TAG, "onServiceConnected: ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
bindService(bind_intent, connection, BIND_AUTO_CREATE);
3.在activity_main.xml布局中添加两个按钮,分别为开始下载和取消下载,注意自定义的id和onClick点击事件函数名
<Button
android:id="@+id/DownloadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="50dp"
android:layout_marginBottom="20dp"
android:layout_gravity="center_horizontal"
android:onClick="StartDownload"
android:text="开始下载" />
<Button
android:id="@+id/CancelDownloadButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="CancelDownload"
android:text="取消下载" />
4.在MainAcitivity.java中写入这两个函数
由于下载的程序是存放在Service中的,所以我们在点击事件StartDownload()中调用startService(),然后在Service的onStartCommand生命周期函数中执行下载的线程即可。
public void StartDownload(View view) {
Intent intent = new Intent();
intent.setClass(this, MyService.class);
startService(intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
myThread = new MyThread();
myThread.start();
return super.onStartCommand(intent, flags, startId);
}
这里使用的MyThread类继承自线程类Thread(在Thread重写run方法,并且通过pause变量实现线程的挂起和解挂)。并且这里吧MyThread类写入MyService类中即可(内部类)
public class MyThread extends Thread{
private final Object lock = new Object();
private boolean pause = false;
void pauseThread(){
pause = true;
}
void resumeThread(){
pause = false;
synchronized (lock){
lock.notify();
}
}
void onPause() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
super.run();
int index = 0;
setDownloadStatus(true);
ServiceSendStatusBroadCast();
try {
for(int i=0; i<30; i++){
while(pause) {
onPause();
}
TimeUnit.SECONDS.sleep(1);
Log.i(TAG, "run: "+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如果要取消下载,那么我们要使用Thread中的interrupt()方法将线程中断,但线程是存放在Service中的,没有办法通过按钮点击事件直接对线程进行中断。
所以我们用到了绑定服务中ServiceConnection的onServiceConnected方法(绑定Service时执行)。在绑定服务后,通过该方法中的IBinder类的参数service可以接收到Binder类实例(需要类型转换)。然后通过localBinder.getService()可以得到绑定的Service。
Intent bind_intent = new Intent();
bind_intent.setClass(this, MyService.class);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
localBinder = (MyService.LocalBinder) service;
Log.i(TAG, "onServiceConnected: ");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
bindService(bind_intent, connection, BIND_AUTO_CREATE);
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return new LocalBinder();
}
public class LocalBinder extends Binder{
public MyService getService(){
return MyService.this;
}
}
因此,通过该方法可以在按钮点击事件中对Service中的下载线程进行中断。
public void cancelDownload(){
if(myThread != null){
myThread.interrupt();
}
}
至此,运行APP,观察LogCat界面是否可以开始下载和取消下载。(取消下载时正常会抛出一个中断异常InterruptedException)
5.正在下载时设置开始下载按钮禁用,不在下载时设置取消下载按钮禁用
这个不难,但是需要结合之间学习的Android本地广播机制。
基本思路是:在Service中写入一个成员变量,用来表示是否在下载(不要在MainActivity中定义,否则app切入后台再切回可能会将该变量重置)。在点击开始下载(取消下载)按钮时根据下载状态改变该变量值,并通过发送本地广播LocalBroadcast通知MainActivity改变按钮状态(前面讲了,通过localBinder获取到MyService中的属性或方法)
public class MyService extends Service {
private boolean DownloadStatus = false;
public boolean getDownloadStatus() {
return DownloadStatus;
}
public void setDownloadStatus(boolean downloadStatus) {
DownloadStatus = downloadStatus;
}
public class MyThread extends Thread{
@Override
public void run() {
super.run();
int index = 0;
setDownloadStatus(true);
ServiceSendStatusBroadCast();
try {
for(int i=0; i<30; i++){
while(pause) {
onPause();
}
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
setDownloadStatus(false);
ServiceSendStatusBroadCast();
}
}
public void cancelDownload(){
if(myThread != null){
myThread.interrupt();
setDownloadStatus(false);
ServiceSendStatusBroadCast();
}
}
}
public class MainActivity extends AppCompatActivity {
public Button DownloadButton;
public Button CancelButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DownloadButton = (Button) findViewById(R.id.DownloadButton);
CancelButton = (Button) findViewById(R.id.CancelDownloadButton);
IntentFilter statusFilter = new IntentFilter();
statusFilter.addAction("DOWNLOAD_STATUS_CHANGED");
BroadcastReceiver statusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MonitorDownloadStatus(localBinder.getService().getDownloadStatus());
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(statusReceiver, statusFilter);
}
public void MonitorDownloadStatus(boolean status){
if(status){
DownloadButton.setEnabled(false);
CancelButton.setEnabled(true);
}else{
DownloadButton.setEnabled(true);
CancelButton.setEnabled(false);
}
}
}
运行app,观察LogCat打印信息是否符合预期(如果没有改变按钮可用状态,检查是否发送了本地广播,是否在每次下载状态改变后根据下载状态变量改变按钮状态)。
6.显示下载进度条
下载进度条和改变下载状态的思路一样,在每一秒(线程中的每一次for循环)通过本地广播发送一个int类型的进度。然后MainActivity接收到广播后根据这个进度值改变ProgressBar控件的值即可。
7.使用Java线程类Thread的挂起wait()和恢复notify()实现暂停和继续下载
调用MyThread类中的挂起和解挂两个方法。然后老规矩,在MainActivity中使用localBinder.getService()。
public class MyService extends Service {
public void pauseDownload(){
myThread.pauseThread();
setDownloadStatus(false);
ServiceSendStatusBroadCast();
}
public void resumeDownload(){
myThread.resumeThread();
setDownloadStatus(true);
ServiceSendStatusBroadCast();
}
}
public class MainActivity extends AppCompatActivity {
public void ContinueDownload(View view) {
localBinder.getService().resumeDownload();
}
public void SuspendDownload(View view) {
localBinder.getService().pauseDownload();
}
}
<Button
android:id="@+id/SuspendButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="50dp"
android:layout_marginBottom="20dp"
android:layout_gravity="center_horizontal"
android:onClick="SuspendDownload"
android:text="暂停下载" />
<Button
android:id="@+id/ContinueButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:onClick="ContinueDownload"
android:text="继续下载" />
别忘了在暂停下载和继续下载执行时根据下载状态变量改变按钮状态。
运行,一个模拟下载的app基本就完成了!
最后
需要源码此处请 Android-Service组件应用-模拟下载
|