是否可以使用delphi读取android上其他应用程序的通知?

elc*_*lie 1 delphi notifications android firemonkey

我正在尝试从 Rad Studio XE7 for android 中制作的应用程序上读取来自其他应用程序的通知。抬头一看,发现在java中可以访问NotificationListenerService,但是如果用delphi可以访问这个服务就不行了。可以做到吗?

Pet*_*sen 6

您可以使用 阅读其他应用程序的通知,NotificationListenerService方法如下

第 1 步:准备好一切

  • 在您的项目文件夹中创建一个名为 Java 的文件夹

    现在在该文件夹中创建一个名为 src 的文件夹,并在其中创建一个与您的 Apps 包名称相同的文件夹结构,例如:src/com/embarcadero/$yourapp

在 src/com/embarcadero/$yourapp 中添加以下 Java 文件作为NotificationService.java

package com.embarcadero.$Yourapp;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.support.v4.content.LocalBroadcastManager;


public class NotificationService extends NotificationListenerService {

    static final String TAG = "NotificationService";

    Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        Log.d(TAG,"Service has been started!");
    }
    @Override

    public void onNotificationPosted(StatusBarNotification sbn) {
    Log.d(TAG,"Notification Posted!");
        String pack = sbn.getPackageName();
        Bundle extras = sbn.getNotification().extras;
        String title = extras.getString("android.title");
        String text = extras.getCharSequence("android.text").toString();
        String id = sbn.getTag();

        Log.i("Package", pack);
        Log.i("Title",title);
        Log.i("Text",text);
        if (id != null){
            Log.i("Key", id);
        }
        Intent msgrcv = new Intent("Msg");
        msgrcv.putExtra("package", pack);
        msgrcv.putExtra("key", id);
        msgrcv.putExtra("title", title);
        msgrcv.putExtra("text", text);
        LocalBroadcastManager.getInstance(context).sendBroadcast(msgrcv);

    }

    @Override

    public void onNotificationRemoved(StatusBarNotification sbn) {
        Log.d(TAG,"Notification Removed");

    }
}
Run Code Online (Sandbox Code Playgroud)

并将以下文件添加到 Java 文件夹:android-support-v4.jar(在您的 SDK 路径下找到:android-sdk-windows\extras\android\support\v4)

添加以下批处理文件build.bat

@echo off

setlocal

REM Set the your paths as needed 
SET PATH=%PATH%;C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows\build-tools\23.0.1
if x%ANDROID% == x set ANDROID=C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows
set ANDROID_PLATFORM=%ANDROID%\platforms\android-23
set DX_LIB=%ANDROID%\build-tools\23.0.1\lib
set EMBO_DEX="C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug\classes.dex"
set PROJ_DIR=C:\Users\PJJ\Documents\allesbeste\Mobiletest\java
REM the PROJ_DIR must point to your Java Folder inside your project folder
set VERBOSE=0
set JAVA="C:\Program Files\Java\jdk1.7.0_71\bin"

echo.
echo Compiling the Java service activity source files
echo.
mkdir output 2> nul
mkdir output\classes 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=-verbose
javac %VERBOSE_FLAG% -source 1.7 -target 1.7 -Xlint:deprecation -cp %ANDROID_PLATFORM%\android.jar;%EMBO_LIB%\fmx.jar;%PROJ_DIR%\android-support-v4.jar -d output\classes src\com\embarcadero\AllesbesteToep\NotificationService.java

echo.
echo Creating jar containing the new classes
echo.
mkdir output\jar 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=v
jar c%VERBOSE_FLAG%f output\jar\Delphi_Service.jar -C output\classes com

echo.
echo Converting from jar to dex...
echo.
mkdir output\dex 2> nul
if x%VERBOSE% == x1 SET VERBOSE_FLAG=--verbose
call dx --dex %VERBOSE_FLAG% --output=%PROJ_DIR%\output\dex\test_classes.dex --positions=lines %PROJ_DIR%\output\jar\Delphi_Service.jar

