java - subscribetotopic - Google Cloud Messaging en Delphi XE5?
how to use google cloud messaging (4)
Tengo una aplicación para Android que estoy pensando en transferir a Delphi, pero no veo una manera de interactuar con GCM. Estoy pensando que posiblemente tendría que ejecutar el GCMBaseIntentService en java y la interfaz con el objeto compartido delphi?
Alternativamente, estoy buscando una forma de hacer notificaciones push en una aplicación de Android Delphi Xe5.
Me complace ver que Delphi está evolucionando y adaptándose a las necesidades actuales. La publicación me hizo curioso, así que busqué un poco, así que encontré estos recursos:
datasnap foro en embarcadero que recomienda el uso de datasnap para resolver el problema de comunicación entre GCM y Delphi Side.
Espero que esto ayude a alguien.
Obtuve GCM trabajando con Delphi e hice un componente de muestra para encargarme de registrar y recibir los mensajes de GCM.
NOTA : Este es solo un código de prueba aproximado, no lo estoy usando en ninguna aplicación real (todavía). Por favor, no dude en modificar y mejorar, y si encuentra errores, por favor publique nuevamente.
Muchas gracias a Brian Long y su artículo sobre los servicios de Android .
Obtenga su ID de remitente de GCM (es su número de proyecto desde la consola de gcm) y su ID de API de GCM (cree una clave para la aplicación de servidor en la consola de GCM), los necesitará (vea las imágenes en la parte inferior).
Antes que nada, necesitas un archivo class.dex modificado. Puede crear esto ejecutando el archivo bat de Java en el archivo, o puede usar el que ya compilé (también incluido en el archivo).
Tienes que AGREGAR las nuevas classes.dex a tu Despliegue Android y DESACTIVAR el embarcadero uno:
Luego, debe editar su AndroidManifest.template.xml y agregarlo justo después de <%uses-permission%>
:
<%uses-permission%>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
y justo después de 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>
En su aplicación, declare la unidad gcmnotification:
uses
gcmnotification;
y luego en tu formulario declaras una variable del tipo TGCMNotification y un procedimiento que vincularás al evento 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;
Coloque en SenderID su número de proyecto de GCM. Para registrar su aplicación con GCM, llame a DoRegister:
procedure TForm8.Button1Click(Sender: TObject);
begin
gcmn.SenderID := YOUR_GCM_SENDERID;
if gcmn.DoRegister then
Toast(''Successfully registered with GCM.'');
end;
Si el DoRegister devuelve verdadero (registrado con éxito), gcmn.RegistrationID tendrá la identificación única que necesita para enviar mensajes a este dispositivo.
Y recibirás mensajes en el procedimiento del evento:
procedure TForm8.OnNotification(Sender: TObject; ANotification: TGCMNotificationMessage);
begin
Memo1.Lines.Add(''Received: '' + ANotification.Body);
end;
.. y eso es TODO lo que necesitas para Recibir. Genial, ¿eh? :-)
Para enviar, simplemente use 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;
A continuación, voy a publicar las unidades que necesita, simplemente guárdelas en el mismo lugar con su aplicación (o simplemente descargue todo desde AQUÍ ).
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.
Aquí está el AndroidManifest.template.xml modificado
<?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) -->
Y la fuente completa para la aplicación de prueba (enviará y recibirá un mensaje de 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.
El GCMReceiver.java que necesita compilar y agregarlo a classes.dex es:
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);
}
}
Y AQUÍ está el archivo zip con la fuente.
Si tiene problemas para hacer que funcione, es probable que algo no esté configurado en su consola de GCM.
Esto es lo que necesita de su consola de GCM:
Número de proyecto (lo usa cuando se registra con GCM, lo coloca en TGCMNotification.SenderID antes de llamar a DoRegister).
API ID lo usará para enviar mensajes a los dispositivos registrados.
Usted interconecta Java con Delphi usando JNI. El JNI le permite llamar en ambas direcciones: Java a Delphi o Delphi a Java. Para que pueda ampliar sus aplicaciones Delphi con clases de Java .
Para implementar lo que desea en Android, hacerlo en Java será la ruta más fácil de seguir, ya que algunos ejemplos disponibles hacen exactamente lo que usted tiene en mente. Sólo echar un vistazo:
- Implementación de Push Notification / Google Cloud Messaging para Android
- Aplicación de cliente de Android * utilizando el servicio de alerta basado en la nube
- Github Gist - GCMIntentService.java
Para conectar Java JNI y Delphi, puede seguir los pasos detallados, lo que permite una comunicación fluida entre el front-end y el back-end de su aplicación.
Si decide volver a utilizar parte del código, recuerde dar el crédito apropiado a los autores.