728x90

출처 : FMXTCanvasDrawFunctions (Delphi)
Fastest way to draw pixels in FireMonkey
Firemonkey Rotate Text
Delphi XE4 FireMonkey/iOS DrawBitmap not working
FIREMONKEY에서 썸네일을 만들려면 (1)
[FMX,VCL 비교] #1 VCL의 Canvas와 FMX의 Canvas 차이
[FM] TCanvasGdiPlus 문제점

FireMonkey - FFMpeg Player

ffmpeg 컴파일 하여 Delphi FireMonkey를 이용해서 Android와 IOS용 Player를 만들어 보았습니다.

처음 XE5가 나오고 일주일 정도 예상했는데, 한달이나 걸렸네요.

처음에는 소스를 공개 할 생각으로 작업했는데, 이것 때문에 너무 고생하다보니, 나중에 강좌를 통해서
강좌를 듣는 분들에게 제공 하도록 하겠습니다.

Android용 Player를 만들어 본적이 있어, JNI 방식으로 Player로 시작했는데,
Delphi는 GCC과 같은 방식으로 호출하는 것이라 JNI 인터페이스로 구현 할 필요가 없더군요.

apk에 정적 라이브러리(static library - .so)를 같이 배포해서 동적로딩 하려고 했었는데,
절대 경로로 로딩은 되는데, apk에 존재하는 정적 라이브러리는 로딩안되더군요.

지금까지 결론은 안드로이드나 IOS는 공유 라이브러리(Shared Library)롤 만들고 컴파일시
링크(ld) 되어야 하는것으로 확인 되었습니다.

IOS의 경우 시뮬레이터에서는 정적 라이브러리를 제공하고 있습니다.

FFMPEG 상용 컴포넌트도 Trial 버전은 시뮬레이터용 .so 파일만 제공하고,
실행 가능한 정품은 .a 파일을 링크해서 배포 하도록 하고 있는것 같습니다.

.a 파일이 존재하는 경로는 Search Path에 추가 하시면 되고,
LD 옵션에 추가되는 라이브러리를 추가 하시면 됩니다.

IOS의 경우 libbz2.dylib, libm.dylib가 추가로 필요하는데, 기존 라이브러리 목록에 빠져 있어서,
SDK Option에 추가 해야 됩니다.

최근에 맥과 IOS도 업데이트 되면서, 기존에 컴파일 했던 방식도 변경된 부분이 많아서,
기존에 llvm-gcc로 컴파일 했던것을 clang으로 해야 되더군요.

iTune의 공유파일를 통해서 동영상 파일을 추가 하려고 했는데, UIFileSharingEnabled 설정도
편법뿐 아직 Delphi에서 설정하는 방법은 없는것 같습니다.

ffmpeg를 컴파일해서 이미지를 보여주는 부분까지는 했는데, 아직 FireMonkey에서 TImage를 이용해서 보여주고 있어,
이미지 출력 주소를 이용해서 바로 보여주는 부분은 아직 찾지 못해 동영상이 출력시 계속 깜박거리고 있습니다.

아직 FireMonkey 관련 자료가 많이 부족해 너무 많이 고생했네요.
그 덕분에 많이 배우기도 했지만, 아직 컴파일러 자체적으로 개선해야 될 부분도 많아 보입니다.

컴파일 동영상

컴파일해서 안드로이드(옵티머스G)에 실행하고, Targer 대상을 IOS로 변경하여 컴파일후 IPAD에 실행하는 동영상입니다.
맥에서 VM으로 컴파일해서 컴파일 과정이 느립니다. 뛰어 넘어 가면서 보세요.

-

Android 라이브러리 패스 추가

libffmpeg.a, libhoffplay.a 파일이 존재하는 lib\android 경로 추가

Android에 컴파일시 필요한 라이브러리 추가

-lffmpeg -lcompiler_rt -lz 추가

IOS 라이브러리 추가

libbz2.dylib, libm.dylib 추가후 Update Local File Chahe 버튼를 누르세요.

IOS 라이브러리 패스 추가

libavcodec.a, libavdevice.a, libavformat.a, libavutil.a, libhoplayer.a, libswscale.a  파일이 존재하는 lib\ios 경로 추가

IOS에 컴파일시 필요한 라이브러리 추가

-lavcodec -lavdevice -lavformat -lavutil -lswscale -lz -lm -lbz2 추가

iTunes에서 파일공유 설정

동영상에서 보았듯이 컴파일시에 Deployment 파일 목록의 {프로젝트명}.info.plist 파일을 수정해야 적용됩니다.
(컴파일시마다 파일이 변경됨)

iTunes에서 파일공유에서 파일 확인


-

728x90
728x90

