Android Service、IntentService,Service和组件间通信

Service组件

Service 和Activity 一样同为Android 的四大组件之一,并且他们都有各自的生命周期,要想掌握Service 的用法,那就要了解Service 的生命周期有哪些方法,并且生命周期中各个方法回调的时机和作用

什么是service?service的基本概念

Service是Android中实现程序后台运行的解决方案,非常适合用于去执行哪些不需要和用户交互而且还要求长期运行的任务。不能运行在一个独立的进程当中,而是依赖与创建服务时所在的应用程序进程。只能在后台运行,并且可以和其他组件进行交互。

Service可以在很多场合使用,比如播放多媒体的时候用户启动了其他Activity,此时要在后台继续播放;比如检测SD卡上文件的变化;比如在后台记录你的地理信息位置的改变等等,总之服务是藏在后台的。

定义(启动)一个Service

Service 有两种启动方式,并且它的两种启动方式的生命周期是不一样的。

1.startService方式启动Service

当应用组件通过startService方法来启动Service 时,Service 则会处于启动状态,一旦服务启动,它就会在后台无限期的运行,生命周期独立于启动它的组件,即使启动它的组件已经销毁了也不受任何影响,由于启动的服务长期运行在后台,这会大量消耗手机的电量,因此,我们应该在任务执行完成之后调用stopSelf()来停止服务,或者通过其他应用组件调用stopService 来停止服务。

startService 启动服务后,会执行如下生命周期:onCreate() -> onStartCommand() -> onStart()(现在已经废弃) -> onDestroy() 。具体看一下它的几个生命周期方法:

  • onCreate() :首次启动服务的时候,系统会调用这个方法,在onStartCommand 和 onBind 方法之前,如果服务已经启动起来了,再次启动时,则不会调用此方法,因此可以在onCreate 方法中做一些初始化的操作,比如要执行耗时的操作,可以在这里创建线程,要播放音乐,可以在这里初始化音乐播放器。

  • onStartCommand(): 当通过startService 方法来启动服务的时候,在onCreate 方法之后就会回调这个方法,此方法调用后,服务就启动起来了,将会在后台无限期的运行,直到通过stopService 或者 stopSelf 方法来停止服务。

  • onDestroy():当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。  

了解了这几个生命周期方法后,就来写一个简单Service 。

要使用Service 就要通过继承Service类(或者继承IntentService ,后文会讲)来实现,代码如下:

package com.example.servicetest;

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");
} //服务执行的操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
} //销毁服务时调用
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
} @Override
public IBinder onBind(Intent intent) {
return null;
}
}

可以看到,我们只是在onCreate()、onStartCommand()和onDestroy()方法中分别打印了一句话,并没有进行其它任何的操作,注意代码注释中这三个方法的作用。

onBind()方法是Service中唯一的一个抽象方法,所以必须要在子类里实现。我们知道,Service可以有两种启动方式:一种是startService(),另一种是bindService()。第二种启动方式才会用到onBind()方法。我们这先用第一种方式启动Service,所以暂时忽略onBind()方法。

(2)在清单文件中声明:(和Activity标签并列)

<service android:name=".MyService">        </service>

(3)修改activity_main.xml代码,如下:

<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/button1_start_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Service" /> <Button
android:id="@+id/button2_stop_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Service" /> </LinearLayout>

我们在布局文件中加入了两个按钮,一个用于启动Service,一个用于停止Service。

(4)在MainActivity作为程序的主Activity,在里面加入启动Service和停止Service的逻辑,代码如下:

package com.example.servicetest;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button; public class MainActivity extends Activity implements OnClickListener { private Button button1_start_service; private Button button2_stop_service; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button1_start_service = (Button) findViewById(R.id.button1_start_service);
button2_stop_service = (Button) findViewById(R.id.button2_stop_service);
button1_start_service.setOnClickListener(this);
button2_stop_service.setOnClickListener(this);
} @Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1_start_service:
Intent startIntent = new Intent(this, MyService.class);
startService(startIntent);
break;
case R.id.button2_stop_service:
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent);
break;
default:
break;
}
} }

