레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Android인 게시물을 표시합니다. 모든 게시물 표시

2013년 3월 12일 화요일

[Android] Android NDK를 이용한 예제 작성

* 지난 번에 Android NDK의 빌드 환경을 설정했다. 그 때에는 빌드 환경을 설정하고, Android에서 제공하는 예제를 실행해 보는 것으로 끝났었다. 이제 Android NDK로 빌드하는 예제를 만들어 보고자 한다.

* Android NDK 예제를 찾다 보니, 잘 설명된 blog가 있어서, 그대로 따라하면서 Project를 생성해 본다.

1. 개발 환경

 * 개발 환경에 대해서는 지난 번 글([Android] Android NDK 빌드 환경 꾸미기)에서 했던 내용이다.

1-1. cygwin

Windows의 경우, cygwin 설치가 되어 있어야 한다. command창에서 javah나 ndk-build 명령을 실행한다.

2. Eclipse : Android Project 생성

 * Eclipse workspace에 새로운 프로젝트를 생성하자[File -> New -> Android Application Project].

 * Next를 클릭하여 다음으로 진행한다.
 * 프로젝트 정보
   - Application Name : NDKTest
   - Package Name : com.shnoh.ndktest
   - Activity Name : NDKTestMainActivity

package com.shnoh.ndktest;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class NDKTestMainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ndktest_main);
    }
    
}

2-1. Layout 구성

 * 단말 화면에 보여질 Layout을 구성한다.
   - 파일 위치 : NDKTest\res\layout\activity_ndktest_main.xml
 * Button 하나와 TextView 하나를 추가한다.



2-2. Activity 구현

 * Button과 TextView의 View ID를 가져온다.
 * Button을 눌러 JNI 함수를 호출하는 부분을 추가한다.
 * Native 함수를 호출할 class 이름을 NativeCall 이라고 만든다.

package com.shnoh.ndktest;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class NDKTestMainActivity extends Activity {

 private Button mBtn;
 private TextView mTextView;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ndktest_main);
        
        mBtn = (Button) findViewById(R.id.callBtn);
        mTextView = (TextView) findViewById(R.id.textVal);
        
        mBtn.setOnClickListener(new OnClickListener() {
   
            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                NativeCall nativeCall = new NativeCall();
                int ret = nativeCall.add(10,20);
                String retStr = nativeCall.stringFromJNI();
                mTextView.setText(retStr);
            }
        });
    }   
}

2-3. JNI를 위한 class 생성

 * 앞 단계에서 이름 지은 NativeCall class를 생성한다.
   - 파일 위치 : NDKTest\src\com.shnoh.ndktest\NativeCall.java
 * 소스 내용
   - native 키워드 : javah 가 헤더(header) 파일을 생성할 때 참조하는 키워드임.
   - 구현부는 없음.
   - static 부분 : class 객체가 생성될 때(new 될 때) 호출됨.
   - System.loadLibrary("my_lib") : ndk-build로 생성된 lib 이름("my_lib").
   - Library 파일 명 : libmy_lib.so

package com.shnoh.ndktest;

public class NativeCall {

    static {
     System.loadLibrary("my_lib");
    }
    
    public native int add(int i, int j);

    public native String stringFromJNI();
 
}

2-4. javah 실행 : Native 소스를 위한 header 파일 생성

 * Eclipse의 NDKTest 프로젝트를 빌드를 하여, NativeCall.class 파일을 만든다.
 * Native 소스를 위한 header 파일 생성하기 위해 command창을 열어 javah 를 실행한다.
 * javah 의 실행 위치 및 command
   - 프로젝트 root 폴더에서 실행 :
     i. 명령어 : NDKTest>javah -classpath bin/classes com.shnoh.ndktest.NativeCall


     i. 결과 화면

   - 프로젝트 bin 폴더에서 실행 :
     i. NDKTest\bin>javah -classpath classes com.shnoh.ndktest.NativeCall


     i. 결과 화면

   - 한번만 해도 될 것을 테스트성으로 각기 다른 폴더에서 javah를 실행함.

 * 이제 프로젝트 루트 폴더에 /jni 폴더를 만들어, 위에서 만든 헤더 파일을 이동한다.


   - header 파일 내용

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_shnoh_ndktest_NativeCall */