aaplus-pc-tiles.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context.xsd">
	

	<bean id="templateResolver"
		class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
		<property name="prefix" value="/WEB-INF/view/" />
		<property name="suffix" value=".html" />
		<property name="templateMode" value="HTML5" />
		<property name="cacheable" value="true" />
	</bean>
	<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
		<property name="templateResolver" ref="templateResolver" />
	</bean>
	<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
		<property name="templateEngine" ref="templateEngine" />
		<property name="order" value="1" />
		<property name="viewNames" value="thymeleaf/*" />
	</bean>

	<!-- Resolves view names to Tiles definitions -->
	<beans profile="tiles3">
		<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver">
			<property name="order" value="0"/>
		</bean>
	
		<!-- Configures Tiles 3-->
		<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
			<property name="definitions">
				<list>
					<value>/WEB-INF/view/jsp/03-tiles/tiles.xml</value>
					<value>/WEB-INF/view/jsp/admin/tiles.xml</value>
				</list>
			</property>
		</bean>
	</beans>
	
	<beans profile="tiles2">
		<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.tiles2.TilesViewResolver">
			<property name="order" value="0"/>
		</bean>
	
		<!-- Configures Tiles 2 -->
		<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
			<property name="definitions">
				<list>
					<value>/WEB-INF/view/jsp/03-tiles/tiles.xml</value>
					<value>/WEB-INF/view/jsp/admin/tiles.xml</value>
				</list>
			</property>
		</bean>
	</beans>

</beans>

/WEB-INF/view/jsp/03-tiles/tiles.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tiles-definitions PUBLIC 
	"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN" 
	"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">	

<tiles-definitions>

	<definition name="03-tiles/*" template="/WEB-INF/view/jsp/03-tiles/layout.jsp">
		<put-attribute name="main" value="/WEB-INF/view/jsp/03-tiles/{1}.jsp" />
	</definition>
	
</tiles-definitions>

/WEB-INF/view/jsp/03-tiles/layout.jsp

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>

<html>
<head>
	<meta charset="utf-8"/>
	<spring:url value="/common/style" var="styleUrl" />
	<link href="${styleUrl}/app.css" rel="stylesheet"/>
	<title><c:out value="${title}"/></title>
</head>

<body>
	<div class="container" style="padding-top: 50px;">
		<jsp:include page="/WEB-INF/view/jsp/menu.jsp"/>	
		<tiles:insertAttribute name="main" />
		<jsp:include page="/WEB-INF/view/jsp/footer.jsp"/>	
	</div>
</body>
</html>
728x90
728x90

출처 : Mobile Tutorial: Using InterBase ToGo (iOS and Android)
http://www.pclviewer.com/android/
Linux Certif - dlopen
Getting the Battery Level on Android With Delphi
XE4 Mobile Tip #2 ? Loading local HTML content
Android/SQLite 공유 라이브러리를 이용하는 C로 프로그램 작성
Delphi XE4 with iOS / #08. Hello World without Firemonkey
Thread: What is the right procedure to add the additional framework on iOS ?
Unable to dlopen(libsomething.so) Cannot load library: link_image[1995]: failed to link libsomething.so

정적 라이브러리 참조

Androidapi.Log.pas (델파이 일부 소스 발취)

unit Androidapi.Log;

interface

{$I Androidapi.inc}

type
  android_LogPriority = (
    ANDROID_LOG_UNKNOWN,
    ANDROID_LOG_DEFAULT,
    ANDROID_LOG_VERBOSE,
    ANDROID_LOG_DEBUG,
    ANDROID_LOG_INFO,
    ANDROID_LOG_WARN,
    ANDROID_LOG_ERROR,
    ANDROID_LOG_FATAL,
    ANDROID_LOG_SILENT
 );
 {$EXTERNALSYM android_LogPriority}


function __android_log_write(Priority: android_LogPriority; const Tag, Text: MarshaledAString): Integer; cdecl;
  external AndroidLogLib name '__android_log_write';
 {$EXTERNALSYM android_LogPriority}

{ Helper functions }
function LOGI(Text: MarshaledAString): Integer;
...

implementation

function LOGI(Text: MarshaledAString): Integer;
begin
  Result := __android_log_write(android_LogPriority.ANDROID_LOG_INFO, 'info', Text);
end;

...

end.

정적라이브러리 소스

testlib.h

/*
 * testlib.h
 *
 *  Created on: 2012. 9. 6.
 *      Author: bluexmas
 */
 
#ifndef LIBTEST_H_
#define LIBTEST_H_
 
extern const char* get_version();
 
#endif /* LIBTEST_H_ */

testlib.c

/*
 * testlib.cpp
 *
 *  Created on: 2012. 9. 6.
 *      Author: bluexmas
 */
 
