我有一个 android 应用程序,我正在考虑移植到 Delphi,但我看不到与 GCM 交互的方法。我在想我可能必须在 java 中运行 GCMBaseIntentService 并与 delphi 共享对象接口?
或者,我正在寻找一种在 Delphi Xe5 android 应用程序中进行推送通知的方法。
我有一个 android 应用程序,我正在考虑移植到 Delphi,但我看不到与 GCM 交互的方法。我在想我可能必须在 java 中运行 GCMBaseIntentService 并与 delphi 共享对象接口?
或者,我正在寻找一种在 Delphi Xe5 android 应用程序中进行推送通知的方法。
您使用 JNI 将 Java 与 Delphi 交互。JNI 允许您双向调用:Java 到 Delphi 或 Delphi 到 Java。因此,您可以使用Java 类扩展您的 Delphi 应用程序。
为了在 Android 上实现您想要的,在 Java 中执行将是更容易遵循的路径,因为一些可用的示例完全符合您的想法。看看:
要连接 Java JNI 和 Delphi,您可以遵循详细的步骤,从而在应用程序的前端和后端之间实现顺畅的通信。
如果您决定重用某些代码,请记住给予作者适当的信任。
我让 GCM 与 Delphi 一起工作,并制作了一个示例组件来负责注册和接收 GCM 消息。
注意:这只是一个粗略的测试代码,我还没有在任何实际应用程序中使用它。请自由修改和改进,如果您发现错误,请回帖。
非常感谢 Brian Long 和他关于Android 服务的文章。
获取您的 GCM 发件人 ID(这是您来自 gcm 控制台的项目编号)和您的 GCM API id(在 GCM 控制台中为服务器应用程序创建一个密钥),您将需要它们(参见底部的图片)。
首先,你需要一个修改过的 classes.dex 文件。您可以通过运行存档中的 bat 文件 Java 目录来创建它,或者您可以使用我已经编译的那个(也包含在存档中)。
您必须将新的 classes.dex添加到您的 Android 部署并取消选中 embarcadero之一:
然后,您需要编辑您的 AndroidManifest.template.xml 并在之后添加<%uses-permission%>
:
<%uses-permission%>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
紧接着android:theme="%theme%">
<receiver
android:name="com.ioan.delphi.GCMReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="%package%" />
</intent-filter>
</receiver>
在您的应用程序中,声明 gcmnotification 单元:
uses
gcmnotification;
然后在您的表单中声明一个 TGCMNotification 类型的变量和一个您将链接到 TGCMNotification.OnReceiveGCMNotification 事件的过程:
type
TForm8 = class(TForm)
//....
private
{ Private declarations }
public
{ Public declarations }
gcmn: TGCMNotification;
procedure OnNotification(Sender: TObject; ANotification: TGCMNotificationMessage);
end;
procedure TForm8.FormCreate(Sender: TObject);
begin
gcmn := TGCMNotification.Create(self);
gcmn.OnReceiveGCMNotification := OnNotification;
end;
在 SenderID 中输入您的 GCM 项目编号。要向 GCM 注册您的 APP,请调用 DoRegister:
procedure TForm8.Button1Click(Sender: TObject);
begin
gcmn.SenderID := YOUR_GCM_SENDERID;
if gcmn.DoRegister then
Toast('Successfully registered with GCM.');
end;
如果 DoRegister 返回 true(成功注册),gcmn.RegistrationID 将具有向此设备发送消息所需的唯一 ID。
您将在事件过程中收到消息:
procedure TForm8.OnNotification(Sender: TObject; ANotification: TGCMNotificationMessage);
begin
Memo1.Lines.Add('Received: ' + ANotification.Body);
end;
..这就是您接收所需的全部内容。酷吧?:-)
要发送,只需使用 TIdHttp:
procedure TForm8.Button2Click(Sender: TObject);
const
sendUrl = 'https://android.googleapis.com/gcm/send';
var
Params: TStringList;
AuthHeader: STring;
idHTTP: TIDHTTP;
SSLIOHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
idHTTP := TIDHTTP.Create(nil);
try
SslIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
idHTTP.IOHandler := SSLIOHandler;
idHTTP.HTTPOptions := [];
Params := TStringList.Create;
try
Params.Add('registration_id='+ gcmn.RegistrationID);
Params.Values['data.message'] := 'test: ' + FormatDateTime('yy-mm-dd hh:nn:ss', Now);
idHTTP.Request.Host := sendUrl;
AuthHeader := 'Authorization: key=' + YOUR_API_ID;
idHTTP.Request.CustomHeaders.Add(AuthHeader);
IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded;charset=UTF-8';
Memo1.Lines.Add('Send result: ' + idHTTP.Post(sendUrl, Params));
finally
Params.Free;
end;
finally
FreeAndNil(idHTTP);
end;
end;
接下来我将发布您需要的单元,只需将它们与您的应用程序一起保存在同一个位置(或者从这里下载整个内容)。
gcmnotification.pas
unit gcmnotification;
interface
{$IFDEF ANDROID}
uses
System.SysUtils,
System.Classes,
FMX.Helpers.Android,
Androidapi.JNI.PlayServices,
Androidapi.JNI.GraphicsContentViewText,
Androidapi.JNIBridge,
Androidapi.JNI.JavaTypes;
type
TGCMNotificationMessageKind = (nmMESSAGE_TYPE_MESSAGE, nmMESSAGE_TYPE_DELETED, nmMESSAGE_TYPE_SEND_ERROR);
{ Discription of notification for Notification Center }
TGCMNotificationMessage = class (TPersistent)
private
FKind: TGCMNotificationMessageKind;
FSender: string;
FWhat: integer;
FBody: string;
protected
procedure AssignTo(Dest: TPersistent); override;
public
{ Unique identificator for determenation notification in Notification list }
property Kind: TGCMNotificationMessageKind read FKind write FKind;
property Sender: string read FSender write FSender;
property What: integer read FWhat write FWhat;
property Body: string read FBody write FBody;
constructor Create;
end;
TOnReceiveGCMNotification = procedure (Sender: TObject; ANotification: TGCMNotificationMessage) of object;
TGCMNotification = class(TComponent)
strict private
{ Private declarations }
FRegistrationID: string;
FSenderID: string;
FOnReceiveGCMNotification: TOnReceiveGCMNotification;
FReceiver: JBroadcastReceiver;
FAlreadyRegistered: boolean;
function CheckPlayServicesSupport: boolean;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function DoRegister: boolean;
function GetGCMInstance: JGoogleCloudMessaging;
published
{ Published declarations }
property SenderID: string read FSenderID write FSenderID;
property RegistrationID: string read FRegistrationID write FRegistrationID;
property OnReceiveGCMNotification: TOnReceiveGCMNotification read FOnReceiveGCMNotification write FOnReceiveGCMNotification;
end;
{$ENDIF}
implementation
{$IFDEF ANDROID}
uses
uGCMReceiver;
{ TGCMNotification }
function TGCMNotification.CheckPlayServicesSupport: boolean;
var
resultCode: integer;
begin
resultCode := TJGooglePlayServicesUtil.JavaClass.isGooglePlayServicesAvailable(SharedActivity);
result := (resultCode = TJConnectionResult.JavaClass.SUCCESS);
end;
constructor TGCMNotification.Create(AOwner: TComponent);
var
Filter: JIntentFilter;
begin
inherited;
Filter := TJIntentFilter.Create;
FReceiver := TJGCMReceiver.Create(Self);
SharedActivity.registerReceiver(FReceiver, Filter);
FAlreadyRegistered := false;
end;
destructor TGCMNotification.Destroy;
begin
SharedActivity.unregisterReceiver(FReceiver);
FReceiver := nil;
inherited;
end;
function TGCMNotification.DoRegister: boolean;
var
p: TJavaObjectArray<JString>;
gcm: JGoogleCloudMessaging;
begin
if FAlreadyRegistered then
result := true
else
begin
if CheckPlayServicesSupport then
begin
gcm := GetGCMInstance;
p := TJavaObjectArray<JString>.Create(1);
p.Items[0] := StringToJString(FSenderID);
FRegistrationID := JStringToString(gcm.register(p));
FAlreadyRegistered := (FRegistrationID <> '');
result := FAlreadyRegistered;
end
else
result := false;
end;
end;
function TGCMNotification.GetGCMInstance: JGoogleCloudMessaging;
begin
result := TJGoogleCloudMessaging.JavaClass.getInstance(SharedActivity.getApplicationContext);
end;
{ TGCMNotificationMessage }
procedure TGCMNotificationMessage.AssignTo(Dest: TPersistent);
var
DestNotification: TGCMNotificationMessage;
begin
if Dest is TGCMNotificationMessage then
begin
DestNotification := Dest as TGCMNotificationMessage;
DestNotification.Kind := Kind;
DestNotification.What := What;
DestNotification.Sender := Sender;
DestNotification.Body := Body;
end
else
inherited AssignTo(Dest);
end;
constructor TGCMNotificationMessage.Create;
begin
Body := '';
end;
{$ENDIF}
end.
uGCMReceiver.pas
unit uGCMReceiver;
interface
{$IFDEF ANDROID}
uses
FMX.Types,
Androidapi.JNIBridge,
Androidapi.JNI.GraphicsContentViewText,
gcmnotification;
type
JGCMReceiverClass = interface(JBroadcastReceiverClass)
['{9D967671-9CD8-483A-98C8-161071CE7B64}']
{Methods}
end;
[JavaSignature('com/ioan/delphi/GCMReceiver')]
JGCMReceiver = interface(JBroadcastReceiver)
['{4B30D537-5221-4451-893D-7916ED11CE1F}']
{Methods}
end;
TJGCMReceiver = class(TJavaGenericImport<JGCMReceiverClass, JGCMReceiver>)
private
FOwningComponent: TGCMNotification;
protected
constructor _Create(AOwner: TGCMNotification);
public
class function Create(AOwner: TGCMNotification): JGCMReceiver;
procedure OnReceive(Context: JContext; ReceivedIntent: JIntent);
end;
{$ENDIF}
implementation
{$IFDEF ANDROID}
uses
System.Classes,
System.SysUtils,
FMX.Helpers.Android,
Androidapi.NativeActivity,
Androidapi.JNI,
Androidapi.JNI.JavaTypes,
Androidapi.JNI.Os,
Androidapi.JNI.PlayServices;
{$REGION 'JNI setup code and callback'}
var
GCMReceiver: TJGCMReceiver;
ARNContext: JContext;
ARNReceivedIntent: JIntent;
procedure GCMReceiverOnReceiveThreadSwitcher;
begin
Log.d('+gcmReceiverOnReceiveThreadSwitcher');
Log.d('Thread: Main: %.8x, Current: %.8x, Java:%.8d (%2:.8x)',
[MainThreadID, TThread.CurrentThread.ThreadID,
TJThread.JavaClass.CurrentThread.getId]);
GCMReceiver.OnReceive(ARNContext,ARNReceivedIntent );
Log.d('-gcmReceiverOnReceiveThreadSwitcher');
end;
//This is called from the Java activity's onReceiveNative() method
procedure GCMReceiverOnReceiveNative(PEnv: PJNIEnv; This: JNIObject; JNIContext, JNIReceivedIntent: JNIObject); cdecl;
begin
Log.d('+gcmReceiverOnReceiveNative');
Log.d('Thread: Main: %.8x, Current: %.8x, Java:%.8d (%2:.8x)',
[MainThreadID, TThread.CurrentThread.ThreadID,
TJThread.JavaClass.CurrentThread.getId]);
ARNContext := TJContext.Wrap(JNIContext);
ARNReceivedIntent := TJIntent.Wrap(JNIReceivedIntent);
Log.d('Calling Synchronize');
TThread.Synchronize(nil, GCMReceiverOnReceiveThreadSwitcher);
Log.d('Synchronize is over');
Log.d('-gcmReceiverOnReceiveNative');
end;
procedure RegisterDelphiNativeMethods;
var
PEnv: PJNIEnv;
ReceiverClass: JNIClass;
NativeMethod: JNINativeMethod;
begin
Log.d('Starting the GCMReceiver JNI stuff');
PEnv := TJNIResolver.GetJNIEnv;
Log.d('Registering interop methods');
NativeMethod.Name := 'gcmReceiverOnReceiveNative';
NativeMethod.Signature := '(Landroid/content/Context;Landroid/content/Intent;)V';
NativeMethod.FnPtr := @GCMReceiverOnReceiveNative;
ReceiverClass := TJNIResolver.GetJavaClassID('com.ioan.delphi.GCMReceiver');
PEnv^.RegisterNatives(PEnv, ReceiverClass, @NativeMethod, 1);
PEnv^.DeleteLocalRef(PEnv, ReceiverClass);
end;
{$ENDREGION}
{ TActivityReceiver }
constructor TJGCMReceiver._Create(AOwner: TGCMNotification);
begin
inherited;
FOwningComponent := AOwner;
Log.d('TJGCMReceiver._Create constructor');
end;
class function TJGCMReceiver.Create(AOwner: TGCMNotification): JGCMReceiver;
begin
Log.d('TJGCMReceiver.Create class function');
Result := inherited Create;
GCMReceiver := TJGCMReceiver._Create(AOwner);
end;
procedure TJGCMReceiver.OnReceive(Context: JContext; ReceivedIntent: JIntent);
var
extras: JBundle;
gcm: JGoogleCloudMessaging;
messageType: JString;
noti: TGCMNotificationMessage;
begin
if Assigned(FOwningComponent.OnReceiveGCMNotification) then
begin
noti := TGCMNotificationMessage.Create;
try
Log.d('Received a message!');
extras := ReceivedIntent.getExtras();
gcm := FOwningComponent.GetGCMInstance;
// The getMessageType() intent parameter must be the intent you received
// in your BroadcastReceiver.
messageType := gcm.getMessageType(ReceivedIntent);
if not extras.isEmpty() then
begin
{*
* Filter messages based on message type. Since it is likely that GCM will be
* extended in the future with new message types, just ignore any message types you're
* not interested in, or that you don't recognize.
*}
if TJGoogleCloudMessaging.JavaClass.MESSAGE_TYPE_SEND_ERROR.equals(messageType) then
begin
// It's an error.
noti.Kind := TGCMNotificationMessageKind.nmMESSAGE_TYPE_SEND_ERROR;
FOwningComponent.OnReceiveGCMNotification(Self, noti);
end
else
if TJGoogleCloudMessaging.JavaClass.MESSAGE_TYPE_DELETED.equals(messageType) then
begin
// Deleted messages on the server.
noti.Kind := TGCMNotificationMessageKind.nmMESSAGE_TYPE_DELETED;
FOwningComponent.OnReceiveGCMNotification(Self, noti);
end
else
if TJGoogleCloudMessaging.JavaClass.MESSAGE_TYPE_MESSAGE.equals(messageType) then
begin
// It's a regular GCM message, do some work.
noti.Kind := TGCMNotificationMessageKind.nmMESSAGE_TYPE_MESSAGE;
noti.Sender := JStringToString(extras.getString(StringToJString('sender')));
noti.What := StrToIntDef(JStringToString(extras.getString(StringToJString('what'))), 0);
noti.Body := JStringToString(extras.getString(StringToJString('message')));
FOwningComponent.OnReceiveGCMNotification(Self, noti);
end;
end;
finally
noti.Free;
end;
end;
end;
initialization
RegisterDelphiNativeMethods
{$ENDIF}
end.
这是修改后的 AndroidManifest.template.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="%package%"
android:versionCode="%versionCode%"
android:versionName="%versionName%">
<!-- This is the platform API where NativeActivity was introduced. -->
<uses-sdk android:minSdkVersion="%minSdkVersion%" />
<%uses-permission%>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<application android:persistent="%persistent%"
android:restoreAnyVersion="%restoreAnyVersion%"
android:label="%label%"
android:installLocation="%installLocation%"
android:debuggable="%debuggable%"
android:largeHeap="%largeHeap%"
android:icon="%icon%"
android:theme="%theme%">
<receiver
android:name="com.ioan.delphi.GCMReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="%package%" />
</intent-filter>
</receiver>
<!-- Our activity is a subclass of the built-in NativeActivity framework class.
This will take care of integrating with our NDK code. -->
<activity android:name="com.embarcadero.firemonkey.FMXNativeActivity"
android:label="%activityLabel%"
android:configChanges="orientation|keyboardHidden">
<!-- Tell NativeActivity the name of our .so -->
<meta-data android:name="android.app.lib_name"
android:value="%libNameValue%" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.embarcadero.firemonkey.notifications.FMXNotificationAlarm" />
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->
以及测试应用程序的完整源代码(它将发送和接收 GCM 消息):
unit testgcmmain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
FMX.StdCtrls, FMX.Layouts, FMX.Memo, IdBaseComponent, IdComponent, IdTCPConnection,
IdTCPClient, IdHTTP, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL,
gcmnotification;
type
TForm8 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
gcmn: TGCMNotification;
procedure OnNotification(Sender: TObject; ANotification: TGCMNotificationMessage);
end;
const
YOUR_GCM_SENDERID = '1234567890';
YOUR_API_ID = 'abc1234567890';
var
Form8: TForm8;
implementation
{$R *.fmx}
procedure TForm8.FormCreate(Sender: TObject);
begin
gcmn := TGCMNotification.Create(self);
gcmn.OnReceiveGCMNotification := OnNotification;
end;
procedure TForm8.FormDestroy(Sender: TObject);
begin
FreeAndNil(gcmn);
end;
procedure TForm8.Button1Click(Sender: TObject);
begin
// register with the GCM
gcmn.SenderID := YOUR_GCM_SENDERID;
if gcmn.DoRegister then
Memo1.Lines.Add('Successfully registered with GCM.');
end;
procedure TForm8.OnNotification(Sender: TObject; ANotification: TGCMNotificationMessage);
begin
// you just received a message!
if ANotification.Kind = TGCMNotificationMessageKind.nmMESSAGE_TYPE_MESSAGE then
Memo1.Lines.Add('Received: ' + ANotification.Body);
end;
// send a message
procedure TForm8.Button2Click(Sender: TObject);
const
sendUrl = 'https://android.googleapis.com/gcm/send';
var
Params: TStringList;
AuthHeader: STring;
idHTTP: TIDHTTP;
SSLIOHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
idHTTP := TIDHTTP.Create(nil);
try
SslIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
idHTTP.IOHandler := SSLIOHandler;
idHTTP.HTTPOptions := [];
Params := TStringList.Create;
try
Params.Add('registration_id='+ gcmn.RegistrationID);
// you can send the data with a payload, in my example the server will accept
// data.message = the message you want to send
// data.sender = some sender info
// data.what = an integer (aka "message type")
// you can put any payload in the data, data.score, data.blabla...
// just make sure you modify the code in my component to handle it
Params.Values['data.message'] := 'test: ' + FormatDateTime('yy-mm-dd hh:nn:ss', Now);
idHTTP.Request.Host := sendUrl;
AuthHeader := 'Authorization: key=' + YOUR_API_ID;
idHTTP.Request.CustomHeaders.Add(AuthHeader);
IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded;charset=UTF-8';
Memo1.Lines.Add('Send result: ' + idHTTP.Post(sendUrl, Params));
finally
Params.Free;
end;
finally
FreeAndNil(idHTTP);
end;
end;
end.
您需要编译并将其添加到 classes.dex 的 GCMReceiver.java 是:
package com.ioan.delphi;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.Context;
import android.util.Log;
public class GCMReceiver extends BroadcastReceiver
{
static final String TAG = "GCMReceiver";
public native void gcmReceiverOnReceiveNative(Context context, Intent receivedIntent);
@Override
public void onReceive(Context context, Intent receivedIntent)
{
Log.d(TAG, "onReceive");
gcmReceiverOnReceiveNative(context, receivedIntent);
}
}
如果您无法使其正常工作,则可能是您的 GCM 控制台中未正确配置某些内容。
以下是 GCM 控制台所需的内容:
项目编号(在 GCM 注册时使用此编号,在调用 DoRegister 之前将其放入 TGCMNotification.SenderID)。
API ID,您将使用它向注册的设备发送消息。
我很高兴看到 delphi 正在不断发展并适应当前的需求。这篇文章让我感到好奇,所以我环顾四周,发现了这些资源:
embarcadero 上的论坛帖子,建议使用datasnap来解决 GCM 和 delphi 端之间的通信问题。
我希望这会对某人有所帮助。