代码所示,在Start Service按钮的点击事件里,我们构建出了一个Intent对象,并调用startService()方法来启动MyService。然后在Stop Serivce按钮的点击事件里,我们同样构建出了一个Intent对象,并调用stopService()方法来停止MyService。代码的逻辑非常简单。

小结:通过startService 方式启动的服务,服务会无限期的在后台运行,直到通过stopService 或 stopSelf 来终止服务。服务独立于启动它的组件,也就是说,当组件启动服务后,组件和服务就再也没有关系了,就算启动它的组件被销毁了,服务照样在后台运行。通过这种方式启动的服务不好与组件之间通信。

bindService 方式启动服务

除了startService 来启动服务之外,另外一种启动服务的方式就是通过bindService 方法了,也就是绑定服务,其实通过它的名字就容易理解,绑定即将启动组件和服务绑定在一起。前面讲的通过startService 方式启动的服务是与组件相独立的,即使启动服务的组件被销毁了,服务仍然在后台运行不受干扰。但是通过bindSerivce 方式绑定的服务就不一样了,它与绑定组件的生命周期是有关的。如下:

多个组件可以绑定到同一个服务上,如果只有一个组件绑定服务,当绑定的组件被销毁时,服务也就会停止了。如果是多个组件绑定到一个服务上,当绑定到该服务的所有组件都被销毁时,服务才会停止。

bindService 绑定服务 和startService 的生命周期是不一样,bindServie 的生命周期如下:onCreate -> onBind -> onUnbind ->onDestroy。其中重要的就是onBind 和onUnbind 方法。

  • onBind(): 当其他组件想通过bindService 与服务绑定时,系统将会回调这个方法,在实现中,你必须返回一个IBinder接口,供客户端与服务进行通信,必须实现此方法,这个方法是Service 的一个抽象方法,但是如果你不允许绑定的话,返回null 就可以了。

  • onUnbind(): 当所有与服务绑定的组件都解除绑定时,就会调用此方法。

了解了这2个方法后,我们来看一下怎么绑定一个服务。
1,首先,添加一个类 继承 Binder ,在Binder 类中添加其他组件要与服务交互的方法,并在onBind() 方法中返回IBinder 实例对象:

public class SimpleService extends Service {
public static final String TAG = "SimpleService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"call onBind...");
//返回IBinder 接口对象
return new MyBinder();
} @Override
public boolean onUnbind(Intent intent) {
Log.i(TAG,"call onUnbind...");
return super.onUnbind(intent);
} @Override
public void onCreate() {
Log.i(TAG,"call onCreate...");
} @Override
public void onStart(Intent intent, int startId) {
Log.i(TAG,"call onStart...");
} @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand...");
return super.onStartCommand(intent, flags, startId);
} @Override
public void onDestroy() {
Log.i(TAG,"call onDestroy...");
} // 添加一个类继承Binder
public class MyBinder extends Binder{
// 添加要与外界交互的方法
public String getStringInfo(){
return "调用了服务中的方法";
}
} }

2, 绑定服务的时候,需要提供一个ServiceConnection 接口,在接口回调中获取Binder 对象,与服务进行通信。

 private SimpleService.MyBinder mMyBinder;
// 绑定/解除绑定 Service 回调接口
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 绑定成功后回调
//1 ,获取Binder接口对象
mMyBinder = (SimpleService.MyBinder) service;
//2, 从服务获取数据
String content = mMyBinder.getStringInfo();
// 3,界面提示
Toast.makeText(ServiceSimpleActivity.this,content,Toast.LENGTH_LONG).show();
} @Override
public void onServiceDisconnected(ComponentName name) {
// 解除绑定后回调
mMyBinder = null;
}
};

3,绑定和解除绑定服务

            case R.id.bind_service:
Intent intent = new Intent(this,SimpleService.class);
// 绑定服务
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
// 解除绑定服务
unbindService(mConnection); break;