#include "testlib.h"
 
const char* get_version() {
  return "testlib ver 0.1";
}

Makefile

ANDROID_NDK_ROOT = /opt/android-ndk-r9
NDK_TOOLCHAIN_VERSION = 4.6
TOOLCHAIN = /opt/android-9-toolchain

CROSS  = $(TOOLCHAIN)/bin/arm-linux-androideabi-
INCDIR = $(TOOLCHAIN)/sysroot/usr/include
LIBDIR = $(TOOLCHAIN)/sysroot/usr/lib

CC     = $(CROSS)gcc
AR     = $(CROSS)ar $(ARFLAGS) r
RANLIB = $(CROSS)ranlib
NM     = $(CROSS)nm

CFLAGS = -g $(INC) -fPIC 

TARGET = libtest.so

OBJS = testlib.o
#SRCS = testlib.c

.SUFFIXES : .c .o 

all : $(TARGET)

$(TARGET) : $(OBJS)
	$(CC) -shared -Wl,-soname,$@ -o $@ $(OBJS)

dep :
	gccmakedep $(INCDIR) $(SRCS)

clean :
	rm -rf $(OBJS) $(TARGET) core

 컴파일

파일

testlib.h /testlib.c /Makefile

위에서 만들어진 정적 라이브러리 파일을 /sdcard 폴더에 복사한 뒤(직접복사), 동적으로 로딩하여 get_version 함수룰 호출하는 예제입니다. library\lib\armeabi\ 경로에서 상대경로로 읽으려고 했지만, 절대경로만 인식하는 것 같습니다. 참고 자료가 별로 없어 확인 할 수 없었습니다.

Unit2.pas

unit Unit2;

interface

uses
  Classes, SysUtils, SyncObjs,
  System.IOUtils, Posix.Dlfcn;

//function __get_version(): MarshaledAString; cdecl; external '/sdcard/libtest.so' name 'get_version';

type
  _get_version = function(): MarshaledAString; {$IFNDEF CLR}cdecl;{$ENDIF}

function LoadLib() : IntPtr;

var
  get_version : _get_version;

implementation

function LoadLib() : IntPtr;
begin
  //
  //hLiteLib2 := dlopen(MarshaledAString(GetHomePath() + PathDelim + 'libtest.so'), RTLD_LAZY);
  //hLiteLib2 := dlopen(MarshaledAString(TPath.Combine(TPath.GetDocumentsPath, 'libtest.so')), RTLD_LAZY);
  Result := dlopen(MarshaledAString('/sdcard/libtest.so'), RTLD_LAZY);

  if NativeUInt(Result) > 0 then begin
    get_version := dlsym(Result, MarshaledAString('get_version'));
  end;
end;

Initialization
  get_version := nil;

end.

Unit1.pas

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types,
  FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.WebBrowser, FMX.StdCtrls, FMX.Layouts,
  FMX.Edit, FMX.Memo;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Edit1: TEdit;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    hLiteLib: IntPtr;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  System.IOUtils, Unit2;

{$R *.fmx}

procedure TForm1.Button1Click(Sender: TObject);
begin
  //ShowMessage(TPath.Combine(TPath.GetDocumentsPath, 'libtest.so'));

  hLiteLib := LoadLib();
  if NativeUInt(hLiteLib) = 0 then begin
    ShowMessage('Cannot load client library: ' + 'libtest.so');
  end;

  if Assigned(get_version) then begin
    Edit1.Text := get_version(); // IntToStr( hLiteLib );
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile(TPath.Combine(TPath.GetDocumentsPath, 'memo.txt'));
end;

end.

Deployment 화면

실행결과

실행결과 Button1 선택

실행결과 Button2 선택

728x90
728x90

출처 : Installing the Android Development Tools
Android Mobile Application Development
DELPHI XE4에서 FireMonkey Mobile용 컴포넌트 추가하기.

1. ShowModal 사용하기 그리고 문제점
ShowModal Dialogs in FireMonkey Mobile Apps

XE5에서 안드로이드의 경우 기존과 동일한 ShowModal를 지원하지 않는 것 같습니다.
안드로이드의 경우 모든 화면 제어가 쓰레드로 동작하기 때문에 기존의 ShowModal를 지원 못하는게 아닌가 싶습니다.

기존 코드

function GetConnectionInfo(AOwner: TComponent): TDBConnection;
var
  FConnect: TFConnect;
begin
  Result := nil;
  FConnect := TFConnect.Create(AOwner);
  try
    if FConnect.ShowModal = mrOk then
    begin
      Result := FConnect.ResultDBConnection;
    end;

  finally
    FConnect.Free;
  end;
end;

