对于 Service, 你真的都懂了吗?

加油.png

android 入行也大半年了,一直都没有对基础知识做一个梳理,今后就对基础知识逐个总结,先从 Service 开始。

一、Servive 概念

我们一般把 Service 称做 "后台默默的劳动者"。现在的 智能手机应用程序都能在后台运行,这就是 得益于 Service 。在
android 开发中,Service 就是用来解决程序后台运行的解决方案,适合去执行一些不需要跟用户交互而且要求长期运行的任务。不依赖任何用户界面,即使程序切换到后台,或者打开新的应用程序。但是需要注意以下两点:

  • Service 不是运行在一个独立的进程中,而是依赖于创建该Service的 应用程序进程,一旦该程序被 kill ,Service 也随之停止运行
  • 不要以为 Service 是工作在后台,就理所当然的认为 Service 会自动开启线程。Service 默认是工作在 UI 线程的,一旦有耗时操作,必须开启新线程,防止 ANR 异常。

二、Service 的定义

使用 android studio 快速定义一个 MyService 继承自 Service.Service中 onBind() 为抽象方法,我们暂且先必须实现它,接着依次实现 onCreate() ,onStartCommand,onDestroy 生命周期方法。如下所示:

public class MyService extends Service {
    private static final String TAG = "MyService";

    public MyService() {
    }
    /**
     * 跟其他组件绑定时使用
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
      /**
     * Service 第一次创建时回调
     */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: excuted");
    }
    /**
     * 每次启动 Service 时回调
     * @param intent
     * @param flags
     * @param startId
     * @return
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: excuted");
        return super.onStartCommand(intent, flags, startId);
    }
    /**
     * 销毁 Service 时回调
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(TAG, "onDestroy: excuted");
    }
}

注意:身为 四大组件之一的 Service 也必须在 Manifest 里面注册,不然会报错,但是这里智能的 as 已经帮我们注册好啦~

        <service
            android:name=".app.service.MyService"
            android:enabled="true"
            android:exported="true">
        </service>
  • enabled表示是否启用该 服务
  • exported表示是否允许除了当前应用程序之外的其他程序访问这个服务。

三、Service 的启动与停止

我们在 activity 中添加两个 Button ,用来触发 Service 的启动与停止。这里使用黄油刀直接注入。启动与停止的逻辑相当简单,一般我们组件间通信都是用 intent ,这里也不例外,startService(),stopService() 传入对应的 intent 即可。

 @OnClick({R.id.start_service, R.id.stop_service})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.start_service:
                Intent startIntent = new Intent(this, MyService.class);
              //启动service
                startService(startIntent);
                break;
            case R.id.stop_service:
                Intent stopIntent = new Intent(this, MyService.class);
               //停止service
                stopService(stopIntent);
                break;
        }

直接 run 程序跑到手机上,来看看 Service 具体的启动过程,我们事先已经在对应的生命周期方法中打好 log 了。现在点击 start 效果如下:

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted

log 很清晰明了。当我们点击 start按钮启动 Service 时,系统回调了onCreate(),onStartCommand() 方法。这时候我们大胆猜测 Service 已经启动了,打开手机应用管理查看,如图


service 启动.png

从此图明显看出 Service 确实启动成功了,同时也可以印证我们之前的观点:

** Service 不是运行在一个独立的进程中,而是依赖于创建该 Service 的 应用程序进程,一旦该程序被 kill ,Service 也随之停止运行**

这个时候我们再点击几次 starService 按钮,看看启动流程。

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted

啊?当我再次点击三次 startService ,从 log 可以清晰的看出。这时候 onCreate() 已经不再回调了,而是重复回调了三次 onStartCommand()。 这时查看手机应用管理查看该 Service 也确实还在。我们抱着怀疑的态度继续做下实验。

点击 stopService 执行停止 Service 的逻辑。

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted

看看 log 知道系统回调了 onDestroy() 方法。此时我们看下手机,啊?MyService 已经没了。这样我们销毁 Service成功。我们继续~

点击 starService

05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:37.532 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:47:42.776 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:09.794 32095-32095/? E/MyService: onStartCommand: excuted
05-18 14:48:58.970 32095-32095/? E/MyService: onDestroy: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onCreate: excuted
05-18 14:42:43.998 32095-32095/? E/MyService: onStartCommand: excuted

看看 log 。纳尼?又从头开始回调一遍,再看看手机中运行程序,卧槽?MyService 又回来了。ok,结论来了~

  • 当我们第一次启动 Service 时,Service 还不存在,系统会回调 onCreate() 创建Service ,同时回调 onStartCommand() 启动 Service。
  • Service 一旦被创建,假如你没有 手动调用 stopService() 去销毁它,或者 kill 程序。它就一直会处于后台进程中,当我们再次启动时,系统不会再去创建,而是直接启动,即回调 onStartCommand() 方法。

四、Service 与 Activity 通信

之前的 Service 启动方式似乎不那么灵活,我们新建一个服务是要他给我们干活的,然而现在我们只能 start 、stop 去启动、停止 Servive 。start 完之后 Service 就在那边不停的干活。怎么干活、干了多少 、做得如何 我们不得而知。那么我们就有必要换一种方式来启动 Service 了。让我们能够拥有主动权。

假设一个场景:我们需要启动一个 Service 去执行下载功能,我们要控制什么时候下载,知道下载进度。

仔细看看 MyService 中的代码 、好像一开始就有一个回调方法在那边没用呢?哈哈,就从这个方法进去搞事情,修改 MyService 中的代码。

public class MyService extends Service {
    private static final String TAG = "MyService";
    private DownloadBinder mBinder = new DownloadBinder();
    /**
     * 跟其他组件绑定时使用
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
  /**
     * Binder---IBinder的实现类,绑定Service 的时候,返回实例给调用方使用
     */
   public class DownloadBinder extends Binder {

        public void startDownload() {
            Log.e(TAG, "startDownload: excuted");
        }

        public int getProgress() {
            Log.e(TAG, "getProgress: excuted");
            return 0;
        }
    }

}

