Android Service基本用法

Service的概念

  • 1.Service作为安卓的四大组件之一,固然是每一位安卓开发者必须掌握的一个知识点。虽然它没有Activity的使用频繁,但也是日常开发经常用到的。

  • 2.通过名字我们知道,它是服务的意思。而且通常是"默默"的为我们服务的。为什么说是默默,因为它并不像Activity一样,能够被我们看到。通常,它用于在后台为我们执行一些耗时,或者需要长时间执行的一些操作的。下面让我们来看看它的基本用法。

Service的创建

  • 1.任何一个对象,想要发挥其作用,那么就应该首先创建出来。Service的创建和Activity类似,也是通过Intent来实现的。而且既然是安卓四大组件之一,那么它也需要在清单文件中进行注册的。下面,看一下一个简单的创建Service的例子:
public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    @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;
    }
}
  • 2.首先,创建一个类SimpleService继承自Service,然后重写它的onCreate,onStartCommand,onDestroy方法,并分别在它们的方法体中打入Log日志。其中onBind方法是默认实现的,具体作用后面会讲到。然后呢,千万不要忘记要在清单文件中注册它,其实如果你是通过Android Studio直接new了一个Service的话,Android Studio会默认帮助你在清单文件中添加对该Service的注册,代码如下:
<service android:name=".ui.main.SimpleService"
            android:enabled="true"
            android:exported="true"/>

我直接在我之前做的一个项目中新建的,所以不要在意包路径的命名,就是包名点类名。细心的朋友可能看到了下面还有两个属性。其中enabled属性,是指该服务是否能够被实例化。如果设置为true,则能够被实例化,否则不能被实例化,默认值是true。一般情况下,我们都会需要实例化,所以也可以选择不设置。而exported属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互(通常如果一个服务需要跨进程使用需要这么设置),否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。

  • 3.接下来创建一个StartActivity,用于在其中创建SimpleService对象。代码如下:
public class StartActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startBtn, stopBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        startBtn = (Button) findViewById(R.id.btn_start_service);
        stopBtn = (Button) findViewById(R.id.btn_stop_service);
     
        startBtn.setOnClickListener(this);
        stopBtn.setOnClickListener(this);  
    }

    @Override
    public void onClick(View v) {
        if (v != null) {
            switch (v.getId()) {
                case R.id.btn_start_service:
                    Intent startIntent = new Intent(this, SimpleService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop_service:
                    Intent stopIntent = new Intent(this, SimpleService.class);
                    stopService(stopIntent);
                    break;          
            }
        }
    }
}

StartActivity的xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<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="com.kitty.android.ui.main.StartActivity">

    <Button
        android:id="@+id/btn_start_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Start Service"/>
    <Button
        android:id="@+id/btn_destroy_service"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="Destroy Service"/>
</LinearLayout>
  • 4.很简单,就是添加两个按钮,分别用于启动Service和停止Service。可以看到,创建一个Service的方法非常简单,就是和创建Activity类似,创建一个Intent对象,然后通过startService方法开启一个服务。然后同样的,通过stopService方法来停止一个服务。为了证明方法的正确性,我们先点击startBtn,看一下Logcat中的日志截图,如下所示:
servicestart.png

由此可以看出,当通过startService方法开启一个服务的时候,会执行Service的onCreate和onStartCommand方法。接下来,我们再次点击一下 start按钮,再来看一下Loacat的日志:

restartservice.png

这一次,只执行了onStartCommand方法,由此我们可以得出结论,当一个Service被创建以后,再次调用startService方法,Service是不会被重新创建的,而是会重新执行onStartCommand方法。无论我们点击多少次start按钮,始终只会执行onStartCommand方法。

以上是服务的创建。接下来,我们点击stop按钮,logcat日志如下:

stopservice.png

可以看到,Service执行了onDestroy方法,这时服务就已经停止了。
以上就是简单的创建一个服务的流程。然而,在我们日常开发中,我们经常需要在服务中做一些逻辑操作,然后将结果返回给一个Activity,即要实现Service和Activity的通信,接下来,让我们看看如何让二者建立起联系。

Service与Activity之间的通信

在上面的介绍中,我们只是实现了在Activity中开启一个服务,然而服务开启了就和这个Activity没什么联系了。其实二者是可以继续保持联络的,还记得前面提到的一个onBind方法吧,其实它就是Service与Activity之间建立通信的桥梁。现在我们修改一下前面的代码,SimpleService代码修改如下:

public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mBinder = new SimpleBinder();
    }

    @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) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            Log.d(TAG, "doTask");
        }
    }
}

现在,我们在SimpleService中创建了一个SimpleBinder类,继承自Binder。然后,在里面创建了一个doTask方法,模拟执行一个任务。然后,我们再在StartActivity中加入两个按钮,分别用于绑定服务和解绑服务,XML文件代码我就不贴了,就是添两个按钮,下面是StartActivity的更改后的代码:

public class StartActivity extends AppCompatActivity implements View.OnClickListener {

    public static final String TAG = "SimpleService";

    private Button startBtn, stopBtn, bindBtn, unBindBtn;

    private SimpleService.SimpleBinder mBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, name.toString());
            mBinder = (SimpleService.SimpleBinder) service;
            mBinder.doTask();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, name.toString());
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        startBtn = (Button) findViewById(R.id.btn_start_service);
        stopBtn = (Button) findViewById(R.id.btn_stop_service);
        bindBtn = (Button) findViewById(R.id.btn_bind_service);
        unBindBtn = (Button) findViewById(R.id.btn_un_bind_service);

        startBtn.setOnClickListener(this);
        stopBtn.setOnClickListener(this);
        bindBtn.setOnClickListener(this);
        unBindBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v != null) {
            switch (v.getId()) {
                case R.id.btn_start_service:
                    Intent startIntent = new Intent(this, SimpleService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop_service:
                    Intent stopIntent = new Intent(this, SimpleService.class);
                    stopService(stopIntent);
                    break;
                case R.id.btn_bind_service:
                    Intent bindIntent = new Intent(this, SimpleService.class);
                    bindService(bindIntent, mConnection, BIND_AUTO_CREATE);
                    break;
                case R.id.btn_un_bind_service:
                    unbindService(mConnection);
                    break;
            }
        }
    }
}

这里,我们创建了一个ServiceConnection的匿名内部类,并实现了onServiceConnected和onServiceDisconnected两个方法。ServiceConnection可以看做是一个由Activity操作的代表,负责与Service进行连接,当Activity与Service连接成功时,会执行onServiceConnected方法,相反的,当二者断开连接的时候,会执行onServiceDisconnected方法。当二者连接成功时,在onServiceConnected方法中,我们可以获取到SimpleService中的SimpleBinder的实例对象,然后我们就可以调用其所有的公共方法来实现我们想要做的事了。

现在我们来看一下绑定一个服务的方法,也是先创建一个Intent,然后将其作为bindService的第一个参数,第二个参数就是我们刚才说到的那个代表ServiceConnection,而第三个参数是一个标记,这里我们传入BIND_AUTO_CREATE标记,此标记表示在Activity和Service建立关联后自动创建Service,并执行Service中的onCreate方法,并不会执行onStartCommand方法。(标记不只这一个,可以自行去查阅其他flag的含义)

为了验证刚刚我所说的,我们点击bind按钮,并查看logcat日志,如下:

bindservice.png

可以看到,确实只执行了onCreate方法,并且在ServiceConnection的onServiceConnected中的第一个参数返回的其实就是SimpleService的具体包名路径。然后接着就执行了SimpleBinder中的doTask方法。当我们再次点击bind按钮,会发现,并没有执行任何的方法,说明了服务如果一旦与一个Activity绑定后,如果没有解绑的话,它是不会重新与这个Activity进行绑定的。

绑定完了,最终我们肯定是需要解绑的,来看一下unBind按钮的操作方法,只有一个unbindService方法,需要传入一个ServiceConnection参数,即我们前面创建的mConnection实例。点击unbind按钮,看到logcat日志如下:

unbindservice.png

执行了onDestroy方法,这是刚刚通过bindService方法创建的Service就已经被摧毁了。

Service创建与摧毁方式的混淆

通过刚刚上述的描述,我们现在知道创建一个Service的方式有两种,即通过startService和bindService的方式。而二者对Service的摧毁方式分别为stopService和unBindService。那么可能很多人会疑问如果我选择startService的方式创建,然后选择unbindService的方式摧毁。或者说我采用bindService方式创建,stopService方式摧毁呢。那么我们来对每种情况分别验证一下会发生什么样的结果。

我们首先现将SimpleService中的代码还原成如下状态。StartActivity中还是四个按钮分别对应四个方法。

public class SimpleService extends Service {

    public static final String TAG = "SimpleService";

    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        mBinder = new SimpleBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 任务逻辑
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            Log.d(TAG, "doTask");
        }
    }
}
  • 1.我们首先点击一下start按钮,然后点击unbind按钮,结果会发现程序崩了。
notregister.png

崩溃日志如下,提示非法状态异常,接着后面又说,Service没有被注册。其实,很容易理解,就是不看崩溃日志,估计很多人也猜到了,Service根本就没有与任何东西绑定,又何谈解绑呢。所以,这里需要注意,在使用unbindService方法关闭一个服务时,为防止出现以上的情况,我们需要在调用比方法前判断一下当前的Service是否已经被绑定。