수정된 코드

var
  FConnect: TFConnect;           // 전역변수

function GetConnectionInfo(AOwner: TComponent; OnDBConnected : TNotifyEvent): TDBConnection;
var
  ConnectFormBase: TConnectFormBase;
begin
  Result := nil;
  try
    if Assigned(FConnect) then begin
      FConnect.Show;
    end else begin
      FConnect := TFConnect.Create(AOwner);
      FConnect.OnDBConnected := OnDBConnected;
      FConnect.ShowModal(procedure(ModalResult : TModalResult)
      begin
        if ModalResult = mrOk then begin
          FConnect.DoConnect();
          if Assigned(Self.OnDBConnected) then begin
            FConnect.OnDBConnected(FConnect.ResultDBConnection);
          end;
        end;
        if Assigned(FConnect) then
          FConnect.DisposeOf;
        FConnect := nil;

        frmMDIMain.Invalidate;
      end);
    end;
  except
    on e : Exception do
      ShowMessage(e.Message);
  end;
end;

지금 까지 찾아낸 문제점

1. ShowModal의 결과 값을 이벤트 형태로 받아 합니다.

FConnect.ShowModal(procedure(ModalResult : TModalResult)
begin
  f ModalResult = mrOk then begin
    // mrOk인 경우 처리
  end;

  // 화면 닫기
  frmMDIMain.DisposeOf;
end);

문제점1 : 변수 사용의 불편함 / In-Line으로 procedure를 구현하므로 전역으로 정의된 변수를 제외하고는 접근이 되지 않음

문제점2 : FormClose 호출 시점 

기존에는 if FConnect.ShowModal = mrOk then 이후 문장이 실행 되기 전에 FormClose가 호출 되었지만,
XE5 안드로이드의 경우 DisposeOf가 호출 시점에 FormClose가 호출 됩니다.

CallBack 이벤트 함수(OnDBConnected 함수)를 만들어서 처리했습니다.

2. 기존에 ShowModal 처럼 정지(?) 되지 않습니다.

문제점1ShowModal 이후 문장이 그대로 실행 됩니다.

ShowModal 이후 문장인 finally 가 바로 실행(FConnect.Free; 실행) 되어 화면이 나타났다가 빠르게 사라집니다.

문제점2 : 정지 되지 않기 때문에 ShowModal 호출하는 이벤트를 다시 호출하면 두개의 화면이 생성됩니다.

Form 변수인 FConnect 를 전역 변수로 선언하고, Assigned 유효 한지 체크하고, 종료시 FConnect := nil; 로 초기화 했습니다.

문제점3 : FireMonkey가 느려서 닫기 버튼을 두번 누르면, DisposeOf 가 두번 호출 되면서 FConnect가 닫혀지고, Main 화면까지도 닫아 지는 것 같은 문제가 있습니다. (버그로 의심됨)

문제점4 DisposeOf 가 호출되고 Main 화면이 나타나야 할 텐데, 화면이 그대로 남아 있어, Main 폼의 frmMDIMain.Invalidate; 를 호출해야 보여집니다. (버그로 의심됨)

- end -

728x90
728x90

다운로드

http://old-releases.ubuntu.com/releases/oneiric/

NDK 다운로드

http://developer.android.com/tools/sdk/ndk/index.html

SSH 서버 설치
http://pirtaja.tistory.com/9

$ sudo apt-get install openssh-server
$ sudo /etc/init.d/ssh restart

jdk 1.6 설치 (Gingerbread and newer)
Installing Java6 JDK on Ubuntu 11.10

$ sudo add-apt-repository ppa:ferramroberto/java
$ sudo apt-get update
$ sudo apt-get install sun-java6-jdk sun-java6-plugin

.bashrc 내용 추가

# Java Setting
export JAVA_HOME=/usr/lib/jvm/java-6-sun
export ANDROID_JAVA_HOME=$JAVA_HOME

Android PDK 빌드 환경 구축 (64Bit OS)
http://source.android.com/source/initializing.html
[Android/안드로이드] Ubuntu에서 E: Package 'lib32readline5-dev' has no installation candidate. 발생시 대처 방법.

$ sudo apt-get install git-core gnupg flex bison gperf build-essential \
  zip curl zlib1g-dev libc6-dev lib32ncurses5-dev ia32-libs \
  x11proto-core-dev libx11-dev lib32readline-gplv2-dev lib32z-dev \
  libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown \
  libxml2-utils xsltproc

Libx11-dev:i386 설치

$ sudo apt-get install libx11-dev:i386

repo 스크립트 다운로드
[Android/안드로이드] 64bit Ubuntu Android ICS Source 다운받기

