« FireMonkey2のTGridでOwnerDraw | トップページ

2013年9月25日 (水)

DelphiXE5でFireMonkeyを使わずにAndroid開発

20130926修正:TEngine.Create()内のPAndroid_appを得るコードがNDKサンプル通りになっていなかったので修正
Delphi XE5がAndroid開発に対応しました。色々遊べて楽しいんですが、 まだまだ仕様が安定していないFireMonkeyだけで開発していくには若干の不安も残ります。

そこで、FireMonekyを使わずに素のNDKに近い状態でNativeActivity開発ができるかどうか試してみました。

需要は微妙ですが、せっかく試したので手順を残しておきます。

(続きにソースを載せますが、プロジェクトファイルも上げておきます Download)
まず、空のFireMonkeyモバイルアプリケーションを新規作成してください。
このとき、フォームユニットが自動的に作成されますが、必要ないのでプロジェクトマネージャで削除しておいてください。

次にプロジェクトソースを表示してください。 (名前はNativeActivityTestにします)
program NativeActivityTest;

uses
  System.StartUpCopy,
  FMX.Forms,  //*
  Unit9 in 'Unit9.pas' {Form9};

{$R *.res}

begin
  Application.Initialize;//*
  Application.Run;//*
end.
上記のようなコードが表示されるので*行を削除します。


次に新しいユニットを追加して、NativeActivityを扱うコードを書きます。今回はNDKサンプルのnative-activityをDelphiに移植してみました。↓こんな感じ
(*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *)

 {
  Port to DelphiXE3 w/o FireMonkey    2013.9.25 TMaeda
 }
unit NA_main;

interface

uses
  System.SysUtils, System.Math, Androidapi.AppGlue, Androidapi.NativeWindow,
  Androidapi.Looper, Androidapi.Input, Androidapi.Sensor, Androidapi.Egl,
  Androidapi.Gles, Androidapi.Gles2,Androidapi.Log;

type
  // Our saved state data.
  TSaved_state = record
    angle: Single;
    x: Single;
    y: Single;
  end;

  PSaved_state = ^TSaved_state;

  // Shared state for our app.
  TEngine = class
  private
    FApp: PAndroid_app;

    FSensorManager: PASensorManager;
    FAccelerometerSensor: PASensor;
    FSensorEventQueue: PASensorEventQueue;

    FAnimating: integer;
    FDisplay: EGLDisplay;
    FSurface: EGLSurface;
    FContext: EGLContext;
    FWidth: integer;
    FHeight: integer;
    FState: TSaved_state;

    function  InitDisplay: integer;
    procedure DrawFrame;
    procedure TermDisplay;
    procedure HandleCmd(Cmd: Int32);
    function  HandleInput(Event: PAInputEvent): Int32;
  public
    constructor Create;

    procedure Run;
  end;

implementation

procedure Log_info(Fmt: String; Params: array of const);
var
  Msg: string;
  M: TMarshaller;
begin
  Msg := Format('native-activity ' + Fmt, Params);
  LOGI(M.AsAnsi(Msg).ToPointer);
end;

procedure Log_warn(Fmt: String; Params: array of const);
var
  Msg: string;
  M: TMarshaller;
begin
  Msg := Format('native-activity ' + Fmt, Params);
  LOGW(M.AsAnsi(Msg).ToPointer);
end;

{ TEngine }

constructor TEngine.Create;
begin
  FApp := InitApp;//(System.DelphiActivity, nil, 0);
//  FApp.savedState := nil;
end;

(* *
  * Initialize an EGL context for the current display.
*)
function TEngine.InitDisplay: integer;
const
  (*
    * Here specify the attributes of the desired configuration.
    * Below, we select an EGLConfig with at least 8 bits per color
    * component compatible with on-screen windows
  *)
  attribs: array[0..8] of EGLint = (EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE,
    8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE);
var
  w, h, format: EGLint;
  numConfigs: EGLint;
  config: EGLConfig;
  surface: EGLSurface;
  context: EGLContext;
  display: EGLDisplay;
