首页 > 其他分享 >Android NotificationListenerService的实操记录

Android NotificationListenerService的实操记录

时间:2024-09-19 20:21:17浏览次数:12  
标签:void NotificationListenerService public 实操 new Override import Android android

文章目录

背景介绍

Android在4.3的版本中(即API 18)加入了NotificationListenerService,根据SDK的描述(AndroidDeveloper)可以知道,当系统收到新的通知或者通知被删除时,会触发NotificationListenerService的回调方法。同时在Android 4.4 中新增了Notification.extras 字段,也就是说可以使用NotificationListenerService获取系统通知具体信息,这在以前是需要用反射来实现的

主要方法

NotificationListenerService主要方法(成员变量):
getActiveNotifications() :返回当前系统所有通知到StatusBarNotification[];
onNotificationPosted(StatusBarNotification sbn) :当系统收到新的通知后出发回调;
onNotificationRemoved(StatusBarNotification sbn) :当系统通知被删掉后出发回调;

技术细节

话不多说,直接实操,首先新建个Android项目,现在新版Android Studio已经默认使用Kotlin语言,但我对Kotlin不熟悉,现在先用Java,后面有空再换成Kotlin实现
选择了Basic Views Activity在这里插入图片描述在这里插入图片描述
android studio默认使用gradle构建工具,若嫌弃官网下载太慢,可更换腾讯镜像https://mirrors.cloud.tencent.com/gradle/在这里插入图片描述
好多年没写Android代码了,来看看有什么变化。AndroidManifest.xml基本没什么变化

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <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.NotificationMoitor"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.NotificationMoitor">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

再看看MainActivity.java,代码如下

package com.ths.notificationmoitor;

import android.os.Bundle;

import com.google.android.material.snackbar.Snackbar;

import androidx.appcompat.app.AppCompatActivity;

import android.view.View;

import androidx.core.view.WindowCompat;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;

import com.ths.notificationmoitor.databinding.ActivityMainBinding;

import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends AppCompatActivity {

    private AppBarConfiguration appBarConfiguration;
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        setSupportActionBar(binding.toolbar);

        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);

        binding.fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAnchorView(R.id.fab)
                        .setAction("Action", null).show();
            }
        });


    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
        return NavigationUI.navigateUp(navController, appBarConfiguration)
                || super.onSupportNavigateUp();
    }
}

这个变化有点大了,androidx替代了原来的support v4,v7扩展包,ActivityMainBinding是新引入的视图绑定,早期版本是 setContentView(R.layout.activity_main);项目新建后默认有两个fragment(FirstFragment,SecondFragment),在MainActivity中未找到哪里设置默认显示FirstFragment,经过一番分析,发现在res目录下navigation/nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/FirstFragment">



    <fragment
        android:id="@+id/FirstFragment"
        android:name="com.ths.notificationmoitor.FirstFragment"
        android:label="@string/first_fragment_label"
        tools:layout="@layout/fragment_first">

        <action
            android:id="@+id/action_FirstFragment_to_SecondFragment"
            app:destination="@id/SecondFragment" />
    </fragment>
    <fragment
        android:id="@+id/SecondFragment"
        android:name="com.ths.notificationmoitor.SecondFragment"
        android:label="@string/second_fragment_label"
        tools:layout="@layout/fragment_second">

        <action
            android:id="@+id/action_SecondFragment_to_FirstFragment"
            app:destination="@id/FirstFragment" />
    </fragment>

</navigation>

猜想这里是定义了fragment,然后指定第一个显示的fragment,于是新建fragment_main.xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainFragment">
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:background="@color/white"
        android:layout_height="match_parent"
        android:padding="16dp">
        <androidx.appcompat.widget.AppCompatButton
            android:text="启动服务"
            android:id="@+id/button_start_service"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

        </androidx.appcompat.widget.AppCompatButton>
    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.core.widget.NestedScrollView>

接着再新建MainFragment.java文件,内容如下

package com.ths.notificationmoitor;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;

import com.ths.notificationmoitor.databinding.FragmentMainBinding;

public class MainFragment extends Fragment {
    private FragmentMainBinding binding;

    @Override
    public View onCreateView(
            LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState
    ) {

        binding = FragmentMainBinding.inflate(inflater, container, false);
        return binding.getRoot();

    }

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        binding.buttonStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}