#ifndef _Included_com_shnoh_ndktest_NativeCall
#define _Included_com_shnoh_ndktest_NativeCall
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_shnoh_ndktest_NativeCall
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_shnoh_ndktest_NativeCall_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_shnoh_ndktest_NativeCall
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_shnoh_ndktest_NativeCall_stringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

2-5. Native 소스 구현

 * jni 폴더에 my_lib.c 파일을 만들어, 위에서 선언한 함수들을 구현한다. 2개의 함수이다.
   - String을 return 해주는 함수
   - 2개의 정수를 입력 받아 합을 return 해주는 함수
 * my_lib.c 파일 내용

#include "com_shnoh_ndktest_NativeCall.h"

JNIEXPORT jstring JNICALL Java_com_shnoh_ndktest_NativeCall_stringFromJNI(JNIEnv *env, jobject obj)
{
    return (*env)->NewStringUTF(env, "Hello JNI!!!!!");
}

JNIEXPORT jint JNICALL Java_com_shnoh_ndktest_NativeCall_add(JNIEnv *env, jobject obj, jint i, jint j)
{
    return i + j;
}

2-6. Android.mk 파일 작성

 * 지난 번에 설치한 Android NDK의 Sample 폴더에 보면, hello-jni 폴더가 있다. 이 프로젝트의 Android.mk 파일을 가져와서 수정을 하자.
 * 수정된 부분은 LOCAL_MODULE 과 LOCAL_SRC_FILES 항목이다.
   - Android.mk 파일 내용

# Copyright (C) 2009 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.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := my_lib
LOCAL_SRC_FILES := my_lib.c

include $(BUILD_SHARED_LIBRARY)

3. ndk-build : command 창 

 * 프로젝트의 root 폴더 또는 /jni 폴더에서 "ndk-build" 라는 명령을 실행한다.
   - 프로젝트 root 폴더에서 실행
     i. 명령어 : NDKTest>ndk-build


     i. 결과 화면

 * 빌드가 수행되고 나면, libs/armeabi 폴더에 "libmy_lib.so" 파일이 생성된다.

4. 실행 및 설치 : apk 빌드 및 설치

 * Eclipse 프로젝트에서 빌드 후 apk를 설치한다.

5. 실행 화면

 * apk를 실행하면 아래와 같은 화면을 볼 수 있다.



 * "Call JNI" Button을 누르면, "Hello JNI!!!!!" 라는 String이 보인다.


 * 2개의 정수를 더한 값을 return하는 것은 연결되어 있지 않다.

6. 샘플 소스

 * 위에서 사용한 샘플 소스이다.
   - https://snoh@bitbucket.org/snoh/androidndk
   - Mercurial command : hg clone https://snoh@bitbucket.org/snoh/androidndk

7. 출처 및 참고 사이트


7-1. 안드로이드 공식 사이트

 * Getting Started with the NDK
   -  http://developer.android.com/tools/sdk/ndk/index.html#GetStarted

7-2. 개인 블로그

 * Memories collected..."추억수집" : NDK를 이용한 안드로이드 App 기본예제
   - http://viiiin.tistory.com/12
 * 박상현님의 블로그 : javah 사용법
   - http://blog.naver.com/PostList.nhn?blogId=multisunchon&categoryNo=26&from=postList




2013년 1월 24일 목요일

[Android] InputMethodManager를 이용한 텍스트 입력..

Android InputMethodManager 이용한 Text 입력

* Android에서 텍스트를 입력하는 방법에 대해 테스트를 해본다.
* 2가지 방법을 이용하여 sample project를 만들어 본다.
  1. EditText (TextWatcher) 이용
  2. InputConnection (KeyListener) 이용