begin
  // initialize OpenGL ES and EGL

  display := eglGetDisplay(EGL_DEFAULT_DISPLAY);

  eglInitialize(display, 0, 0);

  (* Here, the application chooses the configuration it desires. In this
    * sample, we have a very simplified selection process, where we pick
    * the first EGLConfig that matches our criteria *)
  eglChooseConfig(display, @attribs[0], @config, 1, @numConfigs);

  (* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is
    * guaranteed to be accepted by ANativeWindow_setBuffersGeometry().
    * As soon as we picked a EGLConfig, we can safely reconfigure the
    * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. *)
  eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, @format);

  ANativeWindow_setBuffersGeometry(FApp.window, 0, 0, format);

  surface := eglCreateWindowSurface(display, config, FApp.window, nil);
  context := eglCreateContext(display, config, nil, nil);

  if eglMakeCurrent(display, surface, surface, context) = EGL_FALSE then
  begin
    Log_warn('Unable to eglMakeCurrent',[]);
    result := -1;
    exit;
  end;

  eglQuerySurface(display, surface, EGL_WIDTH, @w);
  eglQuerySurface(display, surface, EGL_HEIGHT, @h);

  FDisplay := display;
  FContext := context;
  FSurface := surface;
  FWidth := w;
  FHeight := h;
  FState.angle := 0;

  // Initialize GL state.
  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
  glEnable(GL_CULL_FACE);
  glShadeModel(GL_SMOOTH);
  glDisable(GL_DEPTH_TEST);

  result := 0;
end;

(* *
  * Just the current frame in the display.
*)
procedure TEngine.DrawFrame;
begin
  if FDisplay = nil then
  begin
    // No display.
    exit;
  end;

  // Just fill the screen with a color.
  glClearColor(FState.x / FWidth, FState.angle, FState.y / FHeight, 1);
  glClear(GL_COLOR_BUFFER_BIT);

  eglSwapBuffers(FDisplay, FSurface);
end;

