安卓Service完全解析(上)

版权声明:本文出自汪磊的博客,转载请务必注明出处。  

关于安卓Service相信很多安卓开发者都听说过,作为安卓四大组件之一,即使不经常用也应该听说过,但并不是每一个人都掌握的特别详细,全面。那么今天我将带大家全面了解一下Service.希望对您有所帮助。

什么是Service? 

先来看一下官方定义:

Service is an application component that can perform long-running operations in the background and does not provide a user interface. Another application component can start a service and it will continue to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC). For example, a service might handle network transactions, play music, perform file I/O, or interact with a content provider, all from the background.

翻译过来就是:Service是一个没有用户界面的在后台运行执行耗时操作的应用组件。其他应用组件能够启动Service,

并且当用户切换到另外的应用场景,Service将持续在后台运行。另外,一个组件能够绑定到一个service与之交互(IPC机制),

例如,一个service可能会处理网络操作,播放音乐,操作文件I/O或者与内容提供者(content provider)交互,所有这些活动都是在后台进行。

简单说Service就是一个不依附界面可以在后台长期执行耗时操作的组件。

      

Service基本用法

接下来了解一下Service的启动以及生命周期,下面通过一个简单实例来学习一下。

新建一个MyService继承自Service,并重写父类的onCreate()、onStartCommand()和onDestroy()方法,如下所示:

    

 public class MyService extends Service {

     @Override
public IBinder onBind(Intent arg0) {
return null;
} @Override
public void onCreate() {
Log.i("WLService", "onCreate");
super.onCreate();
} @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("WLService", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
} @Override
public void onDestroy() {
Log.i("WLService", "onDestroy");
super.onDestroy();
} }

然后打开项目布局文件,添加启动,关闭Service按钮,如下:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" > <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="start"
android:text="开启服务" /> <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="stop"
android:text="停止服务" /> </LinearLayout>

Service作为四大组件之一,我们还需要在清单文件中注册一下,如下:

<service android:name="com.wl.servicelife.MyService"></service>

   

然后我们在MainActivity中编写启动,关闭服务代码。如下:

 public class MainActivity extends Activity {

     @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void start(View view) {
Intent intent = new Intent(this, MyService.class);
// 通知框架开启服务。
startService(intent);
} public void stop(View view) {
Intent intent = new Intent(this, MyService.class);
stopService(intent);
} @Override
protected void onDestroy() {
Log.i("WLService", "MainActivity onDestroy");
super.onDestroy();
}
}

在Start Service按钮的点击事件里,我们构建出了一个Intent对象,调用startService()方法来启动MyService。

然后在Stop Serivce按钮的点击事件里,我们同样构建出了一个Intent对象,调用stopService()方法来停止MyService。

基本的项目搭建完成,现在我们运行项目,点击开启服务按钮,会看到打印如下:

安卓Service完全解析(上)

当我们再次点击开启服务按钮,会看到打印如下:只有"onStartCommand"会打印出来

安卓Service完全解析(上)

当我们点击停止服务按钮,会看到打印如下:

安卓Service完全解析(上)

到现在为止相信你对Service有了最基础的了解.接下来,我们还需要了解怎么调用服务里面的方法。  

调用服务里面的方法

有些同学可能会说调用服务里面的方法还不简单吗,new一个对象获得引用,然后不久可以调用了吗?这样可以吗?我们可以自己测试一下。

首先我们在服务里面添加一个方法,如下:

     public void methodInService(){

         Toast.makeText(this, "ClearHeart", Toast.LENGTH_SHORT).show();
}

很简单,我们只是弹出一个吐司。

然后在布局文件加入如下按钮:

     <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="call"
android:text="调用服务里面的方法" />

接下来我们编写代码调用服务里面的方法:

     // 调用服务里面的方法
public void call(View view) { MyService myService = new MyService();
myService.methodInService();
}

运行程序点击按钮调用服务里面的方法我们会发现程序崩溃,报如下错误:

 08-13 10:38:41.114: D/AndroidRuntime(2431): Shutting down VM
