《第一行代码 第3版》学习笔记——第十章 Service

1 Service概述

  • Service是Android中实现程序后台运行的解决方案,适合执行那些不需要和用户交互且长期运行的任务。
  • Service通常是运行在创建Service的那个应用进程中,而不是在单独的进程中运行。但是可以通过配置来让Service运行于单独的进程中。
  • Service通常是需要自己创建线程来运行耗时操作,否则是在主线程中运行,会导致主线程被阻塞的情况出现。

2 Android多线程编程

2.1 多线程开启方式

  • 方式一
    是使用Java中的多线程写法。
    创建类继承Thread类并重写run方法。
class MyThread : Thread() {
	override fun run() {
		//运行逻辑
	}
}

调用start方法启动线程执行。

MyThread().start()
  • 方式二
    使用继承耦合性会比较高,更多的是选择Java中的另一种方式,实现Runnable接口
class MyThread : Runnale {
	override fun run() {
		//运行逻辑
	}
}

启动线程

val myThread = MyThread()
Thread(myThread).start()
  • 方式三
    使用lambda表达式
Thread {
	//执行逻辑
}
  • 方式四
    方式四是kotlin中的独有方式,调用thread这个顶层函数,传入的是一个lambda表达式,表达式中书写线程执行逻辑
thread {
	//执行逻辑
}

2.2 子线程中更新UI

Android中的UI线程是非线程安全的,所以规定所有的UI操作都在主线程中执行,如果在子线程中执行UI的更新操作,则会报异常。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.bt1).setOnClickListener {
            thread {
                findViewById<TextView>(R.id.tv1).text = "Nice to meet you"
            }
        }
    }
}

通过点击按钮触发TextView的文字替换,报错
在这里插入图片描述
只能在原始线程中才能触及这个View,所以规定只能在主线程中修改UI。
Android中提供了一种异步消息处理机制

	val updateText = 1

    val handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            when(msg.what) {
                updateText -> {
                    findViewById<TextView>(R.id.tv1).text = "Nice to meet you"
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.bt1).setOnClickListener {
            thread {
                val msg = Message()
                msg.what = updateText
                handler.sendMessage(msg)
            }
        }
    }

使用Handler机制,进行线程间的消息传递。子线程构造Message,通过主线程的Handler进行发送,消息进入Looper中循环,主线程从Looper中取出消息进行处理。

2.3 异步消息处理机制

Android中的异步消息处理主要由4部分组成:Message、Handler、MessageQueue和Looper。
1、Message
Message是线程之间传递的消息,可以携带少量的信息在线程之前传递。
2、Handler
消息的处理者,主要用来发送和处理消息。
3、MessageQueue
消息队列,用来存放所有通过Handler发送的消息,一个线程只有一个MessageQueue。
4、Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop方法后,Looper会进入一个无限循环中,每当发现MessageQueue中有一个消息就会取出并传递给Handler进行处理。每个线程只有一个Looper对象。

3 Service基本用法

public class MyService extends Service {
    public MyService() {
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

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

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

Service继承自系统的Service类,这里重写了onCreate、onStartCommand、onDestroy和onBind方法。onCreate方法会在Service创建的时候调用,onStartCommand方法会在每次Service启动的时候调用,onDestroy会在Service销毁的时候调用。
除此之外,和Activity一样,Service要使用需要在AndroidMenifest.xml中注册。

<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"></service>

在Android Studio中右键创建Service会自动在AndroidMenifest.xml中注册。

3.1 启动和停止Service

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.startServiceBtn).setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            startService(intent)
        }
        findViewById<Button>(R.id.stopServiceBtn).setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            stopService(intent)
        }
    }
}

使用startService启动Service,使用stopService停止Service,传入的参数是Intent。

2022-09-25 23:03:45.232 3107-3107/com.xy.servicetest D/MyService: onCreate: executed
2022-09-25 23:03:45.240 3107-3107/com.xy.servicetest D/MyService: onStartCommand: executed
2022-09-25 23:03:48.108 3107-3107/com.xy.servicetest D/MyService: onDestroy: executed

startService调用时,会回调onCreate和onStartCommand方法。stopService调用时,会回调onDestroy方法。
Android8.0开始,应用的后台功能被大幅削减,现在只有在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台后,Service随时可能被系统回收。如果需要长期在后台执行一些任务,可以使用前台Service或者WorkManager。
onCreate方法是在Service第一次创建的时候调用,而onStartCommand方法则在每次启动Service的时候都会调用。

3.2 Activity与Service进行通信

Activity与Service之间通信需要依靠Binder来实现,模拟一个下载场景,service执行逻辑,在Activity中启动。
Service代码

class MyService : Service() {

    private val mBinder = DownloadBinder()

    class DownloadBinder : Binder() {
        fun startDownload() {
            Log.d("MyService", "startDownload: executed")
        }
        fun getProgress():Int {
            Log.d("MyService", "getProgress: executed")
            return 0
        }
    }

    override fun onBind(intent: Intent): IBinder {
        return mBinder
    }

    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "onCreate: executed")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.d("MyService", "onStartCommand: executed")
        return super.onStartCommand(intent, flags, startId)
    }

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

Service中创建了Binder的子类,并定义了两个方法,启动下载和获取进度,这里由于是模拟,只是加上了打印。在onBind方法中返回了DownloadBinder 的实例。这样当Activity绑定Service后,就可以通过这个Binder来与Service进行通信了。
Activity代码

class MainActivity : AppCompatActivity() {

    lateinit var downloadBinder: MyService.DownloadBinder

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            downloadBinder = service as MyService.DownloadBinder
            downloadBinder.startDownload()
            downloadBinder.getProgress()
        }

        override fun onServiceDisconnected(name: ComponentName?) {
        }

    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.bindService).setOnClickListener {
            val intent = Intent(this, MyService::class.java)
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
        findViewById<Button>(R.id.unBindService).setOnClickListener {
            unbindService(connection)
        }
    }
}