针对这一判断谷歌并没有提供专门的Api,而我们可以通过在本地创建一个变量,当服务被绑定的时候,在ServiceConnection的onServiceConnected方法中将其设为true, 在onServiceDisconnected方法中将其设为false。这样的话,如果服务未被绑定的话,这个值会永远为false,这样我们就直接在调用unbindService方法前做出相应的提示,以防止崩溃产生。

  • 2.点击bind按钮后,再点击stop按钮。同样,看一下logcat日志:
bindstop.png

请相信我,我真的点击了stop按钮,而且不止点击了一次。可以看到,Service并没有被摧毁。而当我再次点击unbind按钮时,Service才被摧毁。其实这也很好解释,举个例子吧,施瓦星格拍的终结者(第二部)不知道大家有没有看过,施瓦星格扮演的终结者的使命是保护男主,他的程序一旦被启动,就会一直以保护男主(完成使命)为目的,只有消灭了敌人,确保男主可以平安无事了,他才会停止保护男主的行为。Service就相当于施瓦星格,它一旦被绑定,必须等到一个结果来告诉它"使命"完成了(解绑了),它才会停止下来。

  • 3.点击start按钮后,接着点击bind按钮,然后呢,我们分别单独点击stop和unbind按钮,发现Service都不会被摧毁,只有在我们两个按钮都点击了以后(两个按钮的点击顺序无所谓),Service才会被摧毁。通过这一样一个结论,我们可以得出,一个Service,只有在即没有和任何Activity绑定又处于停止状态下的时候,才可以被摧毁。

  • 4.最后,还要大家清楚一个问题就是。当你通过start按钮,开启一个Service后,再次点击bind按钮,只会执行服务的doTask方法,也就是Service绑定的方法回调,而不会执行onCreate方法再次创建一个Service。而同样的当通过点击bind按钮开启一个服务后,再点击start按钮,也不会执行onCreate方法,只会每次点击都执行onstartCommand方法,这里在前面也提到过。

Service的执行线程

很多对Service了解的不是很透彻的同学,当被问到Service运行在什么线程中,很多人都会第一反应是子线程。原因呢,因为大多数人都知道Service通常用来执行一些比较耗时的后台任务,既然提到了耗时,那么肯定不会运行在主线程啊,因为那样的话会阻塞线程的啊。其实呢,并不是这样的,Service其实是运行在主线程的。为了证明我所说的,我分别在StartActivity的onCreate方法和SimpleService的onCreate方法中获取当前运行的线程的名字和id,代码如下:

// Activity的onCreate方法
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);
        Log.d(TAG, "StartActivity-----" + Thread.currentThread().getName() + "--" + Thread.currentThread().getId());  
    }

// Service的onCreate方法
   @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "SimpleService-----" + Thread.currentThread().getName() + "--"Thread.currentThread().getId());     
    }

分别启动Activity和Service,Logcat日志如下:

servicethread.png

可以看到,Service真的和Activity一样,是运行在主线程的。到这估计好多人就懵逼了,这到底是个什么鬼啊,明明是用来执行一些后台的耗时任务的啊,怎么还运行在主线程啊。
其实,这就是不会融会贯通的表现。Service确实运行在主线程,但是我们如果执行耗时操作,我们可以在Service里面开启一个子线程来执行耗时任务啊。那么问题又来了,我们在Activity中就可以创建子线程何必还费二遍事开始一个服务来创建一个子线程呢。这里就不得不说出Service的一个特性了,那就是不管创建Service的Activity是否被摧毁,或者说即使退出了应用程序,但只要应用的进程没有被杀死,这个Service就还会在运行着。而对于Activity来说,一旦Activity被摧毁,那么在它里面创建的线程也就不存在,这样一来执行的任务也就停止了。而如果放在Service中执行,即便是关联的Activity被摧毁了,那么只要重新与Service进行关联,那么它还是会获得原有Service中的Binder实例的。举出这样一个需求,假设应用需要在进程未被杀死的情况下时刻保持着从服务器读取一个状态,很明显这时候就可以用Service来实现了啊。

现在,是不是明白了为什么通常在Service中执行一些后台的耗时操作了吧。下面我列举出一个例子来掩饰在Service中开启一个线程来执行操作:

public class SimpleService extends Service {
    
    private SimpleBinder mBinder;

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new SimpleBinder();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 任务逻辑
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder != null) {
            return mBinder;
        }
        return null;
    }

    class SimpleBinder extends Binder {

        public void doTask() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 任务逻辑
                }
            }).start();
        }
    }
}

看到了吧,是不是很简单!

恍然大悟.png

以上就是Service的一些基础用法,只有掌握了这些基本用法以后,我们才能更好的利用Service这一组件来完成我们平时日常中的开发需求。特别提醒的是,要根据场景恰当的选择使用Service,不要拿出一个任务就用Service来实现,以免造成不必要的开销。(PS:Service的用法不单单是这些,至于他的高级用法日后我会再写一篇文章的,敬请期待!!!)

推荐阅读更多精彩内容