最近由于工作调整,经常会在各地出差的路上,所以原创相对频率可能会慢些,当然空余时间还是会做为学习的输出,今天这篇主要就是介绍了Android的Service组件,Service做为四大组件之一,虽然没有Activity用的多,但是也会使用到,正好最近也是有个想法,先做的Demo技术验证。
为什么要用到Service?
A
其实主要原因是我这边做智能设备的,原来的App程序与硬件交互也都是整一个App下的Module实现,但是每一类的设备,可能对接的硬件不是完全一样,考虑想用单独的Service进程统一管理,App中只做业务逻辑的部分,与硬件的交互通过进程间的通讯完成,这样做的目的:减少App的包大小,因为硬件交互这块通讯成功基本很少有改动,改App里业务逻辑会需要调整或是UI界面修改,那就需要定期升级新版本,这样可以整个分享出来。
为什么要用前台服务?
A
早期写的Service都是后台运行的,而后台运行的Service优先级也相对较低 ,当系统内存不足时,在后台运行的Service有可能会被回收。而前台服务是用户可见的,并且系统内存不足时不允许系统杀死,前台服务还必须有一个状态栏的通知,只有服务被终止或从前台主动移除通知后才能被解除。
代码实现
微卡智享
01
创建Service
新建了一个有Activity的应用程序ServiceDemo,主要是程序的一些设置是需要有配置界面的,在Activity创建一个Service
MySerivce代码
package pers.vaccae.servicedemo
import android.app.*
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
const val TAG = "MyService"
const val MESSAGE_ACTION = "MESSAGE_ACTION"
const val MESSAGE_ID = "MESSAGE_ID"
class MyService : Service() {
private lateinit var mMsgRecv: MessageReceiver
override fun onCreate() {
super.onCreate()
Log.d(TAG, "service onCreate()")
//注册广播
mMsgRecv = MessageReceiver()
val mFilter = IntentFilter()
mFilter.addAction(MESSAGE_ACTION)
registerReceiver(mMsgRecv, mFilter)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "service onStartCommand()")
NotificationUtil.getInstance(this, MainActivity::class.java, packageName)
val notification = NotificationUtil.mNotifiCationBuilder
.setContentTitle("前台服务测试")
.setContentText("我是一个前台服务的Demo")
.setWhen(System.currentTimeMillis())
.setSmallIcon(androidx.loader.R.drawable.notification_bg)
.setContentIntent(NotificationUtil.mPendingIntent)
.build()
// 开始前台服务
startForeground(110, notification)
NotificationUtil.mNotificationManager.notify(1, notification)
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(intent: Intent): IBinder {
Log.d(TAG, "service onBind()")
TODO("Return the communication channel to the service.")
}
override fun onDestroy() {
Log.d(TAG, "service onDestroy")
//停止前台服务
stopForeground(true)
//终止广播
unregisterReceiver(mMsgRecv)
super.onDestroy()
}
}
代码中创建了一个广播MessageReceiver,用于Activity点击向服务中发送消息,通过广播实现的,并且跨进程中通讯也可以通过广播来实现。
MessageReceiver代码
package pers.vaccae.servicedemo
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class MessageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if(MESSAGE_ACTION == intent.action){
val messageid = intent.getStringExtra(MESSAGE_ID)
messageid?.let {
Log.d(TAG, it)
val notification = NotificationUtil.mNotifiCationBuilder
.setContentTitle("前台服务测试")
.setContentText(it)
.setWhen(System.currentTimeMillis())
.setSmallIcon(androidx.loader.R.drawable.notification_bg)
.setContentIntent(NotificationUtil.mPendingIntent)
.setSound(null)
.build()
NotificationUtil.mNotificationManager.notify(1, notification)
}
}
}
}
Receiver中接收到广播消息后,通过Notification中进行通知显示,在MyService中也用到了Notification,文章最初介绍前台服务时也说过前台服务还必须有一个状态栏的通知,只有服务被终止或从前台主动移除通知后才能被解除。
所以这里增加了一个NotificationUtil的类,将通知这里做了一个简单的封装,方便两边同时调用。
NotificationUtil代码
package pers.vaccae.servicedemo
import android.annotation.SuppressLint
import android.app.*
import android.content.Context
import android.content.Context.NOTIFICATION_SERVICE
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
/**
* 作者:Vaccae
* 邮箱:3657447@qq.com
* 创建时间:12:17
* 功能模块说明:
*/
class NotificationUtil {
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var mNotifiCationBuilder: NotificationCompat.Builder
lateinit var mNotificationManager: NotificationManager
@SuppressLint("StaticFieldLeak")
lateinit var mContext: Context
lateinit var mPendingIntent: PendingIntent
fun <T> getInstance(context: Context, clazz: Class<T>, packageName: String) {
mContext = context
mNotificationManager =
context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val intent = Intent(mContext, clazz)
mPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
packageName,
packageName,
NotificationManager.IMPORTANCE_HIGH
)
channel.enableLights(true)
channel.setShowBadge(true)
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
mNotificationManager?.let { mng ->
mng.createNotificationChannel(channel)
mNotifiCationBuilder =
NotificationCompat.Builder(mContext, "default")
.setChannelId(packageName)
}
} else {
mNotifiCationBuilder =
NotificationCompat.Builder(mContext, "default")
}
}
}
}
AndroidManifest中的配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="pers.vaccae.servicedemo">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ServiceDemo"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".MessageReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="MESSAGE_ACTION" />
</intent-filter>
</receiver>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="MY_SERVICE" />
</intent-filter>
</service>
</application>
</manifest>
上面前台的服务配置就完成了,我们在MainActivity中开启前台服务,并点击看看发送广播有没有变化。
MainActivity代码
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//启动前台服务
val srvintent = Intent(this, MyService::class.java)
srvintent.action = "MY_SERVICE"
startForegroundService(srvintent)
binding.tvtest.setOnClickListener {
//发送广播
val broadcast = Intent()
broadcast.action = "MESSAGE_ACTION"
broadcast.putExtra("MESSAGE_ID","我点击了tvtest组件")
sendOrderedBroadcast(broadcast,null)
}
}
}
实现效果
上图中可以看到,前台服务运行后,通知栏里显示了正在运行服务,点击TextView后,通知栏中也显示了点击的消息提示。一个简单的前台服务就这样完成了。
本来做这个的目的就是为了跨进程的通讯,所以接下来就是验证新建一个App发送广播后,当前的服务能否接收到。
新建一个testSrv,plugins设置为application
MainActivity代码
package pers.vaccae.testsrv
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import pers.vaccae.testsrv.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvmsg.setOnClickListener{
val broadcast = Intent()
broadcast.action = "MESSAGE_ACTION"
broadcast.putExtra("MESSAGE_ID","${packageName}中点击了tvmsg组件")
sendOrderedBroadcast(broadcast,null)
}
}
}
代码和ServiceDemo中的发送广播基本一个,只不过这里显示了点击时自己的包名,接下来看看运行效果。
实现效果