接着修改刚才找到的 nav_graph.xml文件,新增

<fragment
        android:id="@+id/MainFragment"
        android:name="com.ths.notificationmoitor.MainFragment"
        android:label="Main Fragment"
        tools:layout="@layout/fragment_main" />

顶部修改默认显示项

app:startDestination="@id/MainFragment"

修改完毕,运行验证
在这里插入图片描述
验证成功。
新建NotificationService.java文件,继承NotificationListenerService

package com.ths.notificationmoitor;

import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;

public class NotificationService extends NotificationListenerService {
    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        super.onNotificationPosted(sbn);
        //通知来源包名
        String notificationPkg = sbn.getPackageName();
        Bundle extras = sbn.getNotification().extras;
        // 获取接收消息的抬头
        String notificationTitle = extras.getString(Notification.EXTRA_TITLE);
        // 获取接收消息的内容
        String notificationText = extras.getString(Notification.EXTRA_TEXT);

        Toast.makeText(this, notificationPkg+notificationTitle+notificationText, Toast.LENGTH_LONG).show();
   
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        super.onNotificationRemoved(sbn);
    }

    @Override
    public StatusBarNotification[] getActiveNotifications() {
        return super.getActiveNotifications();
    }


}

修改AndroidManifest.xml,与Acitivity同级配置

 <service android:name=".NotificationService" />

启动service

binding.buttonStartService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(),NotificationService.class);
                requireActivity().startService(intent);
            }
        });

点击启动按钮,启动服务
在这里插入图片描述

下面我们来模拟发送通知,看能不能监听到,我们再新建个服务来模拟发送通知,代码如下

package com.ths.notificationmoitor;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

import java.text.SimpleDateFormat;

public class SendNotificationService extends Service {
    private static NotificationManager mNotiManager;
    private static Notification mNotification;
    private int noticeId = 0;
    private final Handler mHandler = new Handler();
    @SuppressLint("SimpleDateFormat")
    private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

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

    private final String[] smsTemplate = new String[]{"出来玩啊,有妹纸。", "哥,手头紧,借点钱。","哥,洗脚去呀。","哥,老地方,来喝酒。","哥,出来唱歌。"};

    private void createNotification() {
        String template = smsTemplate[(int) ((Math.random() * 9 + 1) * 10) % smsTemplate.length];
        String mockSms = template + simpleDateFormat.format(System.currentTimeMillis());
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, getPackageName());
        builder.setWhen(System.currentTimeMillis());
        builder.setSmallIcon(android.R.drawable.stat_notify_chat);
        builder.setPriority(Notification.PRIORITY_MAX);
        builder.setContentTitle("张三");
        builder.setContentText(mockSms);
        mNotification = builder.getNotification();
        mNotification.flags = Notification.FLAG_AUTO_CANCEL;
        mNotification.defaults = Notification.DEFAULT_SOUND;