$ mkdir ~/bin
$ PATH=~/bin:$PATH
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod a+x ~/bin/repo

저장소 초기화

$ sudo mkdir /opt/android-source
$ cd /opt/android-source
$ sudo chown test:test .            <-- 옵션
$ repo init -u https://android.googlesource.com/platform/manifest
$ repo init -u https://android.googlesource.com/platform/manifest -b android-4.0.3_r1

소스 내려 받기

$ repo sync

안드로이드 소스 전체빌드
[안드로이드] 안드로이드 소스 전체빌드 해보기
안드로이드 빌드하기

안드로이드 소스 빌드

$ cd /opt/android-source
$ make

빌드 오류 해결법

android build error 해결법
ICS Source download
slang_rs_export_foreach.cpp , error : variable ‘ParamName’ set but not used
Ubuntu 11.10 ics build error

make: *** [out/host/linux-x86/obj/EXECUTABLES/obbtool_intermediates/Main.o] Error 1

$ vi build/core/combo/HOST_linux-x86.mk
#HOST_GLOBAL_CFLAGS += -D_FORTIFY_SOURCE=0
HOST_GLOBAL_CFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0

make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libGLcommon_intermediates/GLDispatch.o] Error 1

$ sudo apt-get install mesa-common-dev

make: *** [out/host/linux-x86/obj/EXECUTABLES/llvm-rs-cc_intermediates/slang_rs_export_foreach.o] Error 1

$ vi frameworks/compile/slang/Android.mk
#local_cflags_for_slang := -Wno-sign-promo -Wall -Wno-unused-parameter -Werror
local_cflags_for_slang := -Wno-sign-promo -Wall -Wno-unused-parameter

make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/liboprofile_pp_intermediates/arrange_profiles.o] Error 1

external/oprofile/libpp/format_output.h 파일의 94번행

-   mutable counts_t & counts;
+   counts_t & counts;

make: *** [out/host/linux-x86/obj/EXECUTABLES/test-librsloader_intermediates/test-librsloader] Error 1

external/llvm/llvm-host-build.mk 내용 추가

+   LOCAL_LDLIBS := -lpthread -ldl

make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libgtest_host_intermediates/gtest-all.o] Error 1

vi external/gtest/include/gtest/internal/gtest-param-util.h 내용추가

#include <cstddef>

make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libMesa_intermediates/src/glsl/linker.o] Error 1

vi external/mesa3d/src/glsl/linker.cpp 내용추가

#include <stddef.h>

make: *** [out/host/linux-x86/obj/STATIC_LIBRARIES/libmtp_intermediates/MtpDevice.o] Error 1

vi frameworks/base/media/mtp/MtpDevice.cpp 수정

-   int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC);
+   int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC, 0777);

Android NDK(C/C++) 설치

http://developer.android.com/tools/sdk/ndk/index.html

$ sudo mv android-ndk-r9-linux-x86_64.tar.bz2 /opt
$ sudo tar xvf android-ndk-r9-linux-x86_64.tar.bz2

.profile-ndk 파일 작성

export ANDROID_NDK_ROOT=/opt/android-ndk-r9
export NDK_TOOLCHAIN_VERSION=4.6
export TOOLCHAIN=/opt/android-9-toolchain

export PATH=$TOOLCHAIN/bin:$PATH

.profile-ndk 실행 (NDK 빌드시 사용)

$ chmod a+x .profile-ndk 
$ source .profile-ndk

ToolChain 만들기

$ $ANDROID_NDK_ROOT/build/tools/make-standalone-toolchain.sh \
--toolchain=arm-linux-androideabi-4.6 \
--ndk-dir=$ANDROID_NDK_ROOT \
--install-dir=$TOOLCHAIN \
--platform=android-9
$ sudo chown test:test -hR /opt/android-9-toolchain

728x90
728x90

출처 : 안드로이드(Android) 유니티(Unity3D) 연동 ,값전달, Toast, Dialog 등...
[android] 메시지 핸들러(Handler) 사용하기
[안드로이드] 유니티 C# 클래스와 자바 클래스간의 연동
Trying to create a simple AlertDialog plugin, crash occurs
Platform Dependent Compilation
20. 카드 짝찾기 게임(7) - 안드로이드용으로 변환 (Keystore 사용 빌드)

Unity에서 Android Activity와 연동하기 위해 Unity C# 소스 작성

Unity의 AndroidJavaObject 객체를 이용해서 Android에 호출하고 호출한 결과값은 바로 받을 수 없어 로직이 끝나고 다시 Android Activity에서 Unity C# 함수를 호출해서 결과 값을 받을 수 있습니다.

