DeviceAPI 개발

DeviceAPI는 넥사크로플랫폼 애플리케이션에서 디바이스의 특화된 기능을 사용할 수 있는 방법을 제공합니다. 개발자는 DeviceAPI를 사용해 모바일 플랫폼(iOS/Android)의 확장된 기능을 사용할 수 있습니다.

DeviceAPI는 크게 두 부분으로 나누어집니다. 하나는 넥사크로 프로젝트에 적용하기 위한 자바스크립트 인터페이스이며, 다른 부분은 모바일 플랫폼 환경에서 개발된 네이티브 모듈입니다.

이번 장에서는 입력받은 문자열을 화면에 표시하는 간단한 예제를 통해 DeviceAPI를 개발하고 적용하는 방법을 소개합니다.

네이티브 모듈

iOS 네이티브 모듈 개발

Xcode를 사용해 iOS 네이티브 모듈을 개발하는 방법을 설명합니다. 제공하는 DeviceAPI 클래스를 상속해 기능을 구현합니다.

예제 개발 시 테스트한 개발 환경은 아래와 같습니다.

개발 OS : OSX 10.9.3
Xcode : Version 5.1.1
테스트 장비 : iPad Mini (iOS 7.1)

프로젝트 생성

  1. Xcode를 실행하고 아래 메뉴에서 프로젝트를 생성합니다.

File > New > Project

프로젝트생성

  1. 기본 템플릿으로 'Cocoa Touch static Library'를 선택합니다.

템플릿선택

  1. Product Name(HelloPlugin) 및 필요한 항목을 채운 후 프로젝트 생성을 완료합니다.

프로젝트생성완료

프로젝트 환경 설정

프로젝트가 생성되면 Targets 항목에서 'HelloPlugin' 프로젝트를 선택하고 Build Phases 항목에 3개의 프레임워크를 추가합니다. 추가하는 프레임워크는 아래와 같습니다.

nexacro14.framework
UIKit.framework
Foundation.framework

프레임워크추가

소스 파일 수정

자동 생성된 HelloPlugin.h 헤더 파일과 HelloPlugin.m 소스 파일을 아래와 같이 수정합니다.

HelloPlugin.h

// HelloPlugin.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <nexacro14/DeviceAPI.h>

@interface HelloPlugin : DeviceAPI {
	NSInteger nID;
	NSString *helloMsg;
}

@property (nonatomic) NSInteger nID;
@property (nonatomic, retain) NSString * helloMsg;
- (void)hello:(NSString*)lid withDict:(NSMutableDictionary*)options;
@end

HelloPlugin.m

64bit 빌드시 위 그림에서 19번째 라인의 형식 지정자 경고가 발생 할 수 있습니다. 64bit 빌드시 아래 소스 내용처럼 self.nID를 (int)self.nID로 변경하시길 바랍니다.

// HelloPlugin.m

#import "HelloPlugin.h"

@implementation HelloPlugin
@synthesize nID, helloMsg;

//hello Method
- (void)hello:(NSString*)lid withDict:(NSMutableDictionary*)options
{
	self.helloMsg = [options objectForKey:@"helloMsg"];

	self.nID = [lid integerValue];
 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:self.helloMsg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
	[alert show];

	//Event
	NSString *callbackResult = [NSString stringWithFormat:@"runCallback(\"%d\",\"_onhello\",{'eventid':\"onhello\",'msg':\"%@\"});", self.nID, self.helloMsg];

//64bit build
/* 
 	NSString *callbackResult = [NSString stringWithFormat:@"runCallback(\"%ld\",\"_onhello\",{'eventid':\"onhello\",'msg':\"%@\"});", (int)self.nID, self.helloMsg];
*/
	[self writeJavascript:callbackResult];
}

@end

빌드

Xcode에서 빌드 후 Products 폴더에 libHelloPlugin.a 파일이 생성됩니다. Finder에서도 .a파일이 생성된 것을 확인할 수 있습니다.

빌드완료

애플리케이션 프로젝트에 libHelloPlugin.a 파일 추가

애플리케이션 프로젝트에서 기존 Framework를 추가하면서 libHelloPlugin.a 파일을 같이 추가합니다. 아래 그림은 NX14라는 이름을 가진 애플리케이션 프로젝트에 생성한 libHelloPlugin.a 파일을 추가한 모습입니다.