08-13 10:38:41.115: E/AndroidRuntime(2431): FATAL EXCEPTION: main
08-13 10:38:41.115: E/AndroidRuntime(2431): Process: com.wl.service, PID: 2431
08-13 10:38:41.115: E/AndroidRuntime(2431): java.lang.IllegalStateException: Could not execute method of the activity
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.view.View$1.onClick(View.java:4007)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.view.View.performClick(View.java:4756)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.view.View$PerformClick.run(View.java:19749)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.os.Handler.handleCallback(Handler.java:739)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.os.Handler.dispatchMessage(Handler.java:95)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.os.Looper.loop(Looper.java:135)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.app.ActivityThread.main(ActivityThread.java:5221)
08-13 10:38:41.115: E/AndroidRuntime(2431): at java.lang.reflect.Method.invoke(Native Method)
08-13 10:38:41.115: E/AndroidRuntime(2431): at java.lang.reflect.Method.invoke(Method.java:372)
08-13 10:38:41.115: E/AndroidRuntime(2431): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
08-13 10:38:41.115: E/AndroidRuntime(2431): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
08-13 10:38:41.115: E/AndroidRuntime(2431): Caused by: java.lang.reflect.InvocationTargetException
08-13 10:38:41.115: E/AndroidRuntime(2431): at java.lang.reflect.Method.invoke(Native Method)
08-13 10:38:41.115: E/AndroidRuntime(2431): at java.lang.reflect.Method.invoke(Method.java:372)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.view.View$1.onClick(View.java:4002)
08-13 10:38:41.115: E/AndroidRuntime(2431): ... 10 more
08-13 10:38:41.115: E/AndroidRuntime(2431): Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.content.ContextWrapper.getResources(ContextWrapper.java:85)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.widget.Toast.<init>(Toast.java:101)
08-13 10:38:41.115: E/AndroidRuntime(2431): at android.widget.Toast.makeText(Toast.java:250)
08-13 10:38:41.115: E/AndroidRuntime(2431): at com.wl.service.MyService.methodInService(MyService.java:38)
08-13 10:38:41.115: E/AndroidRuntime(2431): at com.wl.service.MainActivity.call(MainActivity.java:37)
08-13 10:38:41.115: E/AndroidRuntime(2431): ... 13 more

定位到弹出吐司那一句代码有空指针异常,通过简单分析可以确定this是空,那么为什么this是空呢?

这里需要解释一下,服务(Service)只能由系统创建,而不能通过new MyService()方式自己创建,如果我们自己创建服务对象,这样创建出来的只是一个普通类。Service的创建只能由系统来创建完成,而不能我们自己创建,只有系统框架创建的Service才能将应用上下文传递给Service。

那我们如何调用Service内部方法呢?这里我们需要另一种方式开启服务,bind方式开启服务。

接下来我们新建一个工程,新建MyService类继承系统Service类,代码如下:

 public class MyService extends Service {

     private static final String TAG = "WLHeart";

     @Override
public IBinder onBind(Intent arg0) {
Log.i(TAG, "onBind");
return new MiddlePerson();
} @Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind");
return super.onUnbind(intent);
} @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 void onDestroy() {
Log.i(TAG, "onDestroy");
super.onDestroy();
} /**
* 这是服务里面的一个方法
*/
public void methodInService() {
Toast.makeText(this, "WLHeart", 0).show();
} private class MiddlePerson extends Binder implements IMiddleBind { public void callMethodInService(int money) { methodInService();
}
}
}

可见我们新增了一个私有内部类MiddlePerson继承Binder并且实现IMiddleBind接口,实现IMiddleBind接口中定义的方法,并调用服务中方法,

重写服务的onUnbind方法,返回内部类MiddlePerson的实例对象。

IMiddleBind代码如下:

 public interface IMiddleBind {

     public void callMethodInService();
}

然后我们修改布局文件,很简单不必过多解释了,如下:

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" > <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="bind"
android:text="绑定服务" /> <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="unbind"
android:text="解除绑定服务" /> <Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="call"
android:text="调用服务里面的方法" /> </LinearLayout>