아래 예제는 Cs_CubeManager을 싱글톤 객체로 만들어서 AndroidJavaObject 객체를 Unity 전체에서 사용하도록 했으며, Cs_AndroidManager에서 AndroidJavaObject 객체를 할당 받도록 했습니다.

Android호출시 다른 Activity를 생성하거나 Toast 메시지 출력시 아래와 같이 오류가 발생하므로 Handle 사용해야 합니다.

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

개발시나 iOS에서는 동작하지 않도록 preprocessor로 UNITY_ANDROID를 사용하여, Android에서만 동작할 수 있도록 소스작성 합니다.

#if UNITY_ANDROID 
    // 유니티가 동작하는 액티비티를 저장하는 변수.
    public AndroidJavaObject activity = null;
#endif	

Android Activity와 연동하기 위해서 C# AndroidJavaObject 객체를 아래와 같이 할당 받아야 합니다.

AndroidJavaObject activity = null;

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
cubeMng.activity = jc.GetStatic<AndroidJavaObject>("currentActivity");

AndroidJavaObject 객체의 Call 메소드를 호출 하여 Android Activity의 메소드를 호출합니다.

	public void NativeAlert(string message) {
#if UNITY_ANDROID 
    // 유니티가 동작하는 액티비티를 저장하는 변수.
    	activity.Call("AlertMessage", message);
#endif			
	}

Cs_AndroidManager.cs

using UnityEngine;

public class Cs_AndroidManager : MonoBehaviour {
	
    //private static Cs_AndroidManager _instance = null;
	
	private Cs_CubeManager cubeMng;

    public string androidLog = "No Log";
	
    void Start() {				
		cubeMng = Cs_CubeManager.getInstance();
		cubeMng.device_model = "test";		
#if UNITY_ANDROID
		AndroidCall();
#endif		
	}
	
#if UNITY_ANDROID 
    // 유니티가 동작하는 액티비티를 저장하는 변수.
    //public AndroidJavaObject activity = null;

    void AndroidCall()
    {
        // 현재 실행 중인 유니티 액티비티를 가져와서 변수에 저장.
        //AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
		AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        cubeMng.activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
		cubeMng.activity.Call("initActivity", "Cs_AndroidManager");
    }
#endif	

    // 자바 클래스로부터 전달받은 로그를 보여줍니다.
    public void AndroidLog(string newAndroidLog) {
        androidLog = newAndroidLog;
    }
	
	public void SetCountryCode(string code) {
		cubeMng.country_code = code;
	}
	
	public void SetDeviceID(string did) {
		cubeMng.device_id = did;
	}
	
	public void SetDeviceModel(string dModel) {
		cubeMng.device_model = dModel;
	}	
	
	/*
    public static Cs_AndroidManager Instance
    {
        get
        {
            if (_instance == null)
            {
				
				Debug.Log("------------- _instance.0 = " + _instance);
				
                _instance = FindObjectOfType(typeof(Cs_AndroidManager)) as Cs_AndroidManager;
				
				Debug.Log("------------- _instance.1 = " + _instance);
				
                if (_instance == null)
                {
                    _instance = new GameObject("Cs_AndroidManager").AddComponent<Cs_AndroidManager>();
                }
				
				Debug.Log("------------- _instance.2 = " + _instance);
            }

            return _instance;
        }
    }
    */
}

Cs_CubeManager.cs

using UnityEngine;
using System;

public class Cs_CubeManager {
	
	// 싱글톤 인스턴스 
	private static Cs_CubeManager instance = null;
	
	public string country_code = "KOR";
	
	public string device_id = "";
	
	public string device_model = "";	
	
#if UNITY_ANDROID 
    // 유니티가 동작하는 액티비티를 저장하는 변수.
    public AndroidJavaObject activity = null;
#endif	

	private Cs_CubeManager() {
/*
		if (Camera.mainCamera != null && Camera.mainCamera.GetComponent("WWWWiki") != null)
			wWWWiki = Camera.mainCamera.GetComponent("WWWWiki") as WWWWiki;
*/
	}
	
	public static Cs_CubeManager getInstance() {
		if (instance == null) {
			instance = new Cs_CubeManager();
		}
		return instance;
	}
		
	public void NativeAlert(string message) {
#if UNITY_ANDROID 
    // 유니티가 동작하는 액티비티를 저장하는 변수.
    	activity.Call("AlertMessage", message);
#endif			
	}	
}

Unity3D 에서 Android 소스로 Export 하기

1. 메뉴 [Build Settings...] 선택합니다.

2. [Build Settings] 화면에서 Platform 목록에서 Android 항목을 선택합니다.

3. Bundle Identifier에 Android에서 사용할 Package 명을 입력합니다.

4. 다시 [Build Settings] 화면에서 Create Eclipse project 체크 박스를 선택하고, 버튼 [Export]을 선택합니다.