        try {
            mNotiManager.notify(noticeId, mNotification);
        } catch (Exception e) {
            e.printStackTrace();
        }
        noticeId++;
    }


    Runnable noticeTask = new Runnable() {
        @Override
        public void run() {
            createNotification();
            mHandler.postDelayed(noticeTask, 1000 * 30);
        }
    };

    public void startSendNotice() {
        mNotiManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mNotiManager.createNotificationChannel(new NotificationChannel(getPackageName(), "通知", NotificationManager.IMPORTANCE_HIGH));//第三个参数,重要程度
        }
        mNotification = new Notification();
        mHandler.post(noticeTask);
    }

    public void stopSendNotice() {
        noticeId = 0;
        mHandler.removeCallbacks(noticeTask);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

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


此时,已完成通知消息的模拟发送,要完成通知的监听,监听服务需要增加通知监听的权限配置

<service
            android:name=".NotificationService"
            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
            android:exported="true">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

由于此权限较为敏感,需要要求用户手动点击才能开启,因此在主界面增加个按钮,引导用户点击开启,代码如下

 binding.buttonOpenPermission.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!isEnabled()) {
                    startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
                } else {
                    Toast toast = Toast.makeText(requireActivity(), "监控器开关已打开", Toast.LENGTH_SHORT);
                    toast.show();
                }
            }
        });
 // 判断是否打开了通知监听权限
    private boolean isEnabled() {
        String pkgName = requireActivity().getPackageName();
        final String flat = Settings.Secure.getString(requireActivity().getContentResolver(), "enabled_notification_listeners");
        if (!TextUtils.isEmpty(flat)) {
            final String[] names = flat.split(":");
            for (String name : names) {
                final ComponentName cn = ComponentName.unflattenFromString(name);
                if (cn != null) {
                    if (TextUtils.equals(pkgName, cn.getPackageName())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

运行效果如下
在这里插入图片描述

在这里插入图片描述
到这里,通知消息监听核心代码已完成,后面可根据自己需求做更完善的开发。

标签:void,NotificationListenerService,public,实操,new,Override,import,Android,android
From: https://blog.csdn.net/xs_2012/article/details/142358127

相关文章

  • 《现代操作系统》第10章——实例研究1:UNIX、Linux和Android
    《现代操作系统》第10章——实例研究1:UNIX、Linux和Android10.1UNIX与Linux的历史第一次使UNIX的两种流派一致的严肃尝试来源于IEEE(它是一个得到高度尊重的中立组织)标准委员会的赞助。有上百名来自业界、学界以及政府的人员参加了此项工作。他们共同决定将这个项目......
  • Android中如何调用DLL文件
    在Android设备上直接调用DLL(动态链接库)文件是不可行的,因为DLL文件是Windows操作系统下的一种可执行文件格式,而Android操作系统基于Linux内核,两者在底层架构和API支持上存在根本差异。不过,如果你需要在Android应用中使用在Windows下开发的某些功能或库,有几种可......
  • c#随机数 猜数字 彩票兑奖实操案例
    同学们今天给大家带来一个利用随机数,while循环语句,for语句,if判断语句制作的一个彩票兑奖的一个小案例,本文适合c#零基础的同学进行实操,所以用到的知识不是很多,主要学习for循环语句和if判断语句来进行程序的编写。写代码前我们需要明确我们要实现什么功能。1.用户需要输入0—......
  • 迅为RK3588开发板支持Android13和12版本系统还有Debian11、Buildroot、Ubuntu20与22版
    我们已经在RK3588上开发了稳定又好用的Android13和12版本系统Debian11、Buildroot、Ubuntu20与22版本、银河麒麟、开放麒、统信系统、openEuler24.03系统,内核Linux5.10版本。......
  • Android插件化(二)基础之类加载器
    Android插件化(二)基础之类加载器1.什么是ClassLoader当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中......
  • 【Abyss】Android 平台应用级系统调用拦截框架
    Android平台从上到下,无需ROOT/解锁/刷机,应用级拦截框架的最后一环——SVC系统调用拦截。☞Github☜由于我们虚拟化产品的需求,需要支持在普通的Android手机运行。我们需要搭建覆盖应用从上到下各层的应用级拦截框架,而Abyss作为系统SVC指令的调用拦截,是我们最底层的终极......
  • Android实践:读取和处理SRTM HGT高程数据文件
            在深入探讨如何在Android应用中读取和处理SRTMHGT高程数据文件之前,我们先对SRTM数据及其格式有一个更全面的了解,并详细探讨每一步的实现细节和最佳实践。一、SRTMHGT数据概述        SRTM(ShuttleRadarTopographyMission)是一项由美国宇航局(NASA)......
  • Android优化:耗电量优化
            在移动应用开发领域,随着智能手机功能的日益强大,用户对电池续航能力的关注也达到了前所未有的高度。Android应用由于其复杂的交互设计、频繁的网络通信、以及多样化的传感器使用,往往成为电量消耗的主要源头。因此,优化Android应用的耗电量,提升电池续航能力,已成为......
  • android高程数据如何读取
    在Android中读取高程数据,通常涉及到地理信息系统(GIS)的应用,特别是当你需要处理像SRTM(ShuttleRadarTopographyMission)这样的数据集时。SRTM数据通常是HGT格式的16位整数栅格数据,每个文件代表了特定纬度和经度范围内的高程信息。下面是一些步骤和注意事项,帮助你在Android应用中读......
  • Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
    #!/bin/bash#Cross-compileenvironmentforAndroidonARM64andx86##ContentslicensedunderthetermsoftheOpenSSLlicense#http://www.openssl.org/source/license.html##Seehttp://wiki.openssl.org/index.php/FIPS_Library_and_Android#andhttp:......