接下来我们修改MainActivity中代码,绑定服务并且调用其中代码:

 public class MainActivity extends Activity {
private static final String TAG = "WLHeart";
private MyConn conn;
private IMiddleBind middleBinder; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} // 绑定服务
public void bind(View view) {
Intent intent = new Intent(this, MyService.class);
conn = new MyConn();
bindService(intent, conn, BIND_AUTO_CREATE);
} // 解除绑定服务
public void unbind(View view) {
unbindService(conn);
} @Override
protected void onDestroy() {
Log.i(TAG, "activity,onDestroy");
middleBinder = null;
super.onDestroy();
} // 调用服务里面的方法。
public void call(View view) {
//
if(null != middleBinder){
middleBinder.callMethodInService();
}
} private class MyConn implements ServiceConnection {
//
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
middleBinder = (IMiddleBind) service;
} // 当服务失去连接的时候调用
@Override
public void onServiceDisconnected(ComponentName name) { }
}
}

可以看到我们新建一个MyConn的内部类实现ServiceConnection接口,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,

这两个方法分别会在调用者与Service建立关联和解除关联的时候调用。在onServiceConnected()方法中我们将返回的service参数转型为IMiddleBind类型,这里为什么可以转型呢?大家注意到在MyService的onBind方法中我们返回了其内部类MiddlePerson的实例,

MiddlePerson继承自Binder类,Binder类点进系统源码我们可以看到其实现IBinder接口,而onServiceConnected(ComponentName name, IBinder service)方法第二个参数正是IBinder类型,

到此,大家应该明白了吧。

简单总结一下:在调用者与Service绑定成功的时候会调用onServiceConnected()方法,此方法中第二个参数就是我们在onBind方法中返回的

MiddlePerson实例,好了到此为止我们就可以获取Service内部类的实例引用了,通过这个实例引用我们就可以调用其内部方法了。

    

我们运行程序实验一下,运行程序,点击绑定服务按钮,打印如下:

安卓Service完全解析(上)

点击调用服务里面方法按钮,打印如下:

安卓Service完全解析(上)


点击解除绑定按钮,打印如下:

安卓Service完全解析(上)

到此为止是不是对绑定服务方式开启服务并且调用其中方法有了一步初步认识,似乎过程又有些小复杂,我们总结一下绑定本地服务调用方法的步骤:

    

1.在服务的内部创建一个内部类 提供一个方法,可以间接调用服务的方法      

private class MiddlePerson extends Binder implements IMiddleBind

2.实现服务的onbind方法,返回的就是中间人 MiddlePerson     

     @Override
public IBinder onBind(Intent arg0) {
Log.i(TAG, "onBind");
return new MiddlePerson();
}

3.在activity 绑定服务。bindService();

     // 绑定服务
public void bind(View view) {
Intent intent = new Intent(this, MyService.class);
conn = new MyConn();
bindService(intent, conn, BIND_AUTO_CREATE);
}

4.在服务成功绑定的时候 会执行一个方法 onServiceConnected 传递过来一个 IBinder对象
5.强制类型转化 调用接口里面的方法。

 private class MyConn implements ServiceConnection {
//
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
middleBinder = (IMiddleBind) service;
} // 当服务失去连接的时候调用
@Override
public void onServiceDisconnected(ComponentName name) { }
}

遵循以上5部就可以绑定本地服务并且调用服务种方法,是不是很简单呢。

绑定服务调用服务中方法注意点 

有些同学有没有想过可不可以绑定服务之后立刻调用服务中方法呢?我们可以试验一下,将MainActivity代码改为如下:

 public class MainActivity extends Activity {
private static final String TAG = "WLHeart";
private MyConn conn;
private IMiddleBind middleBinder; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} // 绑定服务
public void bind(View view) {
Intent intent = new Intent(this, MyService.class);
conn = new MyConn();
bindService(intent, conn, BIND_AUTO_CREATE);
middleBinder.callMethodInService();
} // 解除绑定服务
public void unbind(View view) {
unbindService(conn);
} @Override
protected void onDestroy() {
Log.i(TAG, "activity,onDestroy");
middleBinder = null;
super.onDestroy();
} // 调用服务里面的方法。
public void call(View view) {
// } private class MyConn implements ServiceConnection {
//
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
middleBinder = (IMiddleBind) service;
} // 当服务失去连接的时候调用
@Override
public void onServiceDisconnected(ComponentName name) { }
}
}