(* *
  * Tear down the EGL context currently associated with the display.
*)
procedure TEngine.TermDisplay;
begin
  if FDisplay <> EGL_NO_DISPLAY then
  begin
    eglMakeCurrent(FDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    if FContext <> EGL_NO_CONTEXT then
    begin
      eglDestroyContext(FDisplay, FContext);
    end;
    if FSurface <> EGL_NO_SURFACE then
    begin
      eglDestroySurface(FDisplay, FSurface);
    end;
    eglTerminate(FDisplay);
  end;
  FAnimating := 0;
  FDisplay := EGL_NO_DISPLAY;
  FContext := EGL_NO_CONTEXT;
  FSurface := EGL_NO_SURFACE;
end;

(* *
  * Process the next input event.
*)
function engine_handle_input(App: PAndroid_app; Event: PAInputEvent)
  : Int32; cdecl;
begin
  result := TEngine(App.userData).HandleInput(Event);
end;

function TEngine.HandleInput(Event : PAInputEvent) : Int32;
begin
  if AInputEvent_getType(Event) = AINPUT_EVENT_TYPE_MOTION then
  begin
    FAnimating := 1;
    FState.x := AMotionEvent_getX(Event, 0);
    FState.y := AMotionEvent_getY(Event, 0);
    result := 1;
  end else begin
    result := 0;
  end;
end;

(* *
  * Process the next main command.
*)
procedure engine_handle_cmd(App: PAndroid_app; Cmd: Int32); cdecl;
begin
  TEngine(App.userData).HandleCmd(Cmd);
end;

procedure TEngine.HandleCmd(Cmd : Int32);
begin
  case Cmd of
    APP_CMD_SAVE_STATE:
      begin
        // The system has asked us to save our current state.  Do so.
        New(PSaved_state(FApp.savedState));
        PSaved_state(FApp.savedState)^ := FState;
        FApp.savedStateSize := sizeof(TSaved_state);
      end;
    APP_CMD_INIT_WINDOW:
      begin
        // The window is being shown, get it ready.
        if FApp.window <> nil then
        begin
          InitDisplay;
          DrawFrame;
        end;
      end;
    APP_CMD_TERM_WINDOW:
      begin
        // The window is being hidden or closed, clean it up.
        TermDisplay;
      end;
    APP_CMD_GAINED_FOCUS:
      begin
        // When our app gains focus, we start monitoring the accelerometer.
        if FAccelerometerSensor <> nil then
        begin
          ASensorEventQueue_enableSensor(FSensorEventQueue,
            FAccelerometerSensor);
          // We'd like to get 60 events per second (in us).
          ASensorEventQueue_setEventRate(FSensorEventQueue,
            FAccelerometerSensor, round((1000.0 / 60) * 1000));
        end;
      end;
    APP_CMD_LOST_FOCUS:
      begin
        // When our app loses focus, we stop monitoring the accelerometer.
        // This is to avoid consuming battery while not being used.
        if FAccelerometerSensor <> nil then
        begin
          ASensorEventQueue_disableSensor(FSensorEventQueue,
            FAccelerometerSensor);
        end;
        // Also stop animating.
        FAnimating := 0;
        DrawFrame;
      end;
  end;
end;

(* *
  * This is the main entry point of a native application that is using
  * android_native_app_glue.  It runs in its own thread, with its own
  * event loop for receiving input events and doing other things.
*)

procedure TEngine.Run;
var
  ident, events: integer;
  source: Pandroid_poll_source;
  Event: ASensorEvent;
begin
  // Make sure glue isn't stripped.
  app_dummy;

  FApp.userData := self;
  FApp.onAppCmd := engine_handle_cmd;
  FApp.onInputEvent := engine_handle_input;

  // Prepare to monitor accelerometer
  FSensorManager := ASensorManager_getInstance();
  FAccelerometerSensor := ASensorManager_getDefaultSensor(FSensorManager,
    ASENSOR_TYPE_ACCELEROMETER);
  FSensorEventQueue := ASensorManager_createEventQueue(FSensorManager,
    FApp.Looper, LOOPER_ID_USER, nil, nil);

  if (FApp.savedState <> nil) then
  begin
    // We are starting with a previous saved state; restore from it.
    FState := PSaved_state(FApp.savedState)^;
  end;

  // loop waiting for stuff to do.

  while true do
  begin
    // Read all pending events.

    // If not animating, we will block forever waiting for events.
    // If animating, we loop until all events are read, then continue
    // to draw the next frame of animation.
    while true do
    begin
      ident := ALooper_pollAll(ifthen(FAnimating <> 0, 0, -1), nil, @events, @source);
      if ident < 0 then break;

      // Process this event.
      if source <> nil then
      begin
        source.process(FApp, source);
      end;

      // If a sensor has data, process it now.
      if ident = LOOPER_ID_USER then
      begin
        if FAccelerometerSensor <> nil then
        begin
          while (ASensorEventQueue_getEvents(FSensorEventQueue, @Event, 1) > 0) do
          begin
            Log_info('accelerometer: x=%f y=%f z=%f',[
              Event.acceleration.x, Event.acceleration.y, Event.acceleration.z]);
          end;
        end;
      end;

      // Check if we are exiting.
      if FApp.destroyRequested <> 0 then
      begin
        TermDisplay();
        exit;
      end;
    end;

    if FAnimating <> 0 then
    begin
      // Done with events; draw next animation frame.
      FState.angle := FState.angle + 0.01;
      if FState.angle > 1 then
      begin
        FState.angle := 0;
      end;

      // Drawing is throttled to the screen update rate, so there
      // is no need to do timing here.
      DrawFrame;
    end;
  end;
end;
end.
最後にもう一度プロジェクトソースを開き、上述のコードを呼び出します。
program NativeActivityTest;

uses
  System.StartUpCopy,
  NA_main in 'NA_main.pas';

{$R *.res}
var
  app : TEngine;
begin
  app := TEngine.Create;
  app.Run;
  app.Free;
end.
これで実機実行するとFireMonkeyを使わないAndroidアプリケーションの出来上がりです。

|

« FireMonkey2のTGridでOwnerDraw | トップページ

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: DelphiXE5でFireMonkeyを使わずにAndroid開発:

« FireMonkey2のTGridでOwnerDraw | トップページ