10.5--Service 的更多技巧

以上所学的都是关于服务最基本的一些用法和概念,当然也是最常用的。不过,仅仅满足于此显然是不够的,关于的更多高级使用技巧还在等着我们呢,下面就赶快去看一看吧。

 

10.5.1 使用前台Service

前面已经说过,从Android 8.0 系统开始,只有当应用保持在前台可见状态的情况下,Service 才能保证稳定运行,一旦应用进入后台之后,Service 随时都有可能被系统回收。而如果你希望Service 能够一直保持运行状态,就可以考虑使用前台Service。前台Service 和普通Service 最大的区别就在于,它一直会有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

由于状态栏中一直有一个正在运行的图标,相当于我们在应用以另外一种形式保持在前台可见状态,所以系统不会倾向于回收前台Service。另外,用户也可以通过下拉状态兰清楚地知道当前什么应用正在运行,因此也不存在某些恶意应用长期在后台偷偷占用手机资源的情况。

那么我们就来看一下如何才能创建一个前台服务吧,其实并不复杂,修改MyService中的代码,如下所示:

可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。没错!这就是我们在第9章中学习的创建通知的方法,并且我还将small_icon 和 large_icon 这两张图从NotificationTest 项目中复制过来。只不过这次在构建出Notification对象后并没有使用NotificationManager 来将通知显示出来,而是调用了startForeground()方法。

这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法的第一个参数;

第二个参数则是构建出的Notification对象。调用startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来。

另外,从Android9.0系统开始,使用前台Service 必须在Androidmanifest.xml 文件进行权限声明才行,如下所示:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台服务的模式启动了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容。

现在即使你退出应用程序,MyService 也会一直处于运行状态,而且不用担心会被系统回收。当然,MyService所对应的通知也会一直显示在状态栏上面。如果用户不希望我们的程序一直运行,也可以选择手动杀掉应用,这样MyService 就会跟着一起停止运行了。

前台服务的用法就这么简单,只要你在第9章中将通知的用法掌握好了,学习本节的知识一定会特别轻松。

 

10.5.2 使用IntentService

话说回来,在本章一开始的时候我们就已经知道,Service 中的代码都是默认运行在主线程当中的,如果直接在Service 里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。

所以这个时候就需要用到Android多线程编程的技术了,我们应该在Service 的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此,一个比较标准的Service 就可以写成如下形式:

class MyService : Service() {

    ...

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("MyService","onStartCommand executed")
        thread {
            // 处理具体的逻辑
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

但是,这种Service 一旦启动,就会一直处于运行状态,必须调用stopService() 或 stopSelf() 方法,或者被系统回收,Service 才会停止。所以,如果想要实现一个让Service 在执行完毕后自动停止的功能,就可以这样写:

class MyService : Service() {

    ...

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("MyService","onStartCommand executed")
        thread {
            // 处理具体的逻辑
            stopSelf()
        }
        return super.onStartCommand(intent, flags, startId)
    }
}

虽说这种写法并不复杂,但是总会有一些程序员忘记开启线程,或者忘记调用stopSelf() 方法。为了可以简单地创建一个异步的、会自动停止的Service ,Android专门提供了一个IntentService类,这个类就很好地解决了前面所提到的两种尴尬,下面我们就来看一下它的用法。

新建一个MyIntentService类继承自IntentService,代码如下所示:

class MyIntentService : IntentService("MyIntentService") {
    override fun onHandleIntent(intent: Intent?) {
        //打印当前线程的id
        Log.d("MyIntentService","Thread id is"+Thread. currentThread().name)
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService","onDestroy executed")
    }
}

这里首先要求必须先调用父类的构造函数,并传入一个字符串,这个字符串可以随意指定,只在调试的时候有用。然后要在子类中去实现onHandleIntent() 这个抽象方法,在这个方法中可以去处理一些耗时逻辑,而且不用担心ANR的问题,因为这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHandleIntent() 方法中打印了当前线程的名。另外根据 IntentService 的特性,这个Service在运行结束后应该是会自动停止的,所以我们又重写了onDestroy() 方法,在这里也打印了一行日志,以证实Service是不是停止掉了。

接下来修改activity_main.xml中的代码,加入一个用于启动MyIntentService这个服务的按钮,如下所示:

<?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=".MainActivity">

    <Button
        android:id="@+id/startService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Service"
        />
    <Button
        android:id="@+id/stopService"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Stop Service"
        />

    <Button
        android:id="@+id/bindServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Bind Service"
        />
    <Button
        android:id="@+id/unbindServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Unbind Service"
        />
    <Button
        android:id="@+id/startIntentServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start IntentService"
        />

</LinearLayout>

然后修改MainActivity中的代码,如下所示:

        startIntentServiceBtn.setOnClickListener {
            Log.d("MainActivity","Thread is ${Thread.currentThread().name}")
            val intent = Intent(this,MyIntentService::class.java)
            startService(intent)
        }

可以看到,我们在Start IntentService 按钮的点击事件里面去启动MyIntentService这个服务,并在这里打印了一下主线程名,稍后用于和IntentService进行比对。你会发现,其实IntentService的用法和普通的Service没什么两样。

最后不要忘记,服务都是需要在AndroidManifest.xml里注册的,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.servicetest">
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true"></service>
        <service
            android:name=".MyIntentService"
            android:exported="true"
            android:enabled="true"/>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

当然你也可以使用Android Studio提供的快捷方式来创建IntentService,不过由于这样会自动生成一些我们用不到的代码,因此这里我采用了手动创建的方式。

现在重新运行一下程序,点击Start IntentService按钮后,观察logcat中的打印日志:

可以看到,不仅MyIntentService 和 MainActivity 所在的线程名不一样,而且onDestroy() 方法也得到了执行,,说明MyIntentService在运行完毕后确实自动停止了。集开启线程和自动停止于一身,IntentService还是博得了不少程序员的喜爱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值