Подтвердить что ты не робот

Облачные сообщения Google в Delphi XE5?

У меня есть приложение для Android, которое я собираюсь портировать в Delphi, но я не вижу способа взаимодействия с GCM. Я думаю, что мне, возможно, придется запустить GCMBaseIntentService в java и взаимодействовать с общим объектом delphi?

В качестве альтернативы, я ищу способ сделать push-уведомления в приложении для Android Delphi Xe5.

4b9b3361

Ответ 1

Интерфейс Java с Delphi с использованием JNI. JNI позволяет вам звонить в обоих направлениях: Java в Delphi или Delphi на Java. Таким образом, вы можете расширить свои приложения Delphi с помощью Java-классов.

Для реализации того, что вы хотите на Android, сделать это в Java будет более простым путем, поскольку некоторые доступные примеры делают именно то, что вы имеете в виду. Просто посмотрите:

Чтобы взаимодействовать с Java JNI и Delphi, вы можете следить за подробными шагами, тем самым обеспечивая плавное общение между интерфейсом и концом вашего приложения.

Если вы решите повторно использовать часть кода, не забудьте дать авторам соответствующие отзывы.

Ответ 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

Ответ 3

Я думаю, что создание моста для Java может быть хорошей идеей. Взгляните на это сообщение о том, как это сделать. И здесь вы можете найти учебное пособие по внедрению GCM на стороне клиента в Java.

Ответ 4

Я рад видеть, что дельфи развивается и адаптируется к текущим потребностям. Сообщение сделало меня любопытством, поэтому я немного огляделся, чтобы найти эти ресурсы:

сообщение форума на embarcadero, которое рекомендует использовать datasnap для решения проблемы связи между GCM и стороной delphi.

Надеюсь, это поможет кому-то.