echo.
echo Merging dex files
echo.
java -cp %DX_LIB%\dx.jar com.android.dx.merge.DexMerger %PROJ_DIR%\output\dex\classes.dex %PROJ_DIR%\output\dex\test_classes.dex %EMBO_DEX%

echo Tidying up
echo.
REM Just change these as needed
del output\classes\com\embarcadero\AllesbesteToep\NotificationService.class
del output\dex\test_classes.dex
rmdir output\classes\com\embarcadero\AllesbesteToep
rmdir output\classes\com\embarcadero
rmdir output\classes\com
rmdir output\classes
pause

echo.
echo Now we have the end result, which is output\jar\Delphi_Service.jar

:Exit

endlocal
Run Code Online (Sandbox Code Playgroud)

添加位于 C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug 的默认 classes.dex 文件

添加同样位于 C:\Program Files (x86)\Embarcadero\Studio\17.0\lib\android\debug 的 fmx.jar

其重命名为classes.jar

所以你的 Java 文件夹结构现在看起来像这样

  • src //带有正确的文件夹结构和你的 NotificationService.java 里面

  • android-support-v4

  • 建造
  • 类.dex
  • 类.jar

如果您在构建文件中正确设置了所有变量,您现在可以运行 build.bat 来生成输出文件夹(特别感谢最初创建它的 Brian Long)

您现在将在输出文件夹中拥有 2 个文件夹,一个名为 dex,另一个为 jar,现在您将拥有一个新的 classes.dex 文件和 Delphi_Service 可执行文件

让您的应用程序使用新的 classes.dex 文件 http://docwiki.embarcadero.com/RADStudio/XE8/en/Creating_and_Deploying_a_classes.dex_File_Manually

将 Delphi_Service jar 文件添加到您的应用程序 http://docwiki.embarcadero.com/RADStudio/XE8/en/Adding_A_Java_Library_to_Your_Application_Using_the_Project_Manager

在您的项目文件中添加以下单元Android.JNI.LocalBroadcastMan

//Created with the Java2OP tool
unit Android.JNI.LocalBroadcastMan;

interface

uses
  Androidapi.JNIBridge,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.Util,
  Androidapi.JNI.Os,
  Androidapi.JNI.Net;

type
// ===== Forward declarations =====

  JLocalBroadcastManager = interface;//android.support.v4.content.LocalBroadcastManager

// ===== Interface declarations =====

  JLocalBroadcastManagerClass = interface(JObjectClass)
    ['{03179F7E-C439-4369-93CC-AA2BADC54398}']
    {class} function getInstance(context: JContext): JLocalBroadcastManager; cdecl;
  end;

  [JavaSignature('android/support/v4/content/LocalBroadcastManager')]
  JLocalBroadcastManager = interface(JObject)
    ['{6C255CD6-D94E-40BC-A758-EC4965A40725}']
    procedure registerReceiver(receiver: JBroadcastReceiver; filter: JIntentFilter); cdecl;
    function sendBroadcast(intent: JIntent): Boolean; cdecl;
    procedure sendBroadcastSync(intent: JIntent); cdecl;
    procedure unregisterReceiver(receiver: JBroadcastReceiver); cdecl;
  end;
  TJLocalBroadcastManager = class(TJavaGenericImport<JLocalBroadcastManagerClass, JLocalBroadcastManager>) end;

implementation

procedure RegisterTypes;
begin
  TRegTypes.RegisterType('Android.JNI.LocalBroadcastMan.JLocalBroadcastManager', TypeInfo(Android.JNI.LocalBroadcastMan.JLocalBroadcastManager));
end;

initialization
  RegisterTypes;
end.
Run Code Online (Sandbox Code Playgroud)

http://www.fmxexpress.com/free-broadcast-receiver-component-for-delphi-xe7-firemonkey-on-android/下载免费广播接收器组件

安装组件,现在将随附的 BroadcastReceiver 单元添加到您的项目中,并在BroadcastReceiver 中添加以下代码:

添加到您的 uses : Android.JNI.LocalBroadcastMan

TBroadcastReceiver.Add程序更改为