5. Export 받을 경로를 선택하고, 버튼 [폴더 선택]을 선택합니다.

6. 탐색기로 아래와 같이 Export 받은 파일 목록을 확인 합니다.

파일 목록에서 classes.jar 파일을 확인 할 수 있는데,
C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androidplayer\bin 폴더의 classes.jar 파일과 동일한 파일로
Unity에서 Export 받으면 자동으로 classes.jar 파일을 복사해서 빌드 라이브러리에 자동으로 추가 됩니다.

7. [Package Explorer] 에서 오른쪽 버튼을 선택해서, 팝업 메뉴에서 메뉴 [Import...]를 선택합니다.

8. 메뉴 [Existing Projects into Workspace]를 선택합니다.

9. 버튼 [Browse...]을 선택합니다.

10. Unity에서 Export 받은 폴더를 선택합니다.

11. 버튼 [Finish]을 선택합니다.

12. Import 된 파일 목록을 확인합니다.

Android 소스 작성

Unity에서 Export 받은 소스에는 UnityPlayerActivity 를 상속 받지 않아서,
UnityPlayerActivity 겍체를 상속 받아 CubeMainActivity 소스를 작성하고, AndroidManifest.xml에서 Main Activity를 CubeMainActivity 수정했습니다.

Unity에서 호출 받은 로직을 처리하기 위해서 Handler 상속 받아서 Unity3DHandler.java 소스를 작성합니다.

CubeMainActivity.java

package com.azanghs.cube;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;

import com.example.android.trivialdrivesample.util.IabHelper;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;

public class CubeMainActivity extends UnityPlayerActivity {
	
	private static String TAG = CubeMainActivity.class.getSimpleName();
	
	private TelephonyManager m_telephonyManager;
	
	String androidId = "KOR";	
	
    // The helper object
    IabHelper mHelper;
    
    private Unity3DHandler handler = null;
    
   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       handler = new Unity3DHandler(this);

       m_telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
     
       Log.i(TAG, "Build.MODEL = " + Build.MODEL);
       Log.i(TAG, "Build.DEVICE = " + Build.DEVICE);
       Log.i(TAG, "Build.PRODUCT = " + Build.PRODUCT);
   }
   
	@Override
	public void onBackPressed() {
		this.finishMessage();
	}
	
	public void QuitApplication() {
		handler.sendEmptyMessage(0);
	}
	
	public void AlertMessage(final String message) {
		Message msg = handler.obtainMessage(1);
		msg.obj = message;
		handler.sendMessage(msg);
	}
	
	public void finishMessage() {
		new AlertDialog.Builder(this).setMessage("프로그램을 종료하시겠습니까?")
		.setPositiveButton("확인", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
				//System.exit(0);
				UnityPlayer.UnitySendMessage("GameObject", "FinishApp", "");
			}
		}).setNegativeButton("취소", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int whichButton) {
			}
		}).show();	
	}

   //요 부분입니다. 이걸... Thread가 불필요하다면 
   public void initActivity(final String messageFromUnity)
   {
	   
	   Log.i(TAG, "여기 호출 Build.MODEL = " + Build.MODEL);
	   
       UnityPlayer.UnitySendMessage("GameObject", "SetCountryCode", getResources().getConfiguration().locale.getISO3Country());
       UnityPlayer.UnitySendMessage("GameObject", "SetDeviceModel", Build.MODEL);       

       // 디바이스 ID
       String androidId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);       
       UnityPlayer.UnitySendMessage("GameObject", "SetDeviceID", androidId);
	   
       //
       //UnityPlayer.UnitySendMessage("Cs_AndroidManager", "AndroidLog", "4/[" + tagFromUnity + "]" + messageFromUnity);
   }

   public void alert(String message) {
       AlertDialog.Builder bld = new AlertDialog.Builder(this);
       bld.setMessage(message);
       bld.setNeutralButton("OK", null);
       Log.d(TAG, "Showing alert dialog: " + message);
       bld.create().show();
   }  
}

Unity3DHandler.java

package com.azanghs.cube;

import android.os.Handler;
import android.os.Message;

public class Unity3DHandler extends Handler {
	
	private CubeMainActivity mainActivity = null;
	
	public Unity3DHandler(CubeMainActivity mainActivity) {
		this.mainActivity = mainActivity;
	}
	