可以看到,绑定服务的生命周期内依次调用了onCreate ,onBind,onUnbind 和 onDestroy 方法,只有中间两个生命周期方法与startService 启动服务是不同的。

两种方式的生命周期的异同:

Android Service、IntentService,Service和组件间通信

         Service生命周期.png

started服务与bind服务的区别:

区别一:生命周期

  • 通过started方式的服务会一直运行在后台,需要由组件本身或外部组件来停止服务才会以结束运行
  • bind方式的服务,生命周期就要依赖绑定的组件

区别二:参数传递

  • started服务可以给启动的服务对象传递参数,但无法获取服务中方法的返回值
  • bind服务可以给启动的服务对象传递参数,也可以通过绑定的业务对象获取返回结果

实际开发中的技巧;

  • 第一次先使用started方式来启动一个服务
  • 之后可以使用bind的方式绑定服务,从而可以直接调用业务方法获取返回值

IntentService

IntentService 是Service 的子类,它使用工作线程逐一处理所有启动请求,果您不要求服务同时处理多个请求,这是最好的选择。 您只需实现 onHandIntent方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。

IntentService 示例

IntentService 默认为我们开启了一个工作线程,在任务执行完毕后,自动停止服务,因此在我们大多数的工作中,使用IntentService 就够了,并且IntentService 比较简单,只要实现一个方法OnHandleIntent,接下来看一下示例:

(1)新建一个MyIntentService类,继承自IntentService,并重写父类的onHandleIntent()方法,代码如下:

Android Service、IntentService,Service和组件间通信
 1 package com.example.servicetest;
2
3 import android.app.IntentService;
4 import android.content.Intent;
5 import android.util.Log;
6
7 public class MyIntentService extends IntentService{
8
9 public MyIntentService() {
10 super("MyIntentService");//调用父类有参构造函数。这里我们手动给服务起个名字为:MyIntentService
11 // TODO Auto-generated constructor stub
12 }
13
14 //该方法在会在一个单独的线程中执行,来完成工作任务。任务结束后,该Service自动停止
15 @Override
16 protected void onHandleIntent(Intent intent) {
17 // TODO Auto-generated method stub
18 for(int i = 0;i<3;i++) {
19 //打印当前线程的id
20 Log.d("MyIntentService","IntentService线程的id是:"+Thread.currentThread().getId());
21 try {
22 Thread.sleep(1000);
23 } catch (InterruptedException e) {
24 // TODO Auto-generated catch block
25 e.printStackTrace();
26 }
27 }
28 }
29
30 @Override
31 public void onDestroy() {
32 // TODO Auto-generated method stub
33 super.onDestroy();
34 Log.d("MyIntentService","onDestroy");
35 }
36 }
Android Service、IntentService,Service和组件间通信

这里首先要提供一个无参的构造方法,并且必须在其内部调用父类的有参构造方法(9至12行),我们在第10行手动将服务的名字改为“MyIntentService”。

然后在子类中实现onHandleIntent()这个抽象方法,可以在这个方法里去处理一些具体的逻辑,我们就用三次for循环,打印当前线程的id,每次延时1秒。

因为这个服务在运行结束后会自动停止,所以我们在onDestroy()方法中打印日志验证一下。

(2)在清单文件中对服务进行注册服务:

<service android:name=".MyIntentService"> </service>

(3)在activity_main.xml中添加一个按钮button3_stop_intentservice,用于启动MyIntentService服务,代码略。

(4)在MainActivity里面加入启动IntentService的逻辑,核心代码如下:

1       case R.id.button3_stop_intentservice:
2 Log.d("MainActivity","主线程的id是:"+Thread.currentThread().getId());
3 Intent intentService = new Intent(this,MyIntentService.class);
4 startService(intentService);
5 default:

我们在第02行中,打印主线程的id。

运行程序,点击按钮button3_stop_intentservice,显示如下:

Android Service、IntentService,Service和组件间通信

由此可见,启动一个IntentService和启动一个普通的Service,步骤是一样的。

4、Service和Thread的关系:

不少Android初学者都可能会有这样的疑惑,Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!

之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?

