У меня есть приложение для Android, которое я собираюсь портировать в Delphi, но я не вижу способа взаимодействия с GCM. Я думаю, что мне, возможно, придется запустить GCMBaseIntentService в java и взаимодействовать с общим объектом delphi?
В качестве альтернативы, я ищу способ сделать push-уведомления в приложении для Android Delphi Xe5.
Ответ 2
Я получил GCM, работающий с Delphi, и Я сделал образец компонента, чтобы заботиться о регистрации и получении сообщений GCM.
ПРИМЕЧАНИЕ. Это всего лишь грубый тестовый код, я не использую его ни в одном реальном приложении (пока). Пожалуйста, не стесняйтесь изменять и улучшать, и если вы найдете ошибки, отправьте их обратно.
Большое спасибо Брайану Лонгу и его статье о Службах Android.
Получите идентификатор отправителя GCM (его номер проекта из консоли gcm) и идентификатор API GCM (создайте ключ для серверного приложения в консоли GCM), вам понадобятся они (см. рисунки внизу).
Прежде всего, вам нужен модифицированный файл classes.dex. Вы можете создать это, запустив файл bat bat Java в архиве, или вы можете использовать тот, который уже скомпилирован мной (также включен в архив).
Вы должны добавить ADID новые классы .dex к вашему развертыванию Android и UNCHECK embarcadero one:
![classes.dex deployment]()
Затем вам нужно отредактировать свой 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. Для регистрации APP с GCM вызовите 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 будет иметь уникальный идентификатор, необходимый для отправки сообщений на это устройство.
И вы получите сообщения в процедуре события:
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 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 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 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.
GCMReceiver.java, который вам нужно скомпилировать и добавить в класс .dex, это:
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);
}
}
И ЗДЕСЬ архив zip с исходным кодом.
Если у вас возникли проблемы с его работой, возможно, что-то не настроено прямо на консоли GCM.
Вот что вам нужно от консоли GCM:
Номер проекта (вы используете это при регистрации в GCM, поместите его в TGCMNotification.SenderID перед вызовом DoRegister).
![Номер проекта]()
Идентификатор API, вы будете использовать это для отправки сообщений зарегистрированным устройствам.
![API ID]()