Binder 是 IBinder的实现类,绑定 Service 的时候,返回实例给调用方使用。我们自定义 Binder 类,在类中定义 public 方法,最终将 Binder 实例通过 onBind 返回给调用方,调用方只要有 Binder 实例就能随便调用它的公共方法。

那么 Activity (调用方) 需要拿到 Binder 实例。定义全局 DownloadBinder 。实例化
ServiceConnection 回调 onServiceConnected,onServiceDisconnected方法。仔细一看,onServiceConnected 携带 IBinder 参数。这个就是一旦我们绑定成功,Service 回传回来的实例,这时候通过向下转型成 DownloadBinder 即可。之后就可以随意控制方法调用啦~

private MyService.DownloadBinder mDownloadBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mDownloadBinder = ((MyService.DownloadBinder) service);
            mDownloadBinder.startDownload();
            mDownloadBinder.getProgress();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

修改布局 Button.点击 bindService 执行 bindService() 传入对应的 Intent,ServiceConnection实例,标志位(这里表示绑定之后自动创建Service)。

@OnClick({R.id.bind_service, R.id.unbind_service})
   public void onClick(View view) {
       switch (view.getId()) {
           case R.id.bind_service:
               Intent bindIntent = new Intent(this, MyService.class);
               bindService(bindIntent, mConnection, BIND_AUTO_CREATE);//绑定服务
               break;
           case R.id.unbind_service:
               unbindService(mConnection);//解除绑定
               break;
       }
   }

运行效果如下:

05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted

我们看到创建了 Service ,但是不会回调 onStartCommand 方法了。同时执行了下载的方法。
此时点击 unBinderService 按钮,看下log

05-18 17:59:43.440 23371-23371/com.sunnada.jpushdemo E/MyService: onCreate: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: startDownload: excuted
05-18 17:59:43.444 23371-23371/com.sunnada.jpushdemo E/MyService: getProgress: excuted
05-18 18:00:13.051 23371-23371/com.sunnada.jpushdemo E/MyService: onDestroy: excuted

此时回调 onDestroy() 方法 证明 Service 已经销毁。

注意

  • 我们一般在 activity 的 onDestroy() 生命周期函数中 unBinderServise 否则有内存泄漏的风险。
  • 假如 使用 starService 启动了 Service ,并 通过 bindService 又绑定启动 Service 这时必须同时调用 stopService,unBindService 才会销毁 Service。

五、前台 Service

前面我们讲的 Service 都是默默的在后台运行的,系统优先级较低,在内存吃紧的情况下,有可能被系统回收,而前台 Service 会一直有一个正在运行的图标在系统的状态栏显示,类似一个通知。
修改 MyService 代码:

/*
    * Service 第一次创建时回调
    */
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: excuted");
        Intent intent = new Intent(this, LoginActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
        Notification notification = new NotificationCompat
                .Builder(this)
                .setContentTitle("This is content title")
                .setContentText("This is content text")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pendingIntent)
                .build();
        startForeground(1,notification);
    }

这里使用 Builder 模式构造一个通知,最后调用 startForeground ,传入通知的 id ,Notification 实例化对象。让 MyService 变成一个前台服务,并且在状态栏显示。重新 run 程序,点击 BindService

前台服务.png

over~下拉通知栏可以看到明显的通知。

六、IntentService

Service 默认是执行在主线程的,如果需要执行耗时操作,必须开启子线程,我们一般的做法是在 onStartCommand 中开启子线程去执行。

 @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);
    }

但是 Service 一旦启动就会一直在后台运行,不断的执行耗时操作,这是相当消耗资源的,在这种情况下,通常在执行完具体逻辑之后,添加 stopSelf() 让 Service 停止。但是这种做法有时候会让我们忘记开线程,或者忘记 stopSelf ,为了解决这种情况,Android 特地提供了 IntentService 。
新建一个MyIntentService 继承 IntentService,注意别忘了注册

public class MyIntentService extends IntentService {

    private static final String TAG = "MyIntentService";

    public MyIntentService() {
        super("MyIntentService");
    }


    @Override
    protected void onHandleIntent(Intent intent) {
        Log.e(TAG, "onHandleIntent: " + Thread.currentThread().getName());
    }

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

一个空构造 调用父类有参构造,复写 onHandleIntent 抽象方法,直接就是子线程,可以直接执行耗时操作,而且 执行完之后
IntentService 会自动停止。下面修改 activity 按钮来测试看看。
run 程序,点击 starService,看 log

05-18 22:34:42.568 22842-24560/com.sunnada.jpushdemo E/MyIntentService: onHandleIntent: IntentService[MyIntentService]
05-18 22:34:42.569 22842-22842/com.sunnada.jpushdemo E/MyIntentService: onDestroy: excute

从 log 可以看出我们启动 IntentService 之后并没有调用 stop ,而
onDestroy 已经回调了,并且 onHandleIntent 确实是工作在子线程的。

over~由于篇幅的原因,Service 梳理就先到这了,后续会总结一些 Service 的实战应用。

声明:以上只是本人对于基础知识的梳理总结,部分参考自第一行代码第二版,如有不足之处,还望指出。

更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学

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

推荐阅读更多精彩内容