Delphi XE5 中的 Google Cloud Messaging?
我有一个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 Services上的文章。
获取您的 GCM 发件人 ID(这是来自 gcm 控制台的项目编号)和 GCM API ID(在 GCM 控制台中为服务器应用程序创建密钥),您将需要它们(请参阅底部的图片)。
首先,你需要一个修改过的 classes.dex 文件。您可以通过在归档文件中运行 bat 文件 Java dir 来创建它,也可以使用我已经编译的那个(也包含在归档中)。
您必须将新的classs.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;
输入发件人ID您的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。注册 ID 将具有向此设备发送消息所需的唯一 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 将使用它向已注册的设备发送消息。