< EditText 이용>

1. 화면에 TextView와 EditText를 하나 구성한다.

        /*
         * Step 1
         *  - Backgroud로 보여줄 이미지를 설정한다.
         *  - Text가 보여질 TextView를 추가한다.
         *  - TextView에 입력하기 위해 EditText를 하나 생성한다.
         *  
         *  - EditText를 터치하면, IME가 자동으로 올라온다(show).
         *  - IME를 내릴경우(hide) 취소 키를 눌러야 하고, EditText에는 커서가 그대로 남아있다.
         */
        // main layout
        ViewGroup.LayoutParams layoutParamsMain = 
          new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
                     ViewGroup.LayoutParams.MATCH_PARENT); 
        
        // sub layout
        ViewGroup.LayoutParams layoutParamsSub =
          new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                     ViewGroup.LayoutParams.WRAP_CONTENT);
        
        FrameLayout mainLayout = new FrameLayout(this);
        
        LinearLayout subLayout = new LinearLayout(this);
        subLayout.setOrientation(LinearLayout.VERTICAL);
        
        // Background 용 ImageView
        mImageView = new ImageView(this);
        mImageView.setBackgroundResource(R.drawable.iu3);

        // Text 입력을 위한 EditText와 Text를 보여줄 TextView
        mTextView = new TextView(this);
        mEditText = new EditText(this);
     
        // Initial Text 설정.
        mTextView.setText(mInitText1);  
        mEditText.setText(mInitText2);
        
        // Add Views ( ImageView, TextView )
        mainLayout.addView(mImageView, layoutParamsMain);
        subLayout.addView(mTextView, layoutParamsSub);
        subLayout.addView(mEditText, layoutParamsSub);
        
        mainLayout.addView(subLayout, layoutParamsSub);
2. EditText에 TextWatcher의 Listener를 붙인다.
  - 3개의 method를 이용한다(onTextChanged, beforeTextChanged, afterTextChanged).
        /*
         * Step 2
         *  - EditText 에 Text가 입력될 때마다 변경되는 Text를 TextView에 보여주기 위해 
         *  - TextWathcer를 implements 한다.
         *  - 또는 아래 주석처럼 사용할 수도 있겠다.
         *  
         *  - Step 2까지 하게되면, EditText 에 Text를 입력하면 TextView에도 그 Text가 반영된다.
         *  - 이 상태 역시 EditText를 터치하면, IME가 자동으로 올라오게 된다(show).
         *  - IME를 내릴경우(hide) 취소 키를 눌러야 하고, EditText에는 커서가 그대로 남아있다.
         */
        mEditText.addTextChangedListener(this);
        
//        mTextWatcher = new TextWatcher() {
//   
//   @Override
//   public void onTextChanged(CharSequence s, int start, int before, int count) {
//    // TODO Auto-generated method stub
//    Log.i(TAG , "onTextChanged() is called");
//    Log.e(TAG, "String = " + s);
//    
//    mTextView.setText(s);
//   }
//   
//   @Override
//   public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//    // TODO Auto-generated method stub
//    Log.i(TAG , "beforeTextChanged() is called");
//    Log.e(TAG, "String = " + s);
//   }
//   
//   @Override
//   public void afterTextChanged(Editable s) {
//    // TODO Auto-generated method stub
//    Log.i(TAG , "afterTextChanged() is called");
//    Log.e(TAG, "String = " + s);
//   }
//  };
//  mEditText.addTextChangedListener(mTextWatcher);

    /*
     * Step 2
     *  - EditText 에 Text가 입력될 때마다 변경되는 Text를 보기위해 TextWathcer를 implements 한다.
     *  - 아래 method들을 이용하면 되겠다.
     */
    // TextWatcher methods
 @Override
 public void afterTextChanged(Editable s) {
  // TODO Auto-generated method stub
 }

 @Override
 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  // TODO Auto-generated method stub
 }

 @Override
 public void onTextChanged(CharSequence s, int start, int before, int count) {
  // TODO Auto-generated method stub
  Log.i(TAG , "onTextChanged() is called");
  Log.e(TAG, "String = " + s);
  
  // TextView에 입력된 Text를 반영하자.
  mTextView.setText(s);
 }