NX14프레임워크추가

iOS 프로젝트 개발에 대한 설명은 관리자 가이드 '앱 개발 및 실행(iOS)' 내용을 참고해주세요.

http://docs.tobesoft.com/admin_guide_nexacro_14_ko

시뮬레이터로 테스트하는 경우에는 별도의 인증 과정 없이 테스트를 진행할 수 있습니다. 하지만 실제 단말기에 연결하려면 추가적인 설정이 필요합니다.

'Code Signing' 항목에서 'Code Signing Identity' 항목을 아래와 같이 적절한 프로파일로 설정합니다.

[관리자 가이드 > 앱 개발 및 실행 (iOS) > Code Singing]

안드로이드 네이티브 모듈 개발

이클립스를 사용해 안드로이드 네이티브 모듈을 개발하는 방법을 설명합니다. 제공하는 NexacroPlugin 클래스를 상속해 기능을 구현합니다.

예제 개발 시 테스트한 개발 환경은 아래와 같습니다.

JDK(Java SE Development Kit)
이클립스
안드로이드 SDK
ADT 플러그인
테스트 장비 : 옵티머스 G (Android 4.4.2)

프로젝트 생성

  1. 이클립스를 실행하고 아래 메뉴에서 프로젝트를 생성합니다.

File > New > Android Application Project

New_Project

  1. Project Name을 'HelloPlugin'으로 지정하고 새로운 프로젝트를 생성합니다.

New_Android_Application

안드로이드 4.4(Kitkat) 이상 SDK 사용 시 Minumum Required SDK를 3.2(Honeycomb) 이하로 설정하면 하위 호환성 유지를 위해 appcompat_v7 프로젝트를 자동 생성합니다.

http://developer.android.com/tools/support-library/features.html

하지만 넥사크로플랫폼 애플리케이션 생성 시에는 영향을 미치지 않기 때문에 해당 프로젝트를 생성하지 않는 것이 좋습니다. 이런 현상을 피하려면 Minumum Required SDK를 4.0(IceCreamSandwich)로 설정하고 프로젝트를 만든 후 AndroidManifest.xml 설정 시 원하는 하위 버전을 다시 지정할 수 있습니다.

  1. HelloPlugin.java 파일을 생성합니다. 아래 그림과 같은 구조가 만들어집니다.

New_HelloPlugin.java

소스 파일 수정

생성한 HelloPlugin.java 파일을 아래와 같이 수정합니다. HelloPlugin 모듈을 생성하고 params에 메시지를 담아 Android의 Toast 형식으로 메시지를 보여줍니다.

Toasts

http://developer.android.com/guide/topics/ui/notifiers/toasts.html

package com.example.helloplugin;

import org.json.JSONException;
import org.json.JSONObject;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.widget.Toast;

import com.nexacro.Native;
import com.nexacro.plugin.NexacroPlugin;
import com.nexacro.plugin.NexacroPluginManager;
import com.nexacro.util.Log;

public class HelloPlugin extends NexacroPlugin {

	private static String LOG_TAG = "HelloPlugin";
	private static HelloPlugin helloplugin = null;

	public HelloPlugin(String objectId) {
		super(objectId);
		// TODO Auto-generated constructor stub
	}

	@Override
	public void init(JSONObject paramObject) {
		// TODO Auto-generated method stub

	}

	@Override
	public void release(JSONObject paramObject) {
		// TODO Auto-generated method stub

	}

	@Override
	//hello 메소드가 존재하면 'helloMsg' 문자열값을 저장합니다.
	public void execute(String method, JSONObject paramObject) {
		// TODO Auto-generated method stub
		Log.w(LOG_TAG, "execute paramObject:" + paramObject.toString());

		if (method.equals("hello")) {
			String msg = "";

			try {
				JSONObject params = paramObject.getJSONObject("params");

				msg = params.getString("helloMsg"); // repeatcount
			}

			catch (JSONException e) {
				e.printStackTrace();
			}

			show(msg);
		}

	}