可见我们只是将逻辑改为绑定完服务立刻调用其方法,运行程序,点击绑定服务按钮会发现报如下错误:

 08-14 14:47:21.311: E/AndroidRuntime(1890): FATAL EXCEPTION: main
08-14 14:47:21.311: E/AndroidRuntime(1890): Process: com.wl.bindservice, PID: 1890
08-14 14:47:21.311: E/AndroidRuntime(1890): java.lang.IllegalStateException: Could not execute method of the activity
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.view.View$1.onClick(View.java:4007)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.view.View.performClick(View.java:4756)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.view.View$PerformClick.run(View.java:19749)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.os.Handler.handleCallback(Handler.java:739)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.os.Handler.dispatchMessage(Handler.java:95)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.os.Looper.loop(Looper.java:135)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.app.ActivityThread.main(ActivityThread.java:5221)
08-14 14:47:21.311: E/AndroidRuntime(1890): at java.lang.reflect.Method.invoke(Native Method)
08-14 14:47:21.311: E/AndroidRuntime(1890): at java.lang.reflect.Method.invoke(Method.java:372)
08-14 14:47:21.311: E/AndroidRuntime(1890): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
08-14 14:47:21.311: E/AndroidRuntime(1890): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
08-14 14:47:21.311: E/AndroidRuntime(1890): Caused by: java.lang.reflect.InvocationTargetException
08-14 14:47:21.311: E/AndroidRuntime(1890): at java.lang.reflect.Method.invoke(Native Method)
08-14 14:47:21.311: E/AndroidRuntime(1890): at java.lang.reflect.Method.invoke(Method.java:372)
08-14 14:47:21.311: E/AndroidRuntime(1890): at android.view.View$1.onClick(View.java:4002)
08-14 14:47:21.311: E/AndroidRuntime(1890): ... 10 more
08-14 14:47:21.311: E/AndroidRuntime(1890): Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void com.wl.bindservice.IMiddleBind.callMethodInService()' on a null object reference
08-14 14:47:21.311: E/AndroidRuntime(1890): at com.wl.bindservice.MainActivity.bind(MainActivity.java:28)
08-14 14:47:21.311: E/AndroidRuntime(1890): ... 13 more

报空指针异常,定位到middleBinder.callMethodInService();发生空指针,明显是middleBinder为空,那为什么会是空呢?我们不是已经绑定服务了吗?

原来绑定服务的过程是异步过程,这个过程可能持续几十或者几百毫秒,而middleBinder只有在调用完onServiceConnected才会初始化完成,

这样就不难理解为什么会报空指针异常了吧,希望各位使用过程中会注意一下这个地方。

  

两种开启服务方法的区别        

start方式开启服务。 一旦服务开启跟调用者(开启者)就没有任何关系了。开启者退出了,开启者挂了,服务还在后台长期的运行。开启者没有办法去调用服务里面的方法。
bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。开启者可以调用服务里面的方法。

混合方式开启服务

如果我们即想服务长期在后台运行又想调用其中方法怎么办呢?很简单,我们可以采取混合调用服务的方法开启服务

即我们先调用start方式开启服务保证服务在后台长期运行,在调用bind方式绑定服务保证调用服务里面方法,这样子即可。

那我们如何关闭服务呢?我们知道start方式开启服务我们只需要调用stopservice()即可关闭服务,

bind方式开启服务我们只需调用unbind即可关闭服务 ,其实混合方法开启服务我们只需要先调用unbind解除绑定,

再调用stopservice停止服务即可。混合方式调用服务完整过程如下:

      1.start方式开启服务(保证服务长期后台运行)
      2.bind方式绑定服务(保证调用服务的方法)
      3.unbind解除绑定服务
      4.stopService停止服务

好了,到此为止我们算是对服务有了一个比较基础的全面的认识,掌握这些相信能解决平时开发中大部分的问题了。

下一篇(中篇)会带大家了解一下安卓IPC(进程间通讯)机制,最后(下篇)详细了解安卓Binder机制

上一篇:总结源码编译安装mysql


下一篇:[转]如何将高版本的SQL Server数据库备份到低版本的SQL Server