	public void handleMessage(Message msg) {
		switch (msg.what) {
		case 0:
			mainActivity.finishMessage();
			break;
		case 1:
			mainActivity.alert((String)msg.obj);
			break;			
		default:
			break;
		}		
	}
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" android:theme="@android:style/Theme.NoTitleBar" package="com.azanghs.cube" android:versionName="1.0" android:versionCode="1">
  <supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:anyDensity="true" />
  <application android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="false">
    <activity android:name=".CubeMainActivity" android:launchMode="singleTask" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|uiMode|touchscreen" android:screenOrientation="portrait">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>       
    <!--       
    <activity android:name="com.azanghs.cube.CubeProxyActivity" android:launchMode="singleTask" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="portrait">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity android:name="com.azanghs.cube.CubeActivity" android:launchMode="singleTask" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="portrait">
    </activity>
    <activity android:name="com.azanghs.cube.CubeNativeActivity" android:launchMode="singleTask" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen" android:screenOrientation="portrait">
      <meta-data android:name="android.app.lib_name" android:value="unity" />
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false" />
    </activity>
    -->
  </application>
  <uses-feature android:glEsVersion="0x00020000" />
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  <uses-feature android:name="android.hardware.touchscreen" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
  <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
  <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="17" />
</manifest>

Android 프로젝트를 Unity Plugin Jar로 Export 하기

1. 해당 프로젝트를 선택하고 메뉴 [Export...] 선택합니다.

2. 메뉴 [JAR file] 선택합니다.

3. 아래와 같이 선택합니다.
Jar 파일이 생성되는 파일 경로 Unity 소스 폴더의 %소스경로%\Assets\Plugins\Android 로 합니다. (변경불가)

4. 버튼 [Next >] 버튼 선택합니다.

5. 버튼 [Finish] 버튼 선택합니다.

6. Unity에서 Build 하면 Android 폰에 실행된 결과를 확인 할 수 있습니다.

- 설명 작성하려니 귀찬니즘이 발생해서 나중에 시간이 나면 다시 작성하도록 하겠음

728x90
728x90

Android Back 버튼 이벤트 ( 출처 : 원숭이가 만드는 게임 )

if ( Input.GetKeyDown(KeyCode.Escape) )  {
    // 안드로이드 Back버튼 눌렀을때 처리
}

Unity3D에서의 Singleton 사용
( 출처 : Unity3D에서의 singleton 패턴, Unify Community - Singleton,  Global Managers With Generic Singletons )

Unity3D - Android 연동
(출처 : 유니티4! 짜증나는 이클립스 연동 한방에 끝내보자[안드로이드] 유니티 C# 클래스와 자바 클래스간의 연동 )

Android - android id 구하기
( 출처 : Samsung developers - Android - Technical Docs )

Android - 국가코드 가져오기
( 출처 : 단말 정보들 가져오기 )

동적으로 ButtonMessage에 target설정 / onClick 클릭 객체 가지고 오기
( 출처 : Topic: How to detect if mouse is clicking on NGUI area? )
void createItem() {
  obj = Instantiate(
    opengame,
    new Vector3(
      opengame.transform.position.x+(0.5f*(i-1)+k*1.5f),
      opengame.transform.position.y-j,
      opengame.transform.position.z
    ), 
    Quaternion.identity
  ) as GameObject;
  UIButtonMessage btnMessage = obj.GetComponent("UIButtonMessage") as UIButtonMessage; 
  btnMessage.target = btnMessageTarget;
}

void OnOpenStageClick() {
  Debug.Log("Clicked " + UICamera.hoveredObject);
  Cs_loadgameclick script_load = UICamera.hoveredObject.GetComponent("Cs_loadgameclick") as Cs_loadgameclick; 
  Debug.Log("targetstage = " + script_load.targetstage);
}

asfd

- end -


728x90
728x90

출처 : 유니티에서 데이터 저장하기 #1

DataUser.cs

using UnityEngine;
using System.Collections;

public class DataUser {

	private string m_user_name;
	public string UserName {
		get {
			return m_user_name;
		}
		set {
			m_user_name = value;
		}
	}
	
	private string m_nick_name;
	public string NickName {
		get {
			return m_nick_name;
		}
		set {
			m_nick_name = value;
		}
	}
	
}

Main.cs

using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour {
	
	// Use this for initialization
	void Start () {
		/*
		DataUser user = GetUserData();
		user.UserName = "test";
		user.NickName = "blue";
		
		SaveUserData(user);
		*/
		
		DataUser user = GetUserData();
		Debug.Log( user.UserName );
		
	}
	
	void SaveUserData(DataUser user) {
		PlayerPrefs.SetString("UserName", user.UserName);
		PlayerPrefs.SetString("NickName", user.NickName);		
	}
	
	DataUser GetUserData() {
		DataUser user = new DataUser();
		user.UserName = PlayerPrefs.GetString("UserName");
		user.NickName = PlayerPrefs.GetString("NickName");
		return user;
	}
}

 

728x90

+ Recent posts