	//excute() 메소드에 담긴 변수를 Toast 형식으로 보여줍니다.
	public void show(String message) {
		final String msg = message;

		new Thread(new Runnable() {
			@Override
			public void run() {
				new Handler(Looper.getMainLooper()).post(new Runnable() {
					@Override
					public void run() {
						Toast.makeText(
								NexacroPluginManager.getInstance()
										.getActivity(), msg, Toast.LENGTH_LONG)
								.show();
					}
				});
			}
		}).start();

		Native.SendDeviceEvent(getObjectId(), "_onhello", "{}");
 }

}

jar 파일 만들기

Plugin으로 사용할 수 있게 jar 파일을 생성합니다.

  1. HelloPlugin 프로젝트의 마우스 오른쪽 버튼을 클릭한 후 나타나는 팝업 메뉴에서 [Export] 항목을 선택합니다.

Export1

  1. 'Export' 창에서 [Java > JAR file] 항목을 선택합니다.

Export2

  1. 'JAR Export' 창에서 HelloPlugin 프로젝트의 'src' 폴더만 선택하고 나머지 항목은 모두 체크를 해제합니다.

Export3

애플리케이션 프로젝트에 Jar 파일 추가

  1. 애플리케이션 프로젝트의 xml 폴더에 Plugin.xml 파일을 생성합니다. 앞에서 만든 안드로이드 네이티브 모듈을 사용하기 위해 name과 class를 지정합니다.

Nexacro_plugins.xml

<?xml version="1.0" encoding="utf-8"?>
<plugin-config>
	<plugin name="HelloPlugin" class="com.example.helloplugin.HelloPlugin"/>
</plugin-config>
  1. libhelloplugin.jar 파일을 마우스를 끌어 'libs' 폴더 밑에 놓습니다.

Insert_libhelloplugin.jar

안드로이드 프로젝트 개발에 대한 설명은 관리자 가이드 '앱 개발 및 실행(안드로이드)' 내용을 참고해주세요.

http://docs.tobesoft.com/admin_guide_nexacro_14_ko

자바스크립트 인터페이스 개발

자바스크립트 인터페이스로 사용할 Hello.js파일은 넥사크로 스튜디오에서 nexacro.Hello 사용자 컴포넌트를 호출할 수 있게 작성합니다. _pHello.hello 함수가 이 예제에서 사용한 실제 함수 구현 코드입니다.

자바스크립트 인터페이스는 아래와 같이 세 부분으로 이루어집니다.

//Hello.js
if (!nexacro.Hello) {
    nexacro.Hello = function(name, obj)
    {
        this._id = nexacro.Device.makeID();
        nexacro.Device._userCreatedObj[this._id] = this;
        this.name = name || "";	
   		this.enableevent = true;
		
		this._event_list = {
            "onhello": 1
        };
		
        var params = '""';
        var jsonstr = '{"id":' + this._id + ', "div":"HelloPlugin","method":"constructor", "params":' + params + '}';
        nexacro.Device.exec(jsonstr);        
    };

	var _pHello = nexacro.Hello.prototype = nexacro._createPrototype(nexacro.EventSinkObject, nexacro.Hello);	
	_pHello._type_name = "Hello";

	_pHello.destroy = function ()
	{
	    var params = '""';
	    var jsonstr;

	    delete nexacro.Device._userCreatedObj[this._id];

	    jsonstr = '{"id":' + this._id + ', "div":"HelloPlugin", "method":"destroy", "params":' + params + '}';
	    nexacro.Device.exec(jsonstr);

	    return true;
	};
	
	_pHello.on_created = function () { };

    //===============================================================
    // nexacro.Hello : Methods
    //===============================================================
	_pHello.hello = function(strHelloMsg)
	{
		var msg = strHelloMsg;
        var params = '{"helloMsg":"' + msg + '"}';
      
	    var jsonstr = '{"id":' + this._id + ', "div":"HelloPlugin", "method":"hello", "params":' + params + '}';
	    nexacro.Device.exec(jsonstr);
	};
	
    //===============================================================
    // nexacro.Hello : Event
    //===============================================================
	_pHello._onhello = function(objData)
	{
		var e = new nexacro.HelloEventInfo(objData.eventid, objData.msg);
		this._fire_onhello(this, e);
	};
	
	_pHello._fire_onhello = function(objHello, eHelloEventInfo)
	{
		if (this.onhello && this.onhello._has_handlers)
		{
			return this.onhello._fireEvent(this, eHelloEventInfo);
		}
		return true;
	};
	
	//===============================================================
	//nexacro.HelloEventInfo
	//===============================================================
    nexacro.HelloEventInfo = function(strEventId, strMsg) {
        this.eventid = strEventId;										
        this.msg = strMsg;												
    };
    var _pHelloEvnetInfo = nexacro._createPrototype(nexacro.Event, nexacro.HelloEventInfo);
    nexacro.HelloEventInfo.prototype = _pHelloEvnetInfo;
    _pHelloEvnetInfo._type_name = "HelloEventInfo";

    delete _pHelloEvnetInfo;

	delete _pHello;
}