procedure TBroadcastReceiver.Add(Value: String);
{$IFDEF ANDROID}
var
  Filter: JIntentFilter;
  locMan : JLocalBroadcastManager;
{$ENDIF}
begin
  {$IFDEF ANDROID}
  if (FListener = nil) or (FReceiver = nil) then
  begin
    Raise Exception.Create('First use RegisterReceive!');
    Exit;
  end;
  {$ENDIF}

  if FItems <> nil then
    if FItems.IndexOf(Value) = -1 then
    begin
    {$IFDEF ANDROID}
      filter := TJIntentFilter.Create;
      filter.addAction(StringToJString(Value));
      SharedActivityContext.registerReceiver(FReceiver,filter);
      locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
      locMan.registerReceiver(FReceiver,Filter);
    {$ENDIF}
      FItems.Add(Value);
    end;
end;
Run Code Online (Sandbox Code Playgroud)

添加以下程序 unRegisterReceive

procedure TBroadcastReceiver.unRegisterReceive;
var
    locMan : JLocalBroadcastManager;
begin
    locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
    locMan.unregisterReceiver(FReceiver);
end;
Run Code Online (Sandbox Code Playgroud)

添加以下程序 regagain

procedure TBroadcastReceiver.regagain;
var
    locMan : JLocalBroadcastManager;
    filter : JIntentFilter;
begin
    filter := TJIntentFilter.Create;
    filter.addAction(StringToJString('Msg'));
    locMan := TJLocalBroadcastManager.JavaClass.getInstance(SharedActivityContext);
    locMan.registerReceiver(FReceiver,Filter);
end;
Run Code Online (Sandbox Code Playgroud)

在您的项目目录中编辑您的 AndroidManifest.template.xml 以包含服务

<service android:name="com.embarcadero.$Yourapp.NotificationService"
                 android:label="%label%"
          android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
                 <action android:name="android.service.notification.NotificationListenerService" />
             </intent-filter>
   </service>
Run Code Online (Sandbox Code Playgroud)

第 2 步:将所有内容放在一起

将广播接收器组件添加到您的项目中并

将以下过程添加到您的项目中

procedure startService
var
    ServiceIntent: JIntent;
begin
    BroadcastReceiver1.RegisterReceive;
    BroadcastReceiver1.Add('Msg');
    ServiceIntent := TJIntent.JavaClass.init(SharedActivityContext,
      TJLang_Class.JavaClass.forName(
     StringToJString('com.embarcadero.$yourapp.NotificationService'),True,SharedActivity.getClassLoader));
    SharedActivity.startService(serviceIntent)
end;
Run Code Online (Sandbox Code Playgroud)

BroadcastReceiver有一个onReceive将被触发,一旦localBroadcastManager广播从意图事件onNotificationPosted方法

因此,onReceive您现在可以根据该意图做任何您想做的事情,例如

var
    NoteName,NoteTitle,pack : String;
begin
//Do whatever you want with the intent
    NoteName := JStringToString(Intent.getStringExtra(StringToJString('key')));
    NoteTitle := JStringToString(Intent.getStringExtra(StringToJString('title')));
    pack := JStringToString(Intent.getStringExtra(StringToJString('package')));
    ShowMessage('Notification: '+NoteName+' Title :'+ NoteTitle+' package :'+pack);
end;
Run Code Online (Sandbox Code Playgroud)

现在我们需要写一些代码来处理当你的App不在前台或者重新进入前台时localBroadcastManager的生命周期

如果你不使用 Appevents 首先看看这里http://www.fmxexpress.com/handle-android-and-ios-lifecycle-events-in-delphi-xe5-firemonkey/

现在响应EnteredBackgroundAppevent 添加unRegisterReceive程序:BroadcastReceiver1.unRegisterReceive;

响应WillBecomeForegroundAppevent 添加regagain程序:BroadcastReceiver1.regagain;

最后,为了让您的应用程序能够读取其他应用程序的通知,您需要明确授予它这样做的权限。

转到您的手机设置并搜索通知访问,您将看到您的应用程序在那里列出,只需说它是允许的

更新 1:

添加了处理应用程序生命周期的代码,应用程序现在应该可以愉快地在前台和后台之间切换并继续工作