3. TouchListener를 이용하여 IME를 보여주자.
 * 이 단계는 생략하는 것이 EditText를 사용하는데 더 좋은 것 같다.
        /*
         * Step 3
         *  - 이제 TouchListener를 이용하여 IME의 show를 control 해보자.
         *  - 사길 Step 2까지만 하는 것이 EditText에 텍스트를 입력하는 것에는 더 좋을 것이다.
         *  - Step 2에서는 cursor의 위치 이동, 텍스트 잘라내기, 붙여넣기 등의 기능들이 자동으로 제공된다.
         *  - 오히려, EditText를 터치하는 동작에서 IME를 보여주게 되면 기존의 기능들을 별도로 해줘야 한다.
         *  - cursor 위치 이동, 복사, 잘라내기, 붙여넣기 등...
         *  - IME를 보이게 하는 것을 테스트하기 위함이라 아래 코드를 추가한다.
         */
        mEditText.setOnTouchListener(this);  


 /*
  * Step 3
  *  - Touch 동작에 맞게 IME를 보여주는 것을 해보자(show).
  *  - InputMethodManager의 INPUT_METHOD_SERVICE를 이용한다.
  */
 @Override
 public boolean onTouch(View view, MotionEvent event) {
  // TODO Auto-generated method stub
  Log.i(TAG , "onTouch() is called");
  
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
   break;
   
  case MotionEvent.ACTION_MOVE:
   break;
   
  case MotionEvent.ACTION_UP:
   // Touch Up 동작에서 IME를 보여주자.
   showIME(view);
   
   break;
  }
  
  return true;
 }
 
 /**
  * IME를 보이게 해준다.
  * @param view : target view
  */
 public void showIME(View view) {
  Log.i(TAG , "showIME() is called");
  CharSequence initText = null;
  
  initText = mTextView.getText();
  mEditText.setText(initText); // TextView의 초기 Text를 EditText로 보이게 하자.
  
  InputMethodManager mInputMethod = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
  Log.e(TAG , "mInputMethod  = " + mInputMethod);
  
  mInputMethod.showSoftInput(view, InputMethodManager.SHOW_FORCED);
 }


< InputConnection 이용 >

1. 화면에 TextView를 하나 구성한다.
  - 위의 1단계의 코드와 별반 다를 것은 없고, EditText만 삭제하면 된다.