실제 DeviceAPI 구현 객체(Java, Objective-C 구현)의 생성과 소멸은 스크립트에서 약속된 메소드명인 "constructor"와 "destroy"로 이루어집니다.

하지만 현재 시점에서는 안드로이드/iOS DeviceAPI의 인터페이스 차이로 "constructor", "destroy" 메소드 호출 시 파라미터를 전달할 수 없습니다. 필요한 값을 설정하기 위해서는 별도의 사용자 메소드를 정의해 처리해야 합니다.

넥사크로플랫폼 프로젝트 적용

자바스크립트 인터페이스를 적용하기 위해 사용자 컴포넌트를 구성하고 이를 넥사크로플랫폼 프로젝트에 적용합니다. 사용자 컴포넌트 작성과 넥사크로플랫폼 프로젝트에서 사용하는 방법을 간략하게 설명합니다.

사용자 컴포넌트

사용자 컴포넌트 구성

예제에서 사용한 파일은 특정 기능 구현을 위한 것이 아니라 이해를 돕기 위해 작성한 파일입니다. 아래와 같이 4개 파일을 사용합니다.

등록할 모듈과 스크립트 파일은 넥사크로 스튜디오에서 Base Lib Path로 지정된 경로 내 'component' 폴더 하위 경로에 있어야 합니다.

Base Lib Path 기본 설정은 넥사크로플랫폼 설치 경로 아래 'nexacro14lib' 폴더입니다. 사용 환경에 따라 넥사크로 스튜디오 옵션에서 다른 경로로 수정할 수 있습니다.

Options > Environment > Base Lib Path

사용자 컴포넌트에 대한 설명은 사용자 컴포넌트 항목을 참고하세요.

모듈등록

  1. 아래와 같은 형태로 ExtDeviceAPI.json 파일을 작성합니다. 'scripts', 'objInfo' 항목에 지정한 폴더 경로와 파일은 [Base Lib Path > component]에 지정된 폴더 아래 있어야 합니다.

{
	"name": "ComComp",
	"version": "14.0.0.0",
	"description": "nexacro platform Common Component Library",
	"license": "",
	"scripts": [
		"ExtDeviceAPI/Hello.js"
	],
	"objInfo": [
		"ExtDeviceAPI/Hello.info",
	"ExtDeviceAPI/HelloEnum.info"
	]
}
//@ sourceURL=ExtDeviceAPI.json

  1. ExtDeviceAPI.json 파일에서 'scripts', 'objInfo' 항목으로 지정한 'ExtDeviceAPI' 폴더를 생성하고 해당 폴더 아래 3개 파일을 생성합니다.

  1. 넥사크로 스튜디오에서 작성한 ExtDeviceAPI.json 모듈을 등록합니다. 'Project Explorer' 창에서 TypeDefinition 항목을 오른쪽 마우스로 클릭한 후 팝업 메뉴에서 [Edit] 항목을 선택합니다. 'Edit TypeDefinition' 창에서 [Add] 버튼을 클릭해 ExtDeviceAPI.json 파일을 모듈로 등록합니다.

오브젝트 정보

nexacro.Hello 사용자 컴포넌트는 모듈 설정 시 'Hello.info', 'HelloEnum.info' 2개의 ObjInfo 파일을 설정했습니다. 'Hello.info' 파일은 컴포넌트에 필요한 정보를 담고 있으며 'HelloEnum.info' 파일은 Enum 속성에 대한 정보를 담고 있습니다.