Activity中创建一个ServiceConnection 的匿名内部类对象,并在这个类中重写了onServiceConnected会在bindService方法调用时回调。onServiceDisconnected这个方法不太常用,只有在Service的创建进程崩溃或者被杀掉时才会调用。BIND_AUTO_CREATE这个参数表示Activity和Service进行绑定后自动创建Service。
调用bindService和unbindService方法后的打印

2022-09-26 21:53:15.468 3317-3317/com.xy.servicetest D/MyService: onCreate: executed
2022-09-26 21:53:15.481 3317-3317/com.xy.servicetest D/MyService: startDownload: executed
2022-09-26 21:53:15.482 3317-3317/com.xy.servicetest D/MyService: getProgress: executed
2022-09-26 21:53:24.200 3317-3317/com.xy.servicetest D/MyService: onDestroy: executed

4 Service生命周期

在这里插入图片描述
调用startService方法后,Service就被启动了,如果之前没有启动过,就会调用onCreate方法,然后调用onStartCommand方法,如果已经启动过,则直接调用onStartCommand方法。Service启动后,一直处于运行状态,知道stopService或stopSelf方法被调用或是Service被系统回收,此时会回调onDestroy方法。
还可以调用Context的bindService方法来获取一个持久的连接,此时会回调onBind方法。如果Service还未创建,onCreate方法会在onBind方法之前调用,调用onBind会返回一个IBinder类型的对象用于和Service进行通信。只要Activity没有断开连接,Service就一直在运行,直到被系统回收。调用unbindService方法后会回调onUnbind方法,并调用onDestroy方法销毁Service。
startService与bindService并不冲突,启动Service之后也可以绑定Service进行通信,只是不会创建Service实例,Service实例只有一个。但是需要注意的是,当startService和bindService都调用后,必须等stopService和unbindService都调用后Service才会销毁。

5 Service的更多技巧

5.1 使用前台Service

由于普通Service进入后台之后随时有可能被系统所回收,所以如果想要你的Service一直运行,则需要将service设置为前台service,前台service会一直在状态栏有一个图标显示,且不会被系统回收。

override fun onCreate() {
    super.onCreate()
    Log.d(TAG, "onCreate: executed")
    val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channel = NotificationChannel(
            "my_service", "前台Service通知",
            NotificationManager.IMPORTANCE_DEFAULT
        )
        manager.createNotificationChannel(channel)
    }
    val intent = Intent(this, MainActivity::class.java)
    val pi = PendingIntent.getActivity(this, 0, intent, 0)
    val notification = NotificationCompat.Builder(this, "my_service")
        .setContentTitle("This is content title")
        .setContentText("This is content text")
        .setSmallIcon(R.drawable.small_icon)
        .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.large_icon))
        .setContentIntent(pi)
        .build()
    startForeground(1, notification)
}

调用startForeground方法使Service成为前台Service,该方法接收两个参数,第一个参数是通知的id,第二个是一个Notification对象。
Android9开始前台服务要求需要申请权限android.permission.FOREGROUND_SERVICE

java.lang.RuntimeException: Unable to create service com.xy.servicetest.MyService2: java.lang.SecurityException: Permission Denial: startForeground from pid=8960, uid=10125 requires android.permission.FOREGROUND_SERVICE

5.2 使用IntentService

Service是在主线程中执行的,如果执行一些耗时操作会导致ANR,所以应该开启子线程执行业务逻辑。但是Service一旦启动了就会一直处于运行状态,调用stopSelf方法才会停止运行,所以通常的写法为:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    thread { 
        //业务逻辑
        stopSelf()
    }
    return super.onStartCommand(intent, flags, startId)
}

业务执行完成后调用stopSelf方法销毁Service。但是很多人会忘记调用该方法,所以出现了IntentService。

class MyService3 : IntentService("MyService3") {
    override fun onHandleIntent(intent: Intent?) {
        Log.d("MyService3", "onHandleIntent: ")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyService3", "onDestroy: ")
    }

}

启动的方式是一样的,当startService启动MyService3时,可以看到打印

D/MyService3: onHandleIntent: 
D/MyService3: onDestroy: 

IntentService构造函数中传入的字符串只在调试的时候有用,可以随意指定。需要重写onHandleIntent方法,在其中写入耗时的业务逻辑。从打印中可以看出startService后,业务代码执行完成会自动调用onDestroy方法。onHandleIntent中执行的已经是在子线程中执行了,可以自行添加打印验证。
但是现在IntentService已经被废弃了,建议使用WorkManager或者JobIntentService来将业务逻辑看成是一项工作而不是一个服务执行。

6 补充

一些小知识点在这里进行补充,使用Java编写

6.1 ANR问题

前台Service 20s,后台Service 200s。所以Service的耗时处理逻辑需要放到子线程中运行,如果不开启子线程,则在主线程中运行耗时操作可能会导致ANR,可以做个测试:

try {
 Thread.sleep(60000);
} catch (InterruptedException e) {
 e.printStackTrace();
}

这里在onCreate方法中延时60s,运行startService或者bindService,由于主线程阻塞,会报出ANR:
在这里插入图片描述
除了在子线程中执行耗时操作外,还可以让service以单独进程运行来解决。

<service
    android:name=".MyService"
    android:process=":remote" />

但此时,如果使用bindService将Activity与Service进行绑定通信就会出错,因为他们属于不同的进程了,报错如下:
在这里插入图片描述
Binder代理无法转换成自定义的Binder类型。此时就需要使用aidl在进程间进行通信。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值