Android Service

一、基本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)
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值