其实,后台和子线程是两个完全不同的概念:

Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,Service既然是运行在主线程里,在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。

既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例;而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

所以说,一个比较标准的Service,就可以写成本段中第1节的样子。

IntentService总结:IntentService是Service 的子类,默认给我们开启了一个工作线程执行耗时任务,并且执行完任务后自 动停止服务。扩展IntentService比较简单,提供一个构造方法和实现onHandleIntent 方法就可了,不用重写父类的其他方法。但是如果要绑定服务的话,还是要重写onBind 返回一个IBinder 的。使用Service 可以同时执行多个请求,而使用IntentService 只能同时执行一个请求。

Service 与应用组件通信的几种方式

1,BroadcastReceiver
通过前文我们知道,startService方式启动的服务在后台,无限期地运行,并且与启动它的组件是独立的,启动Service 之后也就与启动它的组件没有任何关系了。因此它是不能与启动它的组件之间相互通信的。虽然Service 没有提供这种启动方式的通信方法,我们还是可以通过其他方式来解决的,这就用到了BroadcastReceiver。

场景描述:通过startService 启动一个长期在后台运行的下载图片服务,然后在界面上点击下载按钮,通过intent 传递一个下载链接给Service,在下载完成后,通过BroadcastReceiver 通知Activity 界面显示图片。看一下代码实现:

Service代码如下:

public class DownloadService extends Service {
public static final String IMAGE = "iamge_url";
public static final String RECEIVER_ACTION = "com.zhouwei.simpleservice";
private static final String TAG = "DownloadService";
public static final String ACTION_START_SERVICER = "com.zhouwei.startservice";
public static final String ACTION_DOWNLOAD = "com.zhouwei.startdownload";
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper){
super(looper);
} @Override
public void handleMessage(Message msg) {
// 工作线程做耗时下载
String url = (String) msg.obj;
Bitmap bitmap = null;
try {
bitmap = Picasso.with(getApplicationContext()).load(url).get();
Intent intent = new Intent();
intent.putExtra("bitmap",bitmap);
intent.setAction(RECEIVER_ACTION);
// 通知显示
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
} catch (IOException e) {
e.printStackTrace();
} //工作完成之后,停止服务
stopSelf();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
// 开启一个工作线程做耗时工作
HandlerThread thread = new HandlerThread("ServiceHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// 获取工作线程的Looper
mServiceLooper = thread.getLooper();
// 创建工作线程的Handler
mServiceHandler = new ServiceHandler(mServiceLooper);
} @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"call onStartCommand...");
if(intent.getAction().equals(ACTION_DOWNLOAD)){
handleCommand(intent);
}else if(intent.getAction().equals(ACTION_START_SERVICER)){
//do nothing
} return START_STICKY;
} private void handleCommand(Intent intent){
String url = intent.getStringExtra(IMAGE);
// 发送消息下载
Message message = mServiceHandler.obtainMessage();
message.obj = url;
mServiceHandler.sendMessage(message);
}
}

新建了一个DownloadService ,在里面启动了一个工作线程,在线程里下载图片,然后通过BroadcastReceiver 通知Activity显示。

Activity的代码很简单,注册BroadcastReceiver,在onReceiver中显示图片就好了,代码如下:

private ImageView mImageView;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 显示图片
Bitmap bitmap = intent.getParcelableExtra("bitmap");
mImageView.setImageBitmap(bitmap);
}
}; /**
* 启动下载
*/
private void startDownload(){
Intent intent = new Intent(this,DownloadService.class);
// 启动服务
intent.putExtra(DownloadService.IMAGE,"http://www.8kmm.com/UploadFiles/2012/8/201208140920132659.jpg");
intent.setAction(DownloadService.ACTION_DOWNLOAD);
startService(intent);
}

声明

https://blog.csdn.net/qq_34115898/article/details/83347882

文章来源

上一篇:LeetCode 562. Longest Line of Consecutive One in Matrix(在矩阵中最长的连续1)$


下一篇:WCF Failed to invoke the service. Possible causes: The service is offline or inaccessible