<?xml version='1.0' encoding='utf-8'?>
<MetaInfo version="1.0">
    <Object id="nexacro.Hello"> 
        <!-- define extend component based nexacro.Object -->
        <ObjectInfo 
            typename="nexacro.Hello"
            csstypename="Hello,HelloABC[userprop='abc']"
            csscontrolname="HelloControl,HelloControlABC[userprop='abc']"
            group="Object"
            csspseudo="true"
            container="false"
            composite="true"
            tabstop="true"
            cssstyle="true"
            contents="true"
            formats="false"
            contentseditor="auto"
            defaultwidth="300"
            defaultheight="200"
            requirement="Runtime,HTML5"
            description=""/> 
        
        <PseudoInfo> 
            <Pseudo 
                name="pushed" 
                control="true" 
                deprecated="false" 
                unused="false" 
            /> 
            <Pseudo 
                name="focused" 
                control="true" 
                deprecated="false" 
                unused="true" 
            />
        </PseudoInfo> 

        <ControlInfo>
        </ControlInfo>


        <PropertyInfo> 
            <!-- define new property -->
            <Property
                name="userprop"
                group="Misc"
                type="Enum"
                defaultvalue="abc"
                readonly="false"
                initonly="false"
                hidden="false"
                control="false"
                style="false"
                expr="false"
                deprecated="false"
                unused="false"
                objectinfo=""
                enuminfo="enum_ext_test"
                unitinfo=""
                requirement="Runtime,HTML5"
                description="this is desc"
            />
            <!-- re-define super's property -->
            <Property
                name="tooltiptext"
                group="Misc"
                type="String"
                defaultvalue="1111111"
                readonly="false"
                initonly="false"
                hidden="false"
                control="false"
                style="false"
                expr="false"
                deprecated="false"
                unused="false"
                objectinfo=""
                enuminfo=""
                unitinfo=""
                requirement="Runtime,HTML5"
                description="this is desc"
            />
            <!-- define new style property -->
            <Property
                name="itemopacity"
                group="Style"
                type="Opacity"
                defaultvalue=""
                readonly="false"
                initonly="false"
                hidden="false"
                control="false"
                style="true"
                expr="false"
                deprecated="false"
                unused="false"
                objectinfo="nexacro.Style_opacity"
                enuminfo=""
                unitinfo=""
                requirement="Runtime,HTML5"
                description="this is desc"
            />
        </PropertyInfo> 

        
        <MethodInfo>
            <Method
                name="hello"
                group=""
                async="false"
                deprecated="false"
                unused="false"
                requirement="Runtime,HTML5"
                description="this is test method"
            >
                <Syntax
                    text = "hello(a [, b])"
                >
                <Return/>
                <Arguments>
                    <Argument 
                        name="a" 
                        type="String" 
                        in="true" 
                        out="false" 
                        option="false" 
                        variable="false" 
                        description="any string" 
                    /> 
                    <Argument 
                        name="b" 
                        type="String" 
                        in="true" 
                        out="false" 
                        option="true" 
                        variable="false" 
                        description="any string" 
                    /> 
                </Arguments>
                </Syntax>
            </Method>
        </MethodInfo>
        

        <EventHandlerInfo>
            <!-- define event -->
            <EventHandler
                name="onhello"
                group="Event"
                deprecated="false"
                unused="false"
                requirement="Runtime,HTML5"
                description="this is test event"
            >
                <Syntax
                    text="onhello(obj:Object, e:nexacro.EventInfo)"
                >
                    <Return/>
                    <Arguments>
                        <Argument 
                            name="obj" 
                            type="Object" 
                            in="true" 
                            out="false" 
                            option="false" 
                            variable="false" 
                            description="Event Source Object" 
                        /> 
                        <Argument 
                            name="e" 
                            type="nexacro.EventInfo" 
                            in="true" 
                            out="false" 
                            option="false" 
                            variable="false" 
                            description="Event Information Object" 
                        /> 
                    </Arguments>
                </Syntax>
            </EventHandler>
        </EventHandlerInfo>
    </Object> 
