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的用法不单单是这些,至于他的高级用法日后我会再写一篇文章的,敬请期待!!!)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容