一、基本Service
需要在AndManifest.xml中声明
<service
android:name=".TestService"
android:enabled="true"
android:exported="true">
</service>
1.创建:继承Service类,必须重写onBind()方法,用于进程通信,倘若不需要进程通信则返回null即可;
2.启动/停止:startService(intent),stopService(intent), Service也可自动停止,在内部调用stopSelf()方法即可;
3.通信:参见https://editor.csdn.net/md/?articleId=129664300;一个Service既调用了StartService,又调用了bindService,需要要同时调用stopService和unbindService方法销毁;
class MyService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
super.onDestroy()
}
}
val intent = Intent(this, MyService::class.java)
startService(intent)
stopService(intent)
二、前台服务
从Android 8系统开始,只有当应用保持在前台可见状态的情况下,Service才能保证稳定运行,一旦应用进入后台之后,Service随时都有可能被系统回收。如果需要Service能够一直保持运行状态,可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会有一个正在运行的图标在系统的状态栏一直显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。
需要在AndroidManifest.xml中声明权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
class MyService : Service() {
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("normal", "Normal", NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
val intent = Intent()
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
val notification = NotificationCompat.Builder(this, "normal")
.setContentTitle("Title")
.setContentText("Fore Service")
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.mipmap.ic_launcher)
.build()
startForeground(1, notification)
}
}
启动前台服务
Intent intent = new Intent(this,ForegroundService::class)
startForegroundService(intent)
三、IntentService
执行完任务后自动停止,继承IntentService的类实现onHandleIntent(intent)方法,该方法中执行任务时自动在子线程中运行,不用担心ANR(Application Not Responding)。
class MyIntentService : IntentService("MyIntentService") {
override fun onHandleIntent(intent: Intent?) {
...
}
override fun onDestroy() {
super.onDestroy()
}
}
四、进程通信
1.service类
class TestService : Service() {
private var mChangeValue = 0
//该函数必须重写,暂时返回null
override fun onBind(intent: Intent): IBinder {
return null
}
override fun onUnbind(intent: Intent?): Boolean {
return super.onUnbind(intent)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("SERIVCE", "service process started")
}
override fun onDestroy() {
super.onDestroy()
android.os.Process.killProcess(android.os.Process.myPid())//杀死进程
Log.d("SERVICE", "service process stop")
}
}
2.创建进程通信的aidl,定义进程通信的接口传递信息
interface TestAidl {
int getChangeValue();
void setChangeValue(int changeValue);
void setListener(in ListenerAidl listenerAidl);
}
interface ListenerAidl {
int onChanged(int value);
}
3.service类里实现aidl接口
private lateinit var mRunnable: Runnable
private var mListener: ListenerAidl? = null
val handler = Handler()
//onStartCommand对象startService方式的开启,onBind对象bindService方式的开启,因此内容也可写在OnBind
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("SERIVCE", "service process started")
mRunnable = Runnable {
mChangeValue++
mListener?.onChanged(mChangeValue)
handler.postDelayed(mRunnable, 2000)
}
handler.postDelayed(mRunnable, 2000)
return super.onStartCommand(intent, flags, startId)
}
private var binder: Binder = object : TestAidl.Stub() {
override fun getChangeValue(): Int {
return mChangeValue
}
override fun setChangeValue(changeValue: Int) {
mChangeValue = changeValue
}
override fun setListener(listenerAidl: ListenerAidl?) {
if (listenerAidl != null) {
mListener = listenerAidl
}
}
}
4.C端通信
var mTestAidl: TestAidl? = null
val intent = Intent(applicationContext, TestService::class.java)
//开启、绑定
startService(intent)//可以不用,但相关初始化就需放在onBind内
this.bindService(intent, connection, Context.BIND_AUTO_CREATE)
//关闭、解绑
this.unbindService(connection)
private var connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mTestAidl = TestAidl.Stub.asInterface(service)
if (mTestAidl != null) {
Log.d("MAIN", "onServicesConnected")
//设置监听器以便随时获取得到service消息,即可不需要主动获取
mTestAidl!!.setListener(object : ListenerAidl.Stub() {
override fun onChanged(value: Int): Int {
return Log.d("MAIN", "CHANGE VALUE:$value")
}
})
}
}
//异常断开时调用
override fun onServiceDisconnected(name: ComponentName?) {
Log.d("MAIN", "onServicesDisconnected")
}
}
//主动获取可通过mTestAidl对象
mTestAidl.getChangeValue()
mTestAidl.setChangeValue(int)
五、无障碍服务
1.在AndroidManifest.xml文件中声明无障碍服务
<service
android:name=".MyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="无障碍服务"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />//配置文件,配置无障碍的一些属性
</service>
2.配置文件
在res目录下xml目录中新建xml配置文件
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagReportViewIds|flagIncludeNotImportantViews|flagRetrieveInteractiveWindows"
android:canPerformGestures="true"//指定可以通过无障碍服务模拟手势
android:canRetrieveWindowContent="true" />//指定可以通过无障碍获取窗口内容
EventTypes:指定要监听的事件类型
typeAllMask:所有事件,
typeViewClicked:视图点击事件,
typeViewFocused:视图获取焦点事件,
typeViewSelected:视图被选择事件,
typeViewTextChanged:视图文本内容发生变化事件,
typeWindowStateChanged:窗口状态发生变化事件,
typeNotificationStateChanged:通知状态发生变化事件,
typeViewLongClicked:视图长按事件,
typeWindowContentChanged:窗口内容发生变化事件,
typeTouchExplorationGestureEnd:触摸探索手势结束事件,
typeGestureDetectionEnd:手势检测结束事件。
Flags:无障碍服务的标志位
flagDefault:默认标志位,表示无障碍服务使用默认行为。
flagIncludeNotImportantViews:包括非重要视图。默认情况下,无障碍服务会忽略标记为非重要的视图,使用此标志位可以包括这些视图。
flagReportViewIds:报告视图的ID。使用此标志位,无障碍服务将报告视图的ID,以便辅助功能工具可以识别和处理特定的视图。
flagRequestEnhancedWebAccessibility:请求增强的网页辅助功能。当应用中存在WebView时,使用此标志位可以启用对网页内容的增强访问能力,以提供更好的辅助功能支持。
flagRequestFilterKeyEvents:请求过滤按键事件。当无障碍服务需要对按键事件进行过滤时,可以使用此标志位。
flagRequestTouchExplorationMode:请求触摸探索模式。使用此标志位,无障碍服务可以进入触摸探索模式,使用户能够通过滑动和探索屏幕来执行操作。
3.创建无障碍服务类
class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
// 在服务连接时执行初始化操作
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
// 处理接收到的无障碍事件
}
override fun onInterrupt() {
// 当服务中断时执行清理操作
}
}
4.执行模拟手势操作
通过点击特定位置坐标,通过时间设置可以实现点击和长按
public void performClick() {
GestureDescription.Builder builder = new GestureDescription.Builder();
Path p = new Path();
float x = 200.f;
float y = 200.f;
p.moveTo(x, y);
//p.lineTo(x1,y1);//可以实现滑动
builder.addStroke(new GestureDescription.StrokeDescription(p, 0L, 100L));
GestureDescription gesture = builder.build();
MyAccessibilityService.getService().dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
}
}, null);
}
5.执行模拟点击功能
通过获取页面信息执行点击
private AccessibilityNodeInfo findAccessibilityNodeInfosByViewId(AccessibilityNodeInfo rootNode, String resourceId) {
if (rootNode == null) {
return null;
}
if (resourceId.equals(rootNode.getViewIdResourceName())) {
return rootNode;
}
for (int i = 0; i < rootNode.getChildCount(); i++) {
AccessibilityNodeInfo childNode = rootNode.getChild(i);
AccessibilityNodeInfo targetNode = findAccessibilityNodeInfosByViewId(childNode, resourceId);
if (targetNode != null) {
return targetNode;
}
}
return null;
}
public boolean performClickNode(String nodeId) {
MyAccessibilityService service = MyAccessibilityService.getService();
AccessibilityNodeInfo rootNode = service.getRootInActiveWindow();
AccessibilityNodeInfo buttonNode = findAccessibilityNodeInfosByViewId(rootNode, nodeId);
if (null == buttonNode) return false;
boolean clickResult = buttonNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
return clickResult;
}
//返回上一级
public boolean performActionBack() {
MyAccessibilityService service = MyAccessibilityService.getService()
return service.performGlobalAction(GLOBAL_ACTION_BACK);
}
六、通知监听服务
1.AndroidManifest.xml
<service
android:name=".NotificationListener"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
2.创建并继承NotificationListenerService类
class NotificationListener : NotificationListenerService() {
private lateinit var mListener: messageListener
companion object {
private const val TAG = "NotificationListener"
private const val LISTEN_CALL = "com.android.incallui"
private const val LISTEN_TIM = "com.tencent.tim"
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
when (sbn?.packageName) {
LISTEN_CALL -> Log.d(TAG, "收到电话消息")
LISTEN_TIM -> Log.d(TAG, "收到TIM消息")
else -> Log.d(TAG, "收到未知类型消息")
}
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
super.onNotificationRemoved(sbn)
when (sbn?.packageName) {
LISTEN_CALL -> Log.d(TAG, "移除电话消息")
LISTEN_TIM -> Log.d(TAG, "移除TIM消息")
else -> Log.d(TAG, "移除未知类型消息")
}
}
override fun onListenerConnected() {
super.onListenerConnected()
Log.d(TAG, "建立监听连接")
}
override fun onListenerDisconnected() {
super.onListenerDisconnected()
Log.d(TAG, "监听连接断开")
}
}
3.启动监听
try {
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e: Exception) {
e.printStackTrace()
}
4.解析数据
if (sbn.getNotification().tickerText != null) {
Log.d(TAG, sbn.getNotification().tickerText.toString())//标题和内容一起程序,eg:小明:今天去打球吗?
}
//或
val extras = sbn.notification.extras
if (extras != null) {
// 获取通知标题
val title = extras.getString(Notification.EXTRA_TITLE, "")//eg:小明
Log.d(TAG, title)
// 获取通知内容
val content = extras.getString(Notification.EXTRA_TEXT, "")//eg:今天去打球吗?
Log.d(TAG,content)
}
七、悬浮窗
1.配置
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<service
android:name=".FloatingWindowService"
android:enabled="true"
android:exported="true">
</service>
2.布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<FrameLayout
android:id="@+id/layout_drag"
android:layout_width="match_parent"
android:layout_height="15dp"
android:background="#dddddd">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_close"
android:layout_width="15dp"
android:layout_height="15dp"
android:background="#111111"
android:layout_gravity="end" />
</FrameLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:background="#eeeeee"
android:scrollbars="vertical" />
</LinearLayout>
3.悬浮窗设置
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var tvContent: AppCompatTextView
private var floatingView: View? = null
private val stringBuilder = StringBuilder()
private var x = 0
private var y = 0
// 获取windowManager并设置layoutParams
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
gravity = Gravity.START or Gravity.TOP
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
width = 300
height = 500
}
if (Settings.canDrawOverlays(this)) {
//新建悬浮窗事件
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_float_view, null)
//文字显示控件
tvContent = floatingView!!.findViewById(R.id.tv_content)
//关闭悬浮窗
floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
windowManager.removeView(floatingView)
}
// 设置TextView滚动
tvContent.movementMethod = ScrollingMovementMethod.getInstance()
//设置悬浮窗移动
floatingView!!.findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.rawX.toInt()
y = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
val currentX = event.rawX.toInt()
val currentY = event.rawY.toInt()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
windowManager.addView(floatingView, layoutParams)
}
all code
class MainActivity : AppCompatActivity() {
private lateinit var buttonSend: Button
private lateinit var buttonView: Button
private val TAG = "MainActivity"
@SuppressLint("MissingInflatedId", "UnspecifiedRegisterReceiverFlag")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonSend = findViewById(R.id.buttonClick)
buttonSend.setOnClickListener {
sendMessage()
}
buttonView = findViewById(R.id.buttonView)
buttonView.setOnClickListener {
startWindow()
}
}
//发送广播到悬浮窗
private fun sendMessage() {
Intent("android.intent.action.MyReceiver").apply {
putExtra("content", "float view test!")
sendBroadcast(this)
}
}
//检查悬浮窗权限是否打开,若没有打开则打开系统设置页面
private fun startWindow() {
if (!Settings.canDrawOverlays(this)) {
//Settings.ACTION_APPLICATION_DETAILS_SETTINGS 该应用程序的设置界面
startActivityForResult(
Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
), 0
)
} else {
startService(Intent(this, FloatingWindowService::class.java))
}
}
//请求权限
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, "悬浮窗权限授权成功", Toast.LENGTH_SHORT).show()
startService(Intent(this, FloatingWindowService::class.java))
}
}
}
}
class FloatingWindowService : Service() {
private lateinit var windowManager: WindowManager
private lateinit var layoutParams: WindowManager.LayoutParams
private lateinit var tvContent: AppCompatTextView
private lateinit var handler: Handler
private var receiver: ViewReceiver? = null
private var floatingView: View? = null
private val stringBuilder = StringBuilder()
private var x = 0
private var y = 0
private var floatView = false
@SuppressLint("UnspecifiedRegisterReceiverFlag")
override fun onCreate() {
super.onCreate()
// 注册广播
receiver = ViewReceiver()
val filter = IntentFilter()
filter.addAction("android.intent.action.MyReceiver")
registerReceiver(receiver, filter);
// 获取windowManager并设置layoutParams
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
layoutParams = WindowManager.LayoutParams().apply {
type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY//显示于所有应用之上
} else {
WindowManager.LayoutParams.TYPE_PHONE
}
gravity = Gravity.START or Gravity.TOP
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
width = 300
height = 500
}
handler = Handler(this.mainLooper) { msg ->
tvContent.text = msg.obj as String
// 当文本超出屏幕自动滚动,保证文本处于最底部
val offset = tvContent.lineCount * tvContent.lineHeight
floatingView?.apply {
if (offset > height) {
tvContent.scrollTo(0, offset - height)
}
}
false
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
@SuppressLint("ClickableViewAccessibility")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (Settings.canDrawOverlays(this)) {
//新建悬浮窗事件
floatingView = LayoutInflater.from(this).inflate(R.layout.layout_float_view, null)
//文字显示控件
tvContent = floatingView!!.findViewById(R.id.tv_content)
//关闭悬浮窗
floatingView!!.findViewById<AppCompatImageView>(R.id.iv_close).setOnClickListener {
stringBuilder.clear()
windowManager.removeView(floatingView)
floatView = false
}
// 设置TextView滚动
tvContent.movementMethod = ScrollingMovementMethod.getInstance()
//设置悬浮窗移动
floatingView!!.findViewById<FrameLayout>(R.id.layout_drag).setOnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
x = event.rawX.toInt()
y = event.rawY.toInt()
}
MotionEvent.ACTION_MOVE -> {
val currentX = event.rawX.toInt()
val currentY = event.rawY.toInt()
val offsetX = currentX - x
val offsetY = currentY - y
x = currentX
y = currentY
layoutParams.x = layoutParams.x + offsetX
layoutParams.y = layoutParams.y + offsetY
windowManager.updateViewLayout(floatingView, layoutParams)
}
}
true
}
windowManager.addView(floatingView, layoutParams)
floatView = true
}
return super.onStartCommand(intent, flags, startId)
}
override fun onDestroy() {
// 注销广播并删除浮窗
unregisterReceiver(receiver)
receiver = null
if (floatView) {
windowManager.removeView(floatingView)
}
}
inner class ViewReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val content = intent.getStringExtra("content") ?: ""
stringBuilder.append(content).append("\n")
val message = Message.obtain()
message.what = 0
message.obj = stringBuilder.toString()
handler.sendMessage(message)
}
}
}