2. TextView에 TouchListener를 달아서 테스트를 해보자.
  - InputConnection을 이용한다.

        /*
         * Step 2
         *  - 이제 TouchListener를 이용하여 IME의 show를 control 해보자.
         *  - 텍스트를 보여 줄 TextView를 터치하면, IME 가 나오도록 한다.
         */  
        mTextView.setOnTouchListener(this);
        
        /*
         * Step 3
         *  - IME로부터 입력되는 Text를 보여주기 위한 것.
         *  - TextView에 연결된 IME로부터 Key 입력을 받기 위해, KeyListener를 연결한다.
         */
        mTextView.setFocusable(true);
        mTextView.setFocusableInTouchMode(true); // 이게 있어야 TextView안의 Text가 변경된다.
        mTextView.requestFocus();
        mTextView.focusSearch(View.FOCUS_DOWN);
        
        mKeyListener = TextKeyListener.getInstance();
        mTextView.setKeyListener(mKeyListener);

  - onTouch()에서 ACTION_UP 이벤트에 동작하게 하자.
 /*
  * Step 2
  *  - TextView를 터치하였을 때, IME를 올리도록 하자(show).
  *  - IME와 연동을 위해 InputConnection을 생성하자.
  */
 @Override
 public boolean onTouch(View view, MotionEvent event) {
  // TODO Auto-generated method stub
  Log.i(TAG , "onTouch() is called");
  
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
   break;
   
  case MotionEvent.ACTION_MOVE:
   break;
   
  case MotionEvent.ACTION_UP:
   // Touch Up 동작에서 IME를 보여주자.
   showIME(view);
   
   break;
  }
  
  return true;
 }
 
 /**
  * IME를 보이게 해준다.
  * @param view : target view
  */
 public void showIME(View view) {
  Log.i(TAG , "showIME() is called");
  
  // Text 정보를 전달하기 위해 InputConnection을 생성한다.
  mEditorInfo = new EditorInfo();
  onCreateInputConnection(mEditorInfo);
  
  InputMethodManager mInputMethod = (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE);
  Log.e(TAG , "mInputMethod  = " + mInputMethod);

  // IME를 올려보자.
  mInputMethod.showSoftInput(view, InputMethodManager.SHOW_FORCED);
  // 아래와 같이 하여도 동작한다.
//  mInputMethod.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
  
 }

  - InputConnection과 연결을 한다.

 /*
  * 텍스트 입력을 위해 InputConnection과의 연결을 한다.
  * 
  * @param outAttrs : Editor의 속성 값들을 전달한다.
  * @return InputConnection의 Instance.
  */
 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
  Log.i(TAG , "onCreateInputConnection() is called");
  Log.i(TAG , "onCreateInputConnection() mInputConnection = " + mInputConnection);
  
  if(mInputConnection == null ) {
   mInputConnection = new EditableInputConnection(mTextView, true);
  }
  
  return mInputConnection;
  
 }

* 위에서 테스트한 코드들은 텍스트를 입력하여, TextView에 보이게만 한 것이다.
* EditText를 이용할 때, 복사, 붙여넣기, 잘라내기, Cursor 이동에 대해서는 테스트가 더 필요해 보인다.
* 테스트할 것들이 많구만...

< Source >

https://bitbucket.org/snoh/androidinputmethodtest

Mercurial command : hg clone https://bitbucket.org/snoh/androidinputmethodtest


< 끝 >

2013년 1월 23일 수요일

[Android] Hierarchy Viewer를 사용 하려면..

* Android app.의 layout 구조가 어떻게 되는지 궁금하여, HierarchyViewer를 이용해 보려고 한다.

* hierarchyviewer.bat 를 실행시키는 순간... 아래와 같은 에러 등장...

[hierarchyviewer]Unable to get view server protocol version from device 
[hierarchyviewer]Unable to debug device

음... 뭘까.. 전엔 잘 되었던 것 같은데..

이래 저래 찾다가 아래 사이트를 발견..

Reference (Thanks to Julia)..

Enable HierarchyViewer on production builds


http://jmlinnik.blogspot.ro/2012/08/enable-hierarchyviewer-on-production.html

음.. 그렇구나...

단말이 production build가 된 경우에는 동작을 안한다.

userdebug build나 engineering build가 된 경우에만 동작을 한다.
(예전에 잘되었던 것은 userdebug로 빌드를 한 것 같다).

위의 link에서처럼 RomainGuy의 ViewServer 소스를 받아서 해본다.

소스는 git hub 에 올려져 있음.
https://github.com/romainguy/ViewServer

App. 소스에서 3군데에 추가해 주면 끝나는군...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         // Set content view, etc.
         ViewServer.get(this).addWindow(this);
     }
 
     public void onDestroy() {
         super.onDestroy();
         ViewServer.get(this).removeWindow(this);
    }
 
     public void onResume() {
         super.onResume();
         ViewServer.get(this).setFocusedWindow(this);
    }
}


아.. 그리고,
INTERNET permission을 추가해 줘야 한다..

1
2
<uses-permission android:name="android.permission.INTERNET">
</uses-permission>



블로그에 올려준 Julia에게 감사하고, 소스를 올려준 RomainGuy에게도 고마움을 느낀다..

소소한 것이라 생각했다가 막상 찾으려니 도움이 되어 좋다...