</MetaInfo>
<?xml version='1.0' encoding='utf-8'?>
<MetaInfo version="1.0">
    <!-- define enum information -->
    <EnumInfo
        id="enum_ext_test"
        composite="false"
        delimiter=""
        description="abc, 123"
    >
        <Enum
            name="abc"
            description="abc"
        />
        <Enum
            name="123"
            description="123"
        />    
    </EnumInfo>
</MetaInfo>

사용자 컴포넌트 등록

'Edit TypeDefinition' 창을 실행해 'Objects' 탭에서 [Add] 버튼을 클릭해 사용자 컴포넌트(nexacro.Hello)을 추가합니다.

컴포넌트 툴바에 Hello 오브젝트 아이콘이 생성되었는지 확인합니다.

테스트 폼 작성

HelloTest.xfdl 작성

  1. 넥사크로 스튜디오에서 테스트할 수 있는 간단한 폼(HelloTest.xfdl)을 새로 만듭니다.

  1. Form 위에 Hello 오브젝트를 올려놓으면 Invisible Objects 항목에 나타나며 Hello.info 파일에 설정한 내용이 Properties 창에 표시됩니다. Hello 오브젝트가 정상적으로 등록된 것을 확인했다면 화면 상단에 버튼 컴포넌트를 추가합니다.

  1. 버튼을 컴포넌트에 onclick 이벤트를 추가하고 코드를 작성합니다. 'this.Hello00.' 이라고 입력하면 Hello.js, Hello.info에서 정의한 hello 메소드가 코드 힌트로 나타나는 것을 확인할 수 있습니다.

this.Button00_onclick = function(obj:Button,  e:nexacro.ClickEventInfo)
{
	this.Hello00.hello("Hello World");
}

onhello 이벤트

Hello.info에서 정의한 onhello 이벤트를 Properties 창에서 확인할 수 있습니다.

hello 메소드에 "Hello world" 문자열을 넣고 hello 메소드가 실행되면서 onhello 이벤트를 호출합니다. 그리고 TextArea00 객체에 "onhello Event call" 문자열을 출력합니다.

//hello Method
this.Button00_onclick = function(obj:Button,  e:nexacro.ClickEventInfo)
{
	this.Hello00.hello("Hello World");	
}

//onhello Event
this.Hello00_onhello = function(obj:Hello,  e:nexacro.EventInfo)
{
	this.TextArea00.set_value("onhello Event call");
}

사용자가 HelloTest 버튼을 클릭 시 _onhello 이벤트를 호출하도록 iOS와 안드로이드 네이티브 모듈 소스를 수정합니다.

...
//hello Method
- (void)hello:(NSString*)lid withDict:(NSMutableDictionary*)options
{
	self.helloMsg = [options objectForKey:@"helloMsg"];

	self.nID = [lid integerValue];
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:self.helloMsg delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
	[alert show];

	//HelloTest 버튼 클릭 시 runcallback 메서드를 통해 _onhello 이벤트 호출
	NSString *callbackResult = [NSString stringWithFormat:@"runCallback(\"%d\",\"_onhello\",{'eventid':\"onhello\",'msg':\"%@\"});", self.nID, self.helloMsg];
	[self writeJavascript:callbackResult];
}

@end
...
	public void show(String message) {
		final String msg = message;

		new Thread(new Runnable() {
			@Override
			public void run() {
				new Handler(Looper.getMainLooper()).post(new Runnable() {
					@Override
					public void run() {
						Toast.makeText(
								NexacroPluginManager.getInstance()
										.getActivity(), msg, Toast.LENGTH_LONG)
								.show();
					}
				});
			}
		}).start();
		// HelloTest 버튼 클릭 시 _onhello 이벤트 호출
		Native.SendDeviceEvent(getObjectId(), "_onhello", "{}");
	}
}

실행 테스트

넥사크로 스튜디오에서 생성한 Form 위에 있는 [HelloTest] 버튼을 클릭하면 hello 메소드가 호출되면서 메시지가 Alert 형식으로 나타납니다. hello 함수가 호출되면서 onhello 이벤트가 발생하고 TextArea 영역에 "hello_onhello call"이라는 문자열이 표시되는 것을 확인할 수 있습니다.

Result_iOS

결과화면