[java] Google Cloud Messaging in Delphi XE5?


Answers

Ho ottenuto GCM lavorando con Delphi e ho creato un componente di esempio per occuparmi della registrazione e della ricezione dei messaggi GCM.

NOTA : Questo è solo un codice di test approssimativo, non lo sto usando in nessuna applicazione reale (ancora). Per favore, sei libero di modificare e migliorare, e se trovi bug, per favore pubblica di nuovo.

Un grande ringraziamento a Brian Long e al suo articolo su Android Services .

Ottieni il tuo ID mittente GCM (è il tuo numero progetto dalla console gcm) e il tuo ID API GCM (crea una chiave per l'applicazione server nella console GCM), ne avrai bisogno (vedi le immagini in basso).

Prima di tutto, hai bisogno di un file classes.dex modificato. Puoi crearlo eseguendo la directory Java del file bat nell'archivio, oppure puoi usare quello che è già compilato da me (anch'esso incluso nell'archivio).

Devi AGGIUNGERE il nuovo classes.dex alla tua distribuzione Android e SCOLLEGARE l'embarcadero:

Quindi, devi modificare il tuo AndroidManifest.template.xml e aggiungere subito dopo <%uses-permission%> :

<%uses-permission%>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

e subito dopo 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>

Nell'applicazione, dichiarare l'unità gcmnotification:

uses
  gcmnotification;

e quindi nel modulo dichiarare una variabile del tipo TGCMNotification e una procedura che collegherà all'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;

Inserisci nel SenderID il numero del tuo progetto GCM. Per registrare l'APP con GCM, chiama DoRegister:

procedure TForm8.Button1Click(Sender: TObject);
begin
  gcmn.SenderID := YOUR_GCM_SENDERID;
  if gcmn.DoRegister then
    Toast('Successfully registered with GCM.');
end;

Se DoRegister restituisce true (registrato con esito positivo), gcmn.RegistrationID avrà l'ID univoco necessario per l'invio di messaggi a questo dispositivo.

E riceverai messaggi nella procedura dell'evento:

procedure TForm8.OnNotification(Sender: TObject;  ANotification: TGCMNotificationMessage);
begin
  Memo1.Lines.Add('Received: ' + ANotification.Body);
end;

.. e questo è tutto ciò che serve per ricevere. Splendido, no? :-)

Per inviare, basta usare 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;

Quindi inserirò le unità di cui hai bisogno, salvale nello stesso posto con la tua applicazione (o semplicemente scarica l'intera cosa da QUI ).

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.

Ecco il file AndroidManifest.template.xml modificato

<?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) -->

E la fonte completa per l'applicazione di test (invierà e riceverà un messaggio 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.

Il GCMReceiver.java che devi compilare e aggiungerlo al classes.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);
    }
}

E QUI è l'archivio zip con la fonte.

Se hai problemi a farlo funzionare, probabilmente è qualcosa che non è configurato direttamente nella tua console GCM.

Ecco ciò che ti serve dalla tua console GCM:

Numero del progetto (lo usi quando ti registri a GCM, mettilo in TGCMNotification.SenderID prima di chiamare DoRegister).

ID API che userai per inviare messaggi ai dispositivi registrati.

Question

Ho un'app per Android che sto pensando di eseguire il porting su Delphi ma non riesco a vedere un modo per interfacciarmi con GCM. Sto pensando che avrei dovuto eseguire GCMBaseIntentService in java e interfacciare l'oggetto condiviso delphi?

In alternativa, sto cercando un modo per fare notifiche push in un'applicazione Android Delphi Xe5.




Sono contento di vedere Delphi che si sta evolvendo e adattandosi alle esigenze attuali. Il post mi ha reso curioso quindi mi sono guardato intorno un po 'così ho trovato queste risorse:

forum post su embarcadero che raccomanda l'uso di datasnap per risolvere il problema di comunicazione tra GCM e il lato delphi.

Spero che questo possa aiutare qualcuno.




Related