작성일 : 12-02-16
 조회 : 1,201  
1. FTP 환경설정파일 수정

~# vi /etc/vsftpd/vsftpd.conf

# 문서하단에 추가

chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list


2. chroot_list 파일안에, root 추가 (이파일안에 등록된 USER는 상위디렉토리 접근제한을 받지 않는다.)

~# vi /etc/vsftpd/chroot_list

root


vsftp, ftp 사용자 폴더를 제외한 다른 폴더 접근제한

1)파일을 편집
/etc/vsftpd/vsftpd.conf

chroot_local_user=YES 추가

2)ftp demon restart
/etc/init.d/vsftpd restart


*다음의 파일에 등록된 사용자들은 FTP 접속이 불가능
/etc/vsftpd.ftpusers

/etc/vsftpd.user_list

-------------------------------------------------------------------------------

1. ftpusers와 vsftpd.chroot_list 차이 

/etc/ftpusers 과 chroot_list_file=/etc/vsftpd.chroot_list 파일은 용도가 다릅니다. 

* /etc/ftpusers : 이건 접속을 제한하는 것입니다
. 즉, 여기에 등록된 ID는 ftp 접속 자체를 할 수 없습니다. 
* /etc/vsftpd.chroot_list : 여기에 써진 ID는 접속할 수 있습니다. 
다만, 자신의 홈디렉토리를 벗어날 수(상위디렉토리로 갈 수) 없습니다.
 
이를테면 홈이 /home/truefeel/ 일 때 /home/truefeel/ 를 벗어난 /etc, /home, /usr 등을 갈 수 없다는 것입니다. 
이해되시나요? 
보안상 홈보다 상위 디렉토리로 이동하는 것을 제한하는 경우가 많습니다. 

2. chroot_local_user 와 chroot_list_enable 설정을 함께 사용할 때 

제가 쓴 글을 다시 인용하여 설명하겠습니다. 

인용:

3) 사용자가 홈디렉토리를 못 벗어나게 하고 싶는데? 

 /etc/vsftpd.conf에 다음을 추가하면, 모든 사용자는 자신의 홈디렉토리만 접근할 수 있다. 

 chroot_local_user=YES 

 또한 특정 사용자로만 제한을 하고 싶다면 다음과 같이 한다. /etc/vsftpd.chroot_list에는 제한할 
 사용자 ID를 한줄에 하나씩 나열하면 된다. 

 chroot_list_enable=YES 
 chroot_list_file=/etc/vsftpd.chroot_list 

 주의할 것은 chroot_local_user=YES와 chroot_list_enable=YES를 함께 사용할 경우에는 
 /etc/vsftpd.chroot_list에 포함된 사용자 ID만 제한없이 홈디렉토리를 벗어날 수 있다. 
 즉, 반대로 작용한다. 



위의 내용을 설정부분만 뽑으면 이렇습니다. 

1) 조건 1 
코드:

chroot_local_user=NO 
chroot_list_enable=YES 
chroot_list_file=/etc/vsftpd/chroot_list 



이런 경우는 /etc/vsftpd/chroot_list 파일에 설정한 ID는 홈디렉토리를 벗어날 수(즉, 상위로 갈 수) 없습니다. 

2) 조건 2 
코드:

chroot_local_user=YES 
chroot_list_enable=YES 
chroot_list_file=/etc/vsftpd/chroot_list 



이런 경우는 
/etc/vsftpd/chroot_list 파일에 설정한 ID만 홈디렉토리를 벗어날 수(즉, 상위로 갈 수) 있습니다. 
즉, 조건1과는 반대로 되는 것입니다. 


3. 특정 IP를 막을 때 

레드햇의 자체 vsftpd rpm은 tcp wrapper를 통해 IP제한을 할 수 있습니다. 
님의 말씀대로 이걸 이용하거나 iptables를 통해서 막으면 되겠네요.


sudo 권한이 있어야한다.


사용자추가


$ adduser NewId


하면 알아서 비밀번호 설정 가능


그룹생성


$ groupadd NewGroup


새사용자 그룹지정


$ vi /etc/group


NewGroup:x:1002:

이있는데 뒤에다 사용자 아이디를 써준다

NewGroup:x:1002:NewId

그리고 저장.

NewGroupDir이란 디렉토리의 소유권을 NewGroup으로 줌


$chown -R root:NewGroup NewGroupDir/


그리고 폴더 접근권한을 설정한다


$chmod -R 700 NewGroupDir/



접근권한 참고

r : Read  = 4
w : Write = 2
x : eXcute = 1

-rwxrwxrwx   ( 777 )
-r--r--r--  ( 444 )
-rwx--x--x ( 711 )

2~4필드 : 소유주 ( User ) 권한
5~7필드 : 그룹 ( Group )  권한
8~10필드 : 나머지 ( Others ) 권한


 


프로젝트 폴더에서 마우스오른쪽 클릭 -> Refactor -> Rename

오늘은 JNI를 이용한 C모듈을 안드로이드에 적용하는 방법을 설명하겠습니다.

이 부분은 좀 방대한 부분이고, 저 또한 짧은 시간에 글을 적어야 하므로 충분한 캡처를 동반하지 못함을 아쉽게 생각합니다.

 

우선 기본환경은

이클립스  +  NDK r5b + 우분투 11.04 이며, 모듈 컴파일은 Cygwin에서 사용해도 상관 없습니다.

(참고로 저는 윈도우즈 환경에서 우분투를 VM 으로 사용합니다.)

 

작업순서는 다음과 같이 하려 합니다.

 

가. 모듈 컴파일을 위한 NDK  다운로드 및 환경설정

나. *.java 에서 *.h, *.c의 JNI 파일 만드는 방법

다. 안드로이드 프로젝트 내에 JNI 쓰는 방법

 

가. 리눅스에서 NDK  설정

 

1. 우선 NDK 부터 사이트에서 받도록 합니다. 윈도우즈 환경에서 MS cl.exe 컴파일러를 이용해 *.dll 모듈을 만들어도 되지만

 저는 리눅스의 *.so 모듈을 만들기 위해서 리눅스용 버전을 다운로드 받습니다.

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

 

2. 다운로드를 받았으면 압축을 해제하고 자신의 홈 계정 디렉토리 밑으로 옮깁니다.

 예) home/maluchi/android-ndk-r5b


maluchi@ubuntu:~/android-ndk-r5b$ ls
GNUmakefile  build               ndk-build  projects  tests
README.TXT   docs                ndk-gdb    samples   toolchains
RELEASE.TXT  documentation.html  platforms  sources
maluchi@ubuntu:~/android-ndk-r5b$ pwd
/home/maluchi/android-ndk-r5b

3. 폴더 위치에 상관없이 ndk-build 명령이 수행되도록 PATH를 다음처럼 추가합니다.

   자신의 홈으로 가서 .bash_profile 파일을 만들어서 다음처럼 추가하고 저장합니다.

   PATH=$PATH:$HOME/android-ndk-r5b

maluchi@ubuntu:~/android-ndk-r5b$ cd
maluchi@ubuntu:~$ ls
android-ndk-r5b          androiddev        workspace  문서      사진
android_gingerbread      examples.desktop  공개       바탕화면  음악
android_gingerbread.tgz  froyo2.2.tgz      다운로드   비디오    템플릿
maluchi@ubuntu:~$ vi .bash_profile 

4. 콘솔에서 source ~/.bash_profile 을 해서 바로 업데이트 하고, 잘 입력이 되었는지 env 명령을 넣어 확인합니다.

maluchi@ubuntu:~$ source ~/.bash_profile 
maluchi@ubuntu:~$ env
......

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/maluchi/android-ndk-r5b
....

 

5. 마지막으로 NDK 폴더 내 samples->hello-jni->jni->Android.mk 를 열어 보면 다음과 같은 부분이 있습니다.

LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c

 

LOCAL_MODULE 은 모듈명이며, LOCAL_SRC_FILES는 C의 구현파일입니다. 여러개 존재한다면 '\ ' 추가합니다.

차후에 Android.mk를 복사해서 수정할 것입니다.

예)

LOCAL_SRC_FILES := hello-jni.c \

                                    test.c

 

6. Android.mk 파일 내용을 봐 보겠습니다. 리눅스에서 해당 파일을 받기 힘드시면 아래 내용으로 파일을 만들어도 됩니다. 

maluchi@ubuntu:~/android-ndk-r5b/samples/hello-jni/jni$ more 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    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

maluchi@ubuntu:~/android-ndk-r5b/samples/hello-jni/jni$

 

이상으로 리눅스에서 모듈을 컴파일을 위한 환경은 완료가 되었습니다.

 

-------------------------------------------------------------------------------------------------

 

나. *.java 에서 *.h, *.c의 JNI 파일 만드는 방법

 

1. 이제 C모듈(*.so)를 java로 가져오는 부분이 필요합니다.  CalcJni.java 라는 파일을 만듭니다.

그리고 다음처럼 추가합니다. 사칙연산을 수행하는 네이티브 메서드를 정의하고 "CalcJni" 라는 C 모듈을 로드하는 역할을 합니다.

01.//package com.maluchi.CalcJNITest;
02.public class CalcJni
03.{
04.public CalcJni(){};
05.public native int Sum(int a, int b);
06.public native int Sub(int a, int b);
07.public native int Mul(int a, int b);
08.public native int Div(int a, int b);
09.public native String message();
10. 
11.static{
12.System.loadLibrary("CalcJni");
13.};}


2. 위의 Java 파일로부터 JNI의 C헤더(*.h) 파일을 만드는 것을 수작업으로 하기에는 번거로운 면이 있습니다.

하지만, Java에서 친절하게도 C헤더 파일을 만들어주는 명령이 있습니다. cmd 창을 하나 띄웁니다.

 

3. 자바 SDK가 설치되었다는 전제하에 해당 경로로 이동하여 *.class 파일을 만듭니다.

 >javac CalcJni.java

 

4. *.h 헤더 파일을 만듭니다.

>javah CalcJni

 

5. 다음처럼 CalcJni.h 헤더가 만들어진 것을 확인할 수 있습니다.

C:\tmp\jni>dir
 C 드라이브의 볼륨: Win7
 볼륨 일련 번호: D037-8597

 C:\tmp\jni 디렉터리

2011-06-16  오후 04:23    <DIR>          .
2011-06-16  오후 04:23    <DIR>          ..
2011-06-16  오후 04:22               425 CalcJni.class
2011-06-16  오후 04:23             1,011 CalcJni.h
2011-06-16  오후 04:22               391 CalcJni.java
               3개 파일               1,827 바이트
               2개 디렉터리  20,543,619,072 바이트 남음

6. CalcJni.h 에 맞춰 CalcJni.c 파일을 만듭니다. 이 때 가장 중요한 메서드 명명 규칙이 있습니다.

javah로 *.h를 만들면 아래처럼 패키지명이 적용되지 않고 만들어집니다.

 JNIEXPORT jint JNICALL Java_CalcJni_Sum(JNIEnv *, jobject, jint, jint);

 

중요한 것은 JNICALL Java_패키지명_클래스명_메서드(...)  규칙으로 적용해야 한다는 것입니다.

실제 안드로이드 패키지를 만드고 그곳에서 C 모듈을 로드한다면 반드시 패키지명_클래스명 을 확인해야 합니다.

그렇지 않으면 에러의 원인이 됩니다. 더불어 JNI에서의 구현부는 반드시 파라미터명을 주도록 되어 있으므로 생략되어 있는 파라미터 변수명들을 넣습니다.

 

 다시 1번에 가서 보시면 //package com.maluchi.CalcJNITest; 주석 처리된 것을 볼수 있습니다.

이는 후에 저 패키지에 자바파일이 들어가게 되고  메서드명들을 아래처럼 모두 바꿀 필요가 있기에 임시 주석처리를 한 것입니다. 

예) JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sum(JNIEnv *, jobject, jint, jint);

 

CalcJni.h

01./* DO NOT EDIT THIS FILE - it is machine generated */
02.#include <jni.h>
03./* Header for class CalcJni */
04. 
05.#ifndef _Included_CalcJni
06.#define _Included_CalcJni
07.#ifdef __cplusplus
08.extern "C" {
09.#endif
10./*
11.* Class:     CalcJni
12.* Method:    Sum
13.* Signature: (II)I
14.*/
15.JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sum
16.(JNIEnv *, jobject, jint, jint);
17. 
18./*
19.* Class:     CalcJni
20.* Method:    Sub
21.* Signature: (II)I
22.*/
23.JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Sub
24.(JNIEnv *, jobject, jint, jint);
25. 
26./*
27.* Class:     CalcJni
28.* Method:    Mul
29.* Signature: (II)I
30.*/
31.JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Mul
32.(JNIEnv *, jobject, jint, jint);
33. 
34./*
35.* Class:     CalcJni
36.* Method:    Div
37.* Signature: (II)I
38.*/
39.JNIEXPORT jint JNICALL Java_com.maluchi.CalcJNITest_CalcJni_Div
40.(JNIEnv *, jobject, jint, jint);
41. 
42./*
43.* Class:     CalcJni
44.* Method:    message
45.* Signature: ()Ljava/lang/String;
46.*/
47.JNIEXPORT jstring JNICALL Java_com.maluchi.CalcJNITest_CalcJni_message
48.(JNIEnv *, jobject);
49. 
50.#ifdef __cplusplus
51.}
52.#endif
53.#endif


CalcJni.c

01.#include <stdio.h>
02.#include "CalcJni.h"
03. 
04.JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Sum
05.(JNIEnv *env, jobject obj, jint a, jint b)
06.{
07.return a+b;
08.}
09./*
10.* Class:     CalcJni
11.* Method:    Sub
12.* Signature: (II)I
13.*/
14.JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Sub
15.(JNIEnv *env, jobject obj, jint a, jint b)
16.{
17.return a-b;
18.}
19./*
20.* Class:     CalcJni
21.* Method:    Mul
22.* Signature: (II)I
23.*/
24.JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Mul
25.(JNIEnv *env, jobject obj, jint a, jint b)
26.{
27.return a*b;
28.}
29./*
30.* Class:     CalcJni
31.* Method:    Div
32.* Signature: (II)I
33.*/
34.JNIEXPORT jint JNICALL Java_com_maluchi_CalcJNITest_CalcJni_Div
35.(JNIEnv *env, jobject obj, jint a, jint b)
36.{
37.if(a == 0 || b ==0)
38.return 0;
39.return a/b;
40.}
41./*
42.* Class:     CalcJni
43.* Method:    message
44.* Signature: ()Ljava/lang/String;
45.*/
46.JNIEXPORT jstring JNICALL Java_com_maluchi_CalcJNITest_CalcJni_message
47.(JNIEnv *env, jobject obj)
48.{
49.return (*env)->NewStringUTF(env, "Hello world!! Welcome to maluchi's world");
50.}


7. 이제 JNI 파일까지 모두 준비가 됐습니다.

 

------------------------------------------------------------------------------------------------------

 

다) 안드로이트 프로젝트에서 JNI 사용하는 방법

 가)에서 NDK를 설정하여 *.so 파일을 만드는 환경 설정을 했고, 

 나)에서 *.so 의 구현 C모듈에서 Java로 읽어들오도록 인터페이스를 만들고 c 파일 구현까지 했습니다.

 

이제 실질적으로 구현한 C 파일을 리눅스에서 컴파일을 하고 *.so 파일을 얻은 후 안드로이트 프로젝트에 넣어서 실제 동작하는지 테스트 해보는 일만 남았습니다.

이 작업을 설명하겠습니다.

 

우선 안드로이드 프로젝트를 하나 만듭니다.

 

1. CalcJNITest 라는 프로젝트를 만들고 패키지경로를 com.maluchi.CalcJNITest 로 합니다.

 

2. 이 프로젝트에 jni 라는 폴더를 만들고 아까 만들어둔 CalcJni.h, CalcJni.c, CalcJni.java 를 넣고, 가) 에서 봤던 Android.mk도 복사해서 넣습니다.

반드시 jni 라는 폴더에 넣어야 합니다. 그리고 Android.mk 의 LOCAL_MODULE 과  LOCAL_SRC_FILES을 다음처럼 수정합니다.

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := CalcJni
LOCAL_SRC_FILES := CalcJni.c

include $(BUILD_SHARED_LIBRARY)

 

3. 이제 CalcJNITest라는 프로젝트를 리눅스 NDK홈에 projects라는 폴더를 만들고 projects 폴더로 옮깁니다.

maluchi@ubuntu:~/android-ndk-r5b/projects$ pwd
/home/maluchi/android-ndk-r5b/projects

maluchi@ubuntu:~/android-ndk-r5b/projects$ ls
CalcJNITest

4. CalcJNITest에 프로젝트에 들어가서 ndk-build 명령을 넣습니다.

해당 프로젝트에 jni 폴더 밑에 c 파일이 있고, ndk-build 는 이를 바탕으로 Libs 폴더를 만들어 *.so 파일을 생성합니다.

진행은 다음처럼 하시면 됩니다..

 

maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$ ls
AndroidManifest.xml  bin                 gen  proguard.cfg  src
assets               default.properties  jni  res
maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$ ndk-build
Compile thumb  : CalcJni <= CalcJni.c
SharedLibrary  : libCalcJni.so
Install        : libCalcJni.so => libs/armeabi/libCalcJni.so
maluchi@ubuntu:~/android-ndk-r5b/projects/CalcJNITest$ 

5. 결국 프로젝트에 Libs/armeabi/libCalcJni.so 파일이 만들어짐을 알게 됩니다.

Libs를 통째로 복사해서 윈도우의 해당 안드로이드 프로젝트에 복사합니다.

 

6. 이제 이 C구현부를 java로 불러들이는 일만 남았습니다.

 아까 만들어둔 jni/CalcJni.java를 com.maluchi.CalcJNITest 패키지로 복사를 합니다.

 그리고 CalcJni.java 파일 내에 주석 처리한 package package com.maluchi.CalcJNITest; 주석해제 시킵니다.

 

7. 구현을 다음과 같습니다.

01.package com.maluchi.CalcJNITest;
02. 
03.import android.app.Activity;
04.import android.os.Bundle;
05.import android.os.Handler;
06.import android.util.Log;
07.import android.widget.TextView;
08.public class CalcJNITest extends Activity {
09./** Called when the activity is first created. */
10.@Override
11.public void onCreate(Bundle savedInstanceState) {
12.super.onCreate(savedInstanceState);
13.setContentView(R.layout.main);
14. 
15.CalcJni jni = new CalcJni();
16. 
17.String string ="";
18.string = "Sum :"+jni.Sum(1010)+
19.", Sub: "+jni.Sub(2010)+
20.", Mul: "+jni.Mul(10010)+
21.", Div: "+jni.Div(10010)+
22.", Message: "+jni.message();
23. 
24.Log.d("maluchi", string);
25.TextView v = (TextView) findViewById(R.id.txt);
26.v.setText(string);
27. 
28.// mHandler.sendEmptyMessageDelayed(0, 300);
29. 
30.}
31. 
32.Handler mHandler = new Handler()
33.{
34.public void handleMessage(android.os.Message msg) {
35. 
36.CalcJni jni = new CalcJni();
37.String string = "";
38.string = "Sum :" + jni.Sum(1010) + ", Sub: " + jni.Sub(2010)
39.", Mul: " + jni.Mul(10010) + ", Div: "
40.+ jni.Div(10010) + ", Message: " + jni.message();
41.TextView v = (TextView) findViewById(R.id.txt);
42.v.setText(string);
43.}; 
44.};
45.}


마지막으로 에러 관련 상황입니다.

06-16 06:33:34.304: ERROR/AndroidRuntime(602): Uncaught handler: thread main exiting due to uncaught exception
06-16 06:33:34.354: ERROR/AndroidRuntime(602): java.lang.UnsatisfiedLinkError: Sum
06-16 06:33:34.354: ERROR/AndroidRuntime(602):     at com.maluchi.CalcJNITest.CalcJni.Sum(Native Method)
 

위처럼 나타나는 에러는 JNI의 메서드명 규칙의 JNICALL Java_ 패키지명_클래스명_메서드명() 규칙이 잘못됐을 때 나타는 현상입니다.

 

만약 다음과 같은 예외가 발생한다면 라이브러리 패스가 올바르게 설정되어 있는지 확인합니다.

java.lang.UnsatisfiedLinkError: no hello in shared library path at java.lang.Runtime.loadLibrary(Runtime.java)
        at java.lang.System.loadLibrary(System.java)
       at java.lang.Thread.init(Thread.java)


http://falseisnotnull.wordpress.com/2012/10/31/did-you-lose-your-mariadb-root-password-gnulinux/


Did you lose your MariaDB root password? (GNU/Linux)

Don’t even think to drastical solutions. If you can log into GNU/Linux as root, you can always recover MariaDB root password.

Did you never know the password?

Maybe you installed MariaDB, or you bought a new server, but you don’t know the root password. Don’t panic! It’s ok!

Probably there is no password. Well, this is false; MariaDB asks for a password, and you won’t be able to logon if the password is incorrect; but the password is an empty string. On the CLI, just press enter to access.

Change it: it is insecure. If I had to break into a MariaDB/MySQL installation as root, I would first try an empty password. Don’t let me break into your system so easily!

Ok, you lost the password

Ok, you lost it. This is not the simplest case, but… it’s simple!

1) Log into your GNU/Linux system as the user used by MySQL (usually ‘mysql’) or root.

2) Restart MariaDB with the grant tables disabled:
mysqld_safe --skip-grant-tables --skip-networking

mysqld_safe will shut down mysqld for you.
With --skip-grant-tables, no password is needed to logon.
This is unsafe, so until the password is reset MariaDB should not accept network connections (--skip-networking).

3) Logon with no password:
mysql -u root

4) Set your new password.

Exec these 2 SQL statements and exit the client:

-- change pwd
UPDATE `mysql`.`user`
	SET `Password` = PASSWORD('new_password')
	WHERE `User` = 'root';
-- tell the server to read the grant tables
FLUSH PRIVILEGES;
\quit

(replace ‘new_password’ with the new password)

5) Stop mysqld_safe and restart mysqld:

mysqladmin shutdown
/etc/init.d/mysql start

(depending from your system, you may need to replace ‘/etc/init.d‘ with the correct MySQL path)

6) Logoff from you system (because you are now root or someone very powerful).


<목표> [안드로이드] 터치화면, 제스처 기능을 이용한 터치 인식

       

 

   오늘은  터치화면을 이용한 제스처 기능에 대해서 알아보겠습니다. 제스처 기능은 말그래로 "동작, 움직임" 을 감지합니다. 하지만 핸드폰 자체의 센서를 이용한 움직임은 아닙니다. 즉, 핸드폰을 흔들거나, 기울이거나를 해서 얻는 이벤트를 이용해서 어떠한 기능을 수행하는 기능이 아닙니다. 제스처 기능은 화면 내의 터치를 감지하여, 어떠한 이벤트가 들어왔는지를 분석하여 어떤 기능을 수행할 수 있도록 합니다. 간단한 예로는 화면을 넘기는 것을 예로 들 수 있습니다. 흔히 메인 화면은 여러 개의 화면으로 구성되어 있습니다. 왼쪽에서 오른쪽으로 화면을 넘기기 위해서 손가락을 이용해서 왼쪽에서 오른쪽으로 드래그하는 동작을 취합니다. 이런 방식으로 손가락 모션을 인식하는 것이 제스처 기능입니다.

 

   아래의 예제 코드는 왼쪽에서 오른쪽으로 드래그했을 때, 오른쪽에서 왼쪽으로 드래그를 했을 때, 아래에서 위로, 위에서 아래로 손가락으로 드래그했을 시에 이벤트를 발생시킬 수 있도록 하는 기능을 담고 있습니다. 이벤트가 발생하면 간단하게 토스트를 띄워서 제대로 동작하고 있는지를 확인하도록 되어있습니다. 그리고 부가적으로, 한번 클릭했을 때, 길게 클릭했을 때 등의 기능도 포함되어 있습니다.

 

   Android & Amir 홈페이지에서 가져왔으며, 한 두가지 오류를 찾아 수정한 것입니다.

       

[ 제스처 결과 화면 ]

(오른쪽 방향으로 Swipe하였을 때)

       

STEP 1  Java Source Code

       

   예제는 간단한 한 화면만 있습니다. 상위에는 현재 마우스 클릭(손가락 터치)이 어떠한 방식으로 되고 있는지 출력하는 텍스트 박스가 있으며, 하위에는 마우스 제스처 (손가락 터치 모션) 를 테스트해볼 수 있는 화면이 구성되어 있습니다. 레이아웃은 Xml을 통해 구현하지 않았고, 바로 코드를 이용해서 추가하도록 하였습니다. 눈여겨 봐야할 것은, 제스처를 인식하는 방법입니다.

   

   

  [[ 예제 코드 ]]   Touch UI Gesture (제스처 기능)

   

 

package pulsebeat.example.gesture;

 

import android.app.Activity;

import android.graphics.Color;

import android.os.Bundle;

import android.view.GestureDetector;

import android.view.Gravity;

import android.view.MotionEvent;

import android.view.GestureDetector.OnGestureListener;

import android.widget.LinearLayout;

import android.widget.TextView;

import android.widget.Toast;

 

public class GestureActivity extends Activity implements OnGestureListener{

 

    private LinearLayout main;

    private TextView viewA;

 

    private static final int SWIPE_MIN_DISTANCE = 120;

    private static final int SWIPE_MAX_OFF_PATH = 250;

    private static final int SWIPE_THRESHOLD_VELOCITY = 200;

 

    private GestureDetector gestureScanner;

 

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        gestureScanner = new GestureDetector(this);

        main = new LinearLayout(this);

        main.setBackgroundColor(Color.GRAY);

        main.setLayoutParams(new LinearLayout.LayoutParams(320, 480));

        viewA = new TextView(this);

        viewA.setBackgroundColor(Color.WHITE);

        viewA.setTextColor(Color.BLACK);

        viewA.setTextSize(30);

        viewA.setGravity(Gravity.CENTER);

        viewA.setLayoutParams(new LinearLayout.LayoutParams(320, 80));

        main.addView(viewA);

        setContentView(main);

    }

 

    @Override

    public boolean onTouchEvent(MotionEvent me) {

        return gestureScanner.onTouchEvent(me);

    }

 

    public boolean onDown(MotionEvent e) {

        viewA.setText("-" + "DOWN" + "-");

        return true;

    }

    

    public boolean onFling(MotionEvent e1, MotionEvent e2, floatvelocityX, float velocityY) {

        try {

            if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)

                return false;

            

            // right to left swipe

            if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE

                    && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                Toast.makeText(getApplicationContext(), "Left Swipe",

                        Toast.LENGTH_SHORT).show();

            }

            // left to right swipe

            else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE

                    && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {

                Toast.makeText(getApplicationContext(), "Right Swipe",

                        Toast.LENGTH_SHORT).show();

            }

            // down to up swipe

            else if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE

                    && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {

                Toast.makeText(getApplicationContext(), "Swipe up",

                        Toast.LENGTH_SHORT).show();

            }

            // up to down swipe

            else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE

                    && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {

                Toast.makeText(getApplicationContext(), "Swipe down",

                        Toast.LENGTH_SHORT).show();

            }

        } catch (Exception e) {

            

        }

 

        return true;

    }

 

    public void onLongPress(MotionEvent e) {

        Toast mToast = Toast.makeText(getApplicationContext(), "Long Press", Toast.LENGTH_SHORT);

        mToast.show();

    }

 

    public boolean onScroll(MotionEvent e1, MotionEvent e2, floatdistanceX, float distanceY) {

        viewA.setText("-" + "SCROLL" + "-");

        return true;

    }

 

    public void onShowPress(MotionEvent e) {

        viewA.setText("-" + "SHOW PRESS" + "-");

    }

 

    public boolean onSingleTapUp(MotionEvent e) {

        Toast mToast = Toast.makeText(getApplicationContext(), "Single Tap", Toast.LENGTH_SHORT);

        mToast.show();

        return true;

    }

}

   

 

   Swipe(휘두르다, 강타)라는 용어를 썼는데, 한국어로 어떻게 변경을 해야할지 몰라 그대로 두었습니다. 손가락을 이용하여 여러 개로 구성되어 있는 메인 화면을 넘기는 방법을 생각하시면 가장 간단할 것 같습니다.

 

   코드의 핵심은 GestureDetector 에 있습니다. 엑티비티에서 OnGestureListener 를 상속받으면, GestureDetector를 사용할 수 있습니다. GestureDetector를 엑티비티에 등록시켜면서 생성합니다. 그런 다음 onTouchEvent를 통해 GestureDetector 객체의 onTouchEvent로 등록시켜줍니다. 이렇게 하면 기본 세팅은 마무리가 됩니다. 그런 다음에 자신이 하고 싶은 기능을 상속받아 사용할 수 있습니다. 위에서 보시면, onDown, onLongPress, onScroll, onShowPress, onSingleTapUp, onFling라는 것을 상속받아 구현되어 있는 것을 확인할 수 있습니다. 함수명에서 보시듯 어떠한 터치 동작을 하고 있는 중인지에 따라 각각이 호출되고 있습니다. 다른 것은 직관적으로 아실 수 있을 거라 생각이 듭니다만, 복잡한 듯 보이는 onFling은 조금 생각을 해보아야 합니다. 아래의 세가지 변수를 먼저 알아봅시다. 이것만 이해되신다면 아주 간단한 구조로 되어 있다는 것을 알게 되실 겁니다.

 

 

static final int SWIPE_MIN_DISTANCE = 120;

static final int SWIPE_MAX_OFF_PATH = 250;

static final int SWIPE_THRESHOLD_VELOCITY = 200;

    

 

   터치 이벤트를 통해서 onFling이벤트로 들어옵니다. onFling으로 들어오는 인자를 보시면, 모션 이벤트가 2개가 들어오고, float 형의 velocity(속도)가 들어옵니다. 벌써 이미 느낌이 오실 겁니다. 첫 번째 인자는 터치를 시작한 그 순간의 이벤트이며, 두 번째 인자는 터치를 땐 그 시점의 이벤트 입니다. 세 번째 인자는 X축으로의 속도이며, 네 번째 인자는 Y 축으로의 인자입니다. 즉 이 네 가지의 이벤트를 가지고, 어떤 방향으로의 Swipe동작인지 구분할 수가 있습니다.

 

   네 가지 방향으로의 Swipe동작을 어떻게 구분하는가 하는 것은 거리 측정과, 속도를 통해서 이루어집니다. SWIPE_MIN_DISTANSE 인자는 Swipe를 인식하는 가장 작은 거리를 뜻합니다. 설정된 거리 이상이 되어야 Swipe동작으로 인식한다는 것이죠. SWIPE_MAX_OFF_PATH는 인식하는 최대 거리라고 보시면 됩니다. 그 이상의 거리를 Scrolling하면 제외하겠다는 의미를 가지고 있습니다. 마지막 SWIPE_THRESHOLD_VELOCITY는 속도입니다. 각 방향으로 임의의 속도 이상으로 터치동작이 이루어져야 Swipe로 인식하겠다는 것이지요.

 

  이제 위의 코드를 살펴보시면, 어떤 구조로 되어 있는지 한 눈에 들어오실 겁니다. 첫 좌표와 끝 좌표를 비교하여 거리를 비교하여 적정 수준의 거리와 속도를 가지면 Swipe로 인식하며 토스트를 띄웁니다. 이러한 방식을 이용하여 화면 전환 등의 자신이 하고 싶은 동작을 넣으시면 됩니다.

 

    

STEP 2  Xml Code

         

    Xml 코드를 제외하고 코드로 바로 레이아웃을 설정하였습니다. onCreate에 있는 추가동작을 xml로 구현하셔도 무방합니다.

             

STEP 3  AndroidManifest.xml Code

       

   안드로이드 자체에서 제공되는 서비스기 때문에  메니페스트는 손댈 필요가 없습니다.

 



 마무리 >   터치화면, 제스처 기능을 이용한 터치 인식

       

   

   일반적으로 다이나믹한 UI를 사용자에게 제공하기 위해서는 터치 기능을 이용하여 다양한 서비스를 제공해야합니다. 제스처(Gesture)를 인식하기 위해서는 이벤트 핸들러를 통해서 현재 어떠한 모션을 취하고 있는지를 확인하는 절차가 필요합니다. onGestureListener를 Activity로 상속받고, GestureDetector를 생성하여, 터치이벤트로 등록시키며, 자신이 원하는 동작을 가져올 수 있도록, 터치 이벤트의 동작 지점, 거리, 터치 속도 등을 고려하여 설계를 하시면 됩니다. 간단한 Swipe기능에 대한 예제를 알아보고, 제스처가 어떠한 방식으로 등록되고 사용되는지에 대해서 알아보았습니다. 이것 외에도 안드로이드에서 제공하는 기본 제스처, GestureLibrary 등이 있는 것으로 생각됩니다만, 아직는 그것을 쓸 필요가 없었던 관계로 추후에 기회가 된다면 다시 포스팅 하겠습니다.    

     

출처 : http://pulsebeat.tistory.com/27

xml파일내에서 

<LinearLayout

android:layout_width="240dip"

android:layout_height="320dip" />


설정해줘도 LayoutParams 사용할 경우 안되는 경우가 있었었었었었다



LayoutParams params = new LayoutParams( 240, 320 ) 해줘도 단위가 달라져서 찾아보던 중


final int width = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 240, getResources().getDisplayMetrics());

final int height = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 320, getResources().getDisplayMetrics());


LinearLayout.LayoutParams paramlinear = new LinearLayout.LayoutParams(width,height);


하니까 됨ㅋ





레이아웃 겹치기

 

카메라 어플을 쓰다보면 기능을 조작하는 부분과 화면이 보이는 2가지 레이아웃을 겹치게 되

데 이것을 한번 해봅시다.

 

 

main.xml

 

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

       android:orientation="vertical"

       android:layout_width="fill_parent"

       android:layout_height="fill_parent"

       >

<TextView

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:textSize="16sp"

       android:text="이것은 바닥에 있는 레이아웃입니다."

       />

<Button

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:text="바닥의 버튼"

       />

</LinearLayout>

 

over.xml

 

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:gravity="center"

    android:background="#40ffff00"

    >

<TextView

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:textSize="16sp"

       android:text="이것은 위쪽에 겹쳐진 레이아웃입니다."

       />

<Button

       android:layout_width="wrap_content"

       android:layout_height="wrap_content"

       android:text="위쪽의 버튼"

       />

</LinearLayout>

 

 

자바파일

 

package com.android.ex85;

 

import android.app.*;

import android.content.*;

import android.os.*;

import android.view.*;

import android.widget.*;

 

public class ex85 extends Activity {

       public void onCreate(Bundle savedInstanceState) {

             super.onCreate(savedInstanceState);

             Window win = getWindow();

             win.setContentView(R.layout.main);//첫번째에 메인을 깔고

 

             //그다음 인플레이션으로 겹치는 레이아웃을 깐다

             LayoutInflater inflater = (LayoutInflater)getSystemService(

                           Context.LAYOUT_INFLATER_SERVICE);

             LinearLayout linear = (LinearLayout)inflater.inflate(R.layout.overnull);

            

             LinearLayout.LayoutParams paramlinear = newLinearLayout.LayoutParams(

                           LinearLayout.LayoutParams.FILL_PARENT,

                           LinearLayout.LayoutParams.FILL_PARENT);

             win.addContentView(linear, paramlinear);// 부분이 레이아웃을겹치는 부분

             //add 기존의 레이아웃에 겹쳐서 배치하라는 뜻이다.

       }

}

 


출처 : http://blog.naver.com/baram918?Redirect=Log&logNo=120134113410


Twitter OAuth Sign In Tutorial: Get A User’s Profile Information


Twitter Bird

Since we are going to be using three classes to handle all the OAuth requests for us I would like to explain what will be going on in the background before we begin. I will refer to twitter’s API as the API but know that this applies to all others that use OAuth.

The way OAuth works is the like this

  • your script sends a url to an API
  • the API sends a token (oauth_token) back to us,
  • we use this token to make a url that will take the user to a verification page
  • after granting us access we can request a secret token or access token (oauth_token_secret)
  • now we can use this secret token to make calls to the API

Step 1: The set up

Make three files “keys.php” , “sign-in.php” and “profile-page.php”m

The firs file will be used to store you twitter app keys. We will then make a login form with a twitter sign in button and process the response with “profile-page.php”.

Step 2: Register you twitter application.

Go to this link to register you app Twitter OAuth, your callback URL must be the URL to your “profile-page.php” file.

In your “keys.php” file make two variables, one for your “consumer key” and for your “consumer secret” .

$consumerKey='xxxxxxxxxx';
$consumerSecret='xxxxxxxxxxxxxxxxxxxxxxx';

Step 3: Some files you need

We’ll be using three classes developed by   @jmathai

Download the files “EpiCurl.php”, “EpiOAuth.php” and “EpiTwitter.php” from this link EpiFramework and put them in the same folder as the 3 files in step 1.

Step 4 : Get a  ”Sign in with Twitter” button

Go to this page (sign in with twitter buttons) , scroll all the way down, and download your favorite button image.

Step 5: The sign in page

This is where we request the first token (oauth_token) and use it to make a link to to twitter’s verification page.

Open your “sign-in.php” file and include the three Epi classes and keys file.

We will also make an object for EpiTwitter, the constructor for this class takes two paramaters, you cosumerkey and cosumer secret from step 2.

EpiTwitter has a function called “getAuthenticateUrl()” , this function returns a URL which we’ll use to make a link ( sign in button) to the twitter authentication page.

Contents of “sign-in.php”

<?php
include 'EpiCurl.php';
include 'EpiOAuth.php';
include 'EpiTwitter.php';
include 'keys.php';

$Twitter = new EpiTwitter($consumerKey, $consumerSecret);

echo '<a href="' . $Twitter->getAuthenticateUrl() . '">
<img src="twitterButton.png" alt="sign in with twitter" />
</a>';
?>

Step 6: User is Now on Twitter’s Verification Page.

If your users allow your app they will be redirected to your call back URL with an “oauth_token” variable in the url, http://www.yourdomain.com/profile-page.php?oauth_token=xxxxxxxxxxxxxx  for example.

If they deny access twitter will show them  the following message:

“OK, you’ve denied YourAppName access to interact with your account!”

YourAppName will be a link to your call back url with a “denied” variable in the url. Something like this

http://www.yourdomain.com/profile-page.php?denied=xxxxxxxxxxxxxxx

Step 7: The Profile Page

This is where we will retrieve the user’s info from twitter’s api.

Begin by making an object of the class EpiTwitter.

// include Epi
require_once 'classes/php/oauth/keys.php';
require_once 'classes/php/oauth/EpiCurl.php';
require_once 'classes/php/oauth/EpiOAuth.php';
require_once 'classes/php/oauth/EpiTwitter.php';

$Twitter = new EpiTwitter($consumerKey, $consumerSecret);

We should also check to see if the user allowed or denied access by checking if the “oauth_token” variable is set in our url (Cookie variables explained after the snippet)

// previous code here
if(isset($_GET['oauth_token']) || (isset($_COOKIE['oauth_token']) && isset($_COOKIE['oauth_token_secret'])))
{
  // user has signed in
}
elseif(isset($_GET['denied'])
{
 // user denied access
 echo 'You must sign in through twitter first';
}
else
{
// user not logged in
 echo 'You are not logged in';
}

Before we get a user’s info,  we need an access token, to get this token we use a function called getAccessToken(). The function getAccessToken() returns two variables, oauth_token and oauth_token_secret, we are going to store these two variables in two different cookies, that is why I also checked for these cookies in the previous code.

If the user has already signed in when they get to this page then there is no need to request another token, that means there is no need to call getAccessToken() if we already obtained the secret token and stored it in a cookie. Put the following code where it said “// user has signed in” up above.

// user has signed in
	if( !isset($_COOKIE['oauth_token']) || !isset($_COOKIE['oauth_token_secret']) )
	{
		// user comes from twitter
                // send token to twitter
	        $Twitter->setToken($_GET['oauth_token']);
               // get secret token
		$token = $Twitter->getAccessToken();
                // make the cookies for tokens
		setcookie('oauth_token', $token->oauth_token);
		setcookie('oauth_token_secret', $token->oauth_token_secret);
               // pass tokens to EpiTwitter object
		$Twitter->setToken($token->oauth_token, $token->oauth_token_secret);

	}
	else
	{
	 // user switched pages and came back or got here directly, stilled logged in
        // pass tokens to EpiTwitter object
	 $Twitter->setToken($_COOKIE['oauth_token'],$_COOKIE['oauth_token_secret']);
	}

Finally we can use the object $Twitter to get the user’s profile info. And this is what it looks like.

    $user= $Twitter->get_accountVerify_credentials();

The variable $user is actually an object of SimpleXml containing this response.
Now let’s display some info.

// show screen name (not real name)
echo $user->screen_name}
// show profile image url
echo $user->profile_image_url
// show last tweet
echo $user->status->text;

full contents of profile-page.php

<?php

// include Epi
require_once 'classes/php/oauth/keys.php';
require_once 'classes/php/oauth/EpiCurl.php';
require_once 'classes/php/oauth/EpiOAuth.php';
require_once 'classes/php/oauth/EpiTwitter.php';
	    $Twitter = new EpiTwitter($consumerKey, $consumerSecret);

if(isset($_GET['oauth_token']) || (isset($_COOKIE['oauth_token']) && isset($_COOKIE['oauth_token_secret'])))
{
// user accepted access
	if( !isset($_COOKIE['oauth_token']) || !isset($_COOKIE['oauth_token_secret']) )
	{
		// user comes from twitter
	    $Twitter->setToken($_GET['oauth_token']);
		$token = $Twitter->getAccessToken();
		setcookie('oauth_token', $token->oauth_token);
		setcookie('oauth_token_secret', $token->oauth_token_secret);
		$Twitter->setToken($token->oauth_token, $token->oauth_token_secret);

	}
	else
	{
	 // user switched pages and came back or got here directly, stilled logged in
	 $Twitter->setToken($_COOKIE['oauth_token'],$_COOKIE['oauth_token_secret']);
	}

    $user= $Twitter->get_accountVerify_credentials();
	echo "
	<p>
	Username: <br />
	<strong>{$user->screen_name}</strong><br />
	Profile Image:<br/>
	<img src=\"{$user->profile_image_url}\"><br />
	Last Tweet: <br />
	<strong>{$user->status->text}</strong><br/>

	</p>";

}
elseif(isset($_GET['denied']))
{
 // user denied access
 echo 'You must sign in through twitter first';
}
else
{
// user not logged in
 echo 'You are not logged in';
}

To log a user out, make a log-out.php for example, and expire the cookies.

setcookie("oauth_token", '', time()-100);
setcookie("oauth_token_secret", '', time()-100);

Epi does not currently support twitter sign out even though twitter’s API does, but the user will be signed out of twitter when they close the browser’s window. I will show you how to update statuses and other neat stuff in part of two of this tutorial.


'공부 > Php' 카테고리의 다른 글

[PHP] If 조건문 한줄로 축약  (0) 2023.12.29
[PHP] PC / 모바일 환경인지 체크하기  (0) 2020.01.21
트위터 oauth with php  (0) 2013.07.22
php 파일 include  (0) 2013.01.11
php 파일업로드 크기 제한 설정 php.ini (nginx)  (0) 2013.01.05
How to Authenticate Users With Twitter OAuth

\Rating:

How to Authenticate Users With Twitter OAuth

Tutorial Details


Beginning August 16th, Twitter will no longer support the basic authentication protocol for its platform. That means the only way to authenticate users will be through a Twitter application. In this tutorial, I’ll show you how to use Twitter as your one-click authentication system, just as we did with Facebook.


Step 1: Setting Up The Application

We’ll first need to set up a new Twitter application.

  • Register a new app at dev.twitter.com/apps/
  • Fill in the fields for your site accordingly, just be sure to select Browser in Application Type, and set the Callback URL to something like http://localhost.com/twitter_login.php(http://localhost/ won’t be accepted because it doesn’t have a domain name).
  • Finally, select Read & Write. Fill in the captcha, click “Register Application,” and accept the Terms of Service.

Now, you’ll see the screen as shown below.

We will be using the Consumer key and Consumer secret values shortly.

Now that this is done, let’s download a library. As we will be coding with PHP, it seems the best one istwitteroauth; but if you’re using another language, you’ll find other good libraries here.

Find the twitteroauth directory inside the zip file, and extract it to your application’s folder.

Finally, since we’re using Twitter to authenticate users, we’ll need a database table to store those users. Here’s a quick example of what we will be doing.

  1. CREATE TABLE `users` (  
  2.     `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  
  3.     `oauth_provider` varchar(10),  
  4.     `oauth_uid` text,  
  5.     `oauth_token` text,  
  6.     `oauth_secret` text,  
  7.     `username` text,  
  8.     PRIMARY KEY (`id`)  
  9. ) ENGINE=MyISAM  DEFAULT CHARSET=latin1;  

Notice the oauth_token and oauth_secret fields. Twitter’s OAuth requires token and a token_secretvalues to authenticate the users, so that’s why we’re including those. With that, we are done with the setup!


Step 2: Registering Users

In this step we, will be doing three things:

  • Requesting authorization from Twitter.
  • Registering or, if the user is already registered, logging the user in.
  • Setting the data into a session.

Requesting authorization

The OAuth workflow starts by generating a URL for the request; the user is redirected to that URL and is asked for authorization. After granting it, the application redirects back to our server with two tokens in the URL parameters, which are required for the authentication.

Let’s begin by including the library and starting a session handler.

  1. require("twitteroauth/twitteroauth.php");  
  2. session_start();  

After that, let’s create a new TwitterOAuth instance, giving it the consumer key and consumer secret that Twitter gave us when we created the application. Then, we’ll request the authentication tokens, saving them to the session, and redirect the user to Twitter for authorization.

  1. // The TwitterOAuth instance  
  2. $twitteroauth = new TwitterOAuth('YOUR_CONSUMER_KEY''YOUR_CONSUMER_SECRET');  
  3. // Requesting authentication tokens, the parameter is the URL we will be redirected to  
  4. $request_token = $twitteroauth->getRequestToken('http://localhost.com/twitter_oauth.php');  
  5.   
  6. // Saving them into the session  
  7. $_SESSION['oauth_token'] = $request_token['oauth_token'];  
  8. $_SESSION['oauth_token_secret'] = $request_token['oauth_token_secret'];  
  9.   
  10. // If everything goes well..  
  11. if($twitteroauth->http_code==200){  
  12.     // Let's generate the URL and redirect  
  13.     $url = $twitteroauth->getAuthorizeURL($request_token['oauth_token']); 
  14.     header('Location: '. $url); 
  15. } else { 
  16.     // It's a bad idea to kill the script, but we've got to know when there's an error.  
  17.     die('Something wrong happened.');  
  18. }  

Save it as twitter_login.php, go to http://localhost.com/twitter_login.php or whatever your local host name is. If everything went correctly, you should be redirected to twitter.com, and you should see something like this.

Click allow, and you will be redirected to http://localhost.com/twitter_oauth.php — since we set this URL as a parameter in the getRequestToken statement. We haven’t created that file, so it should throw an error. Create that file, and then include the library and start a session, just like we did in the first file.

After that, we will need three things:

  • Auth verifier in the URL query data
  • Auth token from the session
  • Auth secret from the session

So, the first thing to do in this script is validate this data and redirect if one of these variables is empty.

  1. if(!empty($_GET['oauth_verifier']) && !empty($_SESSION['oauth_token']) && !empty($_SESSION['oauth_token_secret'])){  
  2.     // We've got everything we need  
  3. else {  
  4.     // Something's missing, go back to square 1  
  5.     header('Location: twitter_login.php');  
  6. }  

Now, if everything is set, inside the conditional we will be creating the TwitterOAuth instance, but with the tokens we just got as third and fourth parameters; after that, we will be getting the access token, which is an array. That token is the one we will be saving to the database. Finally, we’ll do a quick test to see if everything works out.

  1. // TwitterOAuth instance, with two new parameters we got in twitter_login.php  
  2. $twitteroauth = new TwitterOAuth('YOUR_CONSUMER_KEY''YOUR_CONSUMER_SECRET'$_SESSION['oauth_token'], $_SESSION['oauth_token_secret']);  
  3. // Let's request the access token  
  4. $access_token = $twitteroauth->getAccessToken($_GET['oauth_verifier']); 
  5. // Save it in a session var 
  6. $_SESSION['access_token'] = $access_token; 
  7. // Let's get the user's info 
  8. $user_info = $twitteroauth->get('account/verify_credentials'); 
  9. // Print user's info  
  10. print_r($user_info);  

If nothing goes wrong, the print_r should show the user’s data. You can get the user’s id with$user_info->id, his or her username with $user_info->screen_name; there’s a bunch of other info in there as well.

It is very important to realize that the oauth_verifier hasn’t been used before this. If you see the user’s info correctly and then reload the page, the script will throw an error since this variable has been used. Just go back to twitter_login.php and it will automatically generate another fresh token.

Registering users

Now that we have the user’s info we can go ahead and register them, but first we have to check if they exist in our database. Let’s begin by connecting to the database. Add these lines in the script’s beginning.

  1. mysql_connect('localhost''YOUR_USERNAME''YOUR_PASSWORD');  
  2. mysql_select_db('YOUR_DATABASE');  

Modify the database info as required. Now, just below where we fetch the user’s info, we’ll have to check for the user in our database. If he or she is not there, we’ll enter the info. If the user has been registered, we must update the tokens, because Twitter has generated new ones and the ones we have in the database are now unusable. Finally, we set the user’s info to the session vars and redirect to twitter_update.php.

  1. if(isset($user_info->error)){  
  2.     // Something's wrong, go back to square 1  
  3.     header('Location: twitter_login.php'); 
  4. } else { 
  5.     // Let's find the user by its ID  
  6.     $query = mysql_query("SELECT * FROM users WHERE oauth_provider = 'twitter' AND oauth_uid = "$user_info->id);  
  7.     $result = mysql_fetch_array($query);  
  8.   
  9.     // If not, let's add it to the database  
  10.     if(empty($result)){  
  11.         $query = mysql_query("INSERT INTO users (oauth_provider, oauth_uid, username, oauth_token, oauth_secret) VALUES ('twitter', {$user_info->id}, '{$user_info->screen_name}', '{$access_token['oauth_token']}', '{$access_token['oauth_token_secret']}')");  
  12.         $query = mysql_query("SELECT * FROM users WHERE id = " . mysql_insert_id());  
  13.         $result = mysql_fetch_array($query);  
  14.     } else {  
  15.         // Update the tokens  
  16.         $query = mysql_query("UPDATE users SET oauth_token = '{$access_token['oauth_token']}', oauth_secret = '{$access_token['oauth_token_secret']}' WHERE oauth_provider = 'twitter' AND oauth_uid = {$user_info->id}");  
  17.     }  
  18.   
  19.     $_SESSION['id'] = $result['id']; 
  20.     $_SESSION['username'] = $result['username']; 
  21.     $_SESSION['oauth_uid'] = $result['oauth_uid']; 
  22.     $_SESSION['oauth_provider'] = $result['oauth_provider']; 
  23.     $_SESSION['oauth_token'] = $result['oauth_token']; 
  24.     $_SESSION['oauth_secret'] = $result['oauth_secret']; 
  25.  
  26.     header('Location: twitter_update.php');  
  27. }  

Note that these queries are not validated; if you leave them as they are, you are leaving your database vulnerable. Finally, below the database connection, we should set a check to verify that the user is logged in.

  1. if(!empty($_SESSION['username'])){  
  2.     // User is logged in, redirect  
  3.     header('Location: twitter_update.php');  
  4. }  

You can now greet the user by his or her username.

  1. <h2>Hello <?=(!empty($_SESSION['username']) ? '@' . $_SESSION['username'] : 'Guest'); ?></h2>  

Let’s get to the fun side: updating, following and reading.


Step 3: Reading Statuses

There are over twenty categories of resources available: timeline, tweets, users, trends, lists, direct messages, etc. Each one has a bunch of methods, you can check them all in the official documentation. We’ll get to the basics, as most of these features are accessed in a similar way.

Just like the other two scripts, we’ll need to create the TwitterOAuth instance, including the variables in the session.

  1. if(!empty($_SESSION['username'])){  
  2.     $twitteroauth = new TwitterOAuth('YOUR_CONSUMER_KEY''YOUR_CONSUMER_SECRET'$_SESSION['oauth_token'], $_SESSION['oauth_secret']);  
  3. }  

We’ll begin with the user’s timeline. The reference tells us that the path is statuses/home_timeline; ignore the version and format, the library will take care of it.

  1. $home_timeline = $twitteroauth->get('statuses/home_timeline');  
  2. print_r($home_timeline);  

That will get you the timeline. You can fetch each item with a foreach loop. However, the reference specifies some optional parameters like count, which limits how many tweets will be fetched. In fact, get‘s second parameter is an array of every option needed, so if you want to fetch the latest forty tweets, here’s the code:

  1. $home_timeline = $twitteroauth->get('statuses/home_timeline'array('count' => 40));  

Also, you can see somebody else’s timeline, as long as it’s not protected. statuses/user_timelinerequires either a user’s id or screen name. If you want to check @nettuts timeline, you’ll have to use the following snippet:

  1. $nettuts_timeline = $twitteroauth->get('statuses/user_timeline'array('screen_name' => 'nettuts'));  

As you can see, after authenticating, reading timelines is a breeze.


Step 4: Friendships

With friendships, you can check if a user follows another one, as well as follow or unfollow other users. This snippet will check if you are following me and and will create the follow if not.

But first, check the friendships/exists and friendships/create reference. Notice something?friendships/create method is POST. Fortunately, the library includes a post() function, which works just as the get() function; the main difference is that get() is for reading and post() is for creating, deleting or updating.

Anyways, friendships/exists requires two parameters: user_a and user_b, and friendships/createrequires just one, either screen_name or user_id.

  1. $follows_faelazo = $twitteroauth->get('friendships/exists'array('user_a' => $_SESSION['username'], 'user_b' => 'faelazo'));  
  2. if(!$follows_faelazo){  
  3.     echo 'You are NOT following @faelazo!';  
  4.     $twitteroauth->post('friendships/create'array('screen_name' => 'faelazo'));  
  5. }  

You can unfollow an user with basically the same code that creates a follow, just replace create withdestroy:

  1. $follows_faelazo = $twitteroauth->get('friendships/exists'array('user_a' => $_SESSION['username'], 'user_b' => 'faelazo'));  
  2. if($follows_faelazo){  
  3.     echo 'You are following @faelazo! Proceed to unfollow...';  
  4.     $twitteroauth->post('friendships/destroy'array('screen_name' => 'faelazo'));  
  5. }  

Step 5: Posting Updates

This is probably the most interesting section, since it’s Twitter’s core: posting an update, as you might have imagined, is pretty straightforward. The path is statuses/update, the method is POST (since we are not reading), and the one required argument is status.

  1. $twitteroauth->post('statuses/update'array('status' => 'Hello Nettuts+'));  

Now go to your Twitter profile page and you’ll see your tweet.

Let’s retweet @Nettuts’ update announcing the HTML 5 Competition; the status id is 19706871538 and the reference tells us that the path is statuses/retweet/:id, where the :id part is the status id we will be retweeting. The method is POST and it doesn’t require additional parameters.

  1. $twitteroauth->post('statuses/retweet/19706871538');  

To delete a tweet, you’ll have to pass the status id you’ll be destroying in the first parameter, just like retweeting. If the tweet’s id is 123456789, the code to destroy will be.

  1. $twitteroauth->post('statuses/destroy/123456789');  

Of course, this code can only delete tweets made by the authenticated user.


Conclusions

Twitter’s API is quite easy to understand; it’s far more documented than even Facebook’s (even though Facebook offers an in-house library). Unfortunately, the authentication is not as smooth as we might hope, depending on session data.

One thing worth noticing is that, once a Twitter user has been authorized (assuming the app has read and write permissions), you have plenty of control over this account. If you change something on behalf of the user without his permission, you’ll create trouble. Use it with caution!

The API changes coming to Twitter will deny basic authentication; Twitter is focusing on ceasing the countless scams that trick users into giving up their login credentials. OAuth is the solution; and, if you’ve worked through the Facebook Connect tutorial, you can now provide your website or app users with a quick login without credentials, using your choice of the two most used social networks. How cool is that?


'공부 > Php' 카테고리의 다른 글

[PHP] PC / 모바일 환경인지 체크하기  (0) 2020.01.21
트위터 OAuth Sign in 튜토리얼  (0) 2013.07.22
php 파일 include  (0) 2013.01.11
php 파일업로드 크기 제한 설정 php.ini (nginx)  (0) 2013.01.05
php 한글깨짐현상  (0) 2012.08.03

JNI 프로그램 작성을 위한 개발 환경 셋팅

  • JNI를 사용하기 위해서는 윈도우즈와 리눅스에서 개발환경을 셋팅할 수 있다.
  • 리눅스와 윈도우즈 둘의 차이점은 cygwin 설치 밖에 없음.
  • Android SDK, Android NDK, Cygwin 프로그램이 시스템에 설치되었고 ADT가 Eclipse에 설치되었다면 Android 프로젝트에서 JNI(Java Native Interface)를 이용하여 C, C++언어에서 작성된 공유 라이브러리(*.so)를 로드하고 포함된 함수를 호출할 수 있는 프로그램을 작성할 수 있다.


리눅스에서의 개발환경 셋팅


윈도우즈에서의 개발환경 셋팅

Cygwin 설치

  • Cygwin 이란 ? 윈도우 시스템에서 Linux와 비슷한 환경을 만들어주는 프로그램
  • Cygwin 설치방법
    1. setup.exe 파일 실행 - install from internet 선택
    2. Root 경로 지정 & install For All Users
    3. Download 받을 패키지가 저장될 경로 지정(Select Local package Directory)
    4. Internet 연결 - Direct connection 선택
    5. Download Site 선택 - ftp://ftp.kaist.ac.kr
    6. Progress - 패키지 목록을 가져옴 --> 처음 설치할 경우 설치할 패키지를 선택할 수 있다는 알림창 뜸
    7. 설치할 수 있는 패키지 목록이 대화창에 출력됨, 필요한 것 선택 Devel/gcc-g++, Devel/gcc-core, Devel/make, Editor/vim
      • 나중에 다시 필요한 패키지 설치할 수 있음. 선택한 패키지의 크기에 따라 시간이 좀 걸린다.
      • 설치화면
      • ㅣ설치화면
    8. 시작메뉴에 Cygwin Bash Shell 메뉴 생성된 것 확인 . 설치 완료
      • Cygwin Bash Shell 화면
      • Cygwin Bash Shell 화면
      • 더 설치하고 싶은 패키지가 있으면 다시 setup.exe 파일을 실행시켜 동일한 방법으로 다운받으면 된다.


Android NDK 설치

  • NDK Download 받기
    • URL : http://developer.android.com/sdk/ndk/index.html
    • Android developers 사이트에 가면 SDK 페이지 좌측에 메뉴에 Native Development Tools 메뉴에서 NDK 선택
    • Download the Android NDK 에서 자신의 개발환경에 맞는 OS 선택 - 이 페이지의 설명은 Windows 기반임.
    • Download the Android NDK Webpage
    • ㅣDownload the Android NDK Webpage


  • NDK 설치하기
    • 다운받은 android-ndk-r4.zip 파일을 Cygwin이 설치된 폴더 안의 Home에 복사
    • copy path - C:/cgywin/home/계정명/ 밑에 복사해줌
    • Android NDK Path 설정
    • ㅣAndroid NDK Path 설정


  • 다운로드 받아서 Path만 지정해주면 NDK를 사용하기 위한 절차는 끝


Hello JNI Example

  • NDK 설치가 끝나면 테스트해보기 위한 Sample Code가 있다. Sample Code 중 HelloJni를 구동하는 절차를 설명한다.
  • 윈도우즈 환경 셋팅에서 설치한 cgywin을 사용하여 NDK 빌드를 할 수 있다.

NDK 빌드

  • Cygwin Bash Shell 을 실행해서 android-ndk-r4 폴더로 이동
    • 빌드할 프로젝트 폴더인 samples로 이동 >> android-ndk-r4/samples/hello-jni
    • ndk-build 를 실행하여 빌드 시작 >> /home/Ryoung/android-nkd-r4/ndk-build -B
    • ㅣNDK Build
  • .so 파일 생성
    • /android_ndk/samples/hello-jni/libs/armeabi 경로에 가면 libhello-jni.so 파일 생성된 것 확인할 수 있다.
    • ㅣ.so파일 생성

Hello Jni 실행

  • 예제 파일이였던 Hello Jni가 들어있는 Sample 파일 가져와서 프로젝트 생성
    • 경로 : android-ndk/samples/hello-jni
    • ㅣndk 빌드해서 생성한 .so 파일 확인


  • Android 에뮬레이터 부팅이 ㅣ끝나면 HelloJni 구동화면 볼 수 있음
  • ㅣ안드로이드 에뮬레이터 구동화면
  • Android NDK에서 제공하는 Sample code는 원시코드의 헤더파일이 이미 만들어져있기 때문 NDK 빌드만 하면 에뮬레이터로 결과를 확인할 수 있다.



HelloJni Sample Code

  • NDK에 포함되어 있는 샘플 코드는 원시코드의 헤더파일이 이미 만들어져있으나, 샘플코드를 기반으로 헤더파일을 만들고 JNI 프로그램을 작성하는 절차를 설명하도록 한다.


안드로이드 프로젝트 생성

  • 제일 먼저 이클립스에서 일반적인 안드로이드 프로젝트를 생성한다.
    • New Project

자바 클래스 작성

  • JNI 규칙에 따라서 공유라이브러리를 로드하고 라이브러리에 포함된 함수를 호출하는 native 메소드를 선언한다.
  • HelloJni.java
package com.example.hellojni;
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
 
public class HelloJni extends Activity {
        /**Called when the activity is first created.*/
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                TextView tv= new TextView(this);
                tv.setText(stringFromJNI()); // A native 함수호출
                   setContentView(tv);
        }           
 
        // A native function
        public native String stringFromJNI();
 
        // 정의하지 않은 함수 호출시 java.lang.UnsatisfiedLink Errorexception 발생!
        public native String unimplementedStringFromJNI();
 
        // com.example.HelloJni/lib/libhello-jni.so
        static {
                System.loadLibrary("hello-jni");
        }
}
  • Native Method Declaration : public native String stringFromJNI();
    • Native 선언은 자바 가상 머신 내에서 원시 함수를 호출할 수 있는 브릿지를 제공해 준다.
  • 라이브러리 적재 : System.loadLibrary()
    • 원시 코드 구현을 포함하고 있는 라이브러리는 System.loadLibrary()를 호출함으로써 적재된다. 정적 초기화 구문(static initializer ensures) 내에서 이 호출을 함으로써, 클래스당 단 한번만 적재되도록 한다. 클래스에 대해 공유되는 멤버들은 static을 이용하여 선언한다. 클래스 멤버를 선언하는 위치에 static{....} 과 같이 초기화 블럭을 정의하여 사용할 수 있다.
  • main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
	android:id="@+id/textView"  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>


헤더파일 작성과 프로그램 컴파일

  • 프로그램 컴파일은 command line을 이용하므로 앞에서 설치한 cgywin을 사용한다.
    • 프로젝트가 컴파일 된 bin 디렉토리로 이동한 뒤 javah packagename.classname 를 수행하면 아래 그림과 같이 헤더파일이 생성된다.
    • javah
    • 패키지명이 포함되어 헤더파일이름이 생성되므로 너무 길면 줄여서 사용해도 무방하다.
      • hufs.dislab.hellojni.HelloJni.h --> HelloJni.h


  • 생성된 HelloJni.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class hufs_dislab_hellojni_HelloJni */
 
#ifndef _Included_hufs_dislab_hellojni_HelloJni
#define _Included_hufs_dislab_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     hufs_dislab_hellojni_HelloJni
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_hufs_dislab_hellojni_HelloJni_stringFromJNI
  (JNIEnv *, jobject);
 
/*
 * Class:     hufs_dislab_hellojni_HelloJni
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_hufs_dislab_hellojni_HelloJni_unimplementedStringFromJNI
  (JNIEnv *, jobject);
 
#ifdef __cplusplus
}
#endif
#endif

라이브러리 생성을 위한 jni 디렉토리 생성

  • 만들어진 헤더파일(HelloJni.h)을 jni 디렉토리로 이동시킨다.
  • jni 디렉토리는 프로젝트 내에 생성하고 이 디렉토리는 위에서 생성한 헤더파일, C파일, Android.mk 파일을 저장한다.
  • Jni 디렉토리 생성


Android.mk 파일 생성

  • LOCAL_MODULE 에 설정한 값으로 나중에 생성될 공유 라이브러리 이름이 된다. HelloJniTest --> libHelloJniTest.so (일반적으로 라이브러리 이름은 소문자로 해야함...;; )
  • 컴파일할 소스파일이 여러 개일 경우에는 LOCAL_SRC_FILES에 나열해 준다.
  • Android.mk
LOCAL_PATH := $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE    := HelloJniTest
LOCAL_SRC_FILES := HelloJni.c
 
include $(BUILD_SHARED_LIBRARY)

Native code 작성(C파일 생성)

  • 생성한 헤더파일에 선언된 C 함수를 구현한다.선언된 C함수를 모두 구현하지 않아도 무방하다.
  • Hello-Jni.c
#include <string.h>
#include <jni.h>
 
jstring Java_hufs_dislab_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
        return (*env) -> NewStringUTF(env, "Hello From JNI !");
}


C소스 컴파일 / 공유라이브러리 생성

  • 앞에서 구현한 C 코드를 컴파일하여 라이브러리를 생성한다.
  • C코드 컴파일 & 라이브러리 생성
  • 위의 그림과 같이 Cygwin을 실행하고 해달 프로젝트로 이동한다. 그리고 ndk-build 명령을 이용하여 컴파일을 한다.
  • 컴파일이 끝나면 프로젝트 안에 libs/armeabi 디렉토리가 생성되고 HelloJniTest.so 가 생긴 것을 확인할 수 있다.
  • .so파일 생성 확인
  • 라이브러리 생성 과정까지 마치고 에뮬레이터를 돌려 결과를 확인한다. JNI 프로그램 작성 끝!


참고

Android NDK

  • NAtive code를 안드로이드 애플리케이션에 적용할 수 있게 함
  • NDK는 다음을 제공
    • C와 C++로 라이브러리 작성하는 툴
    • 라이브러리를 Android에 적재할 수 있는 .apsk로 변환하는 방법 제공
    • Native System headers & libraries
    • Documentation, samples & tutorials
  • NDK 활용의 예 : 신호처리, 물리 시뮬레이션, 커스텀 바이트코드/ 명령어 인터프리터 등과 같이 메모리를 너무 많이 할당하지 않으면서도 CPU를 많이 사용하는 작업에 적함
    • 장점 : 빠른실행
    • 단점 : 이식성 없음, JNI 오버헤드 수반, 시스템 라이브러리에 접근불가, 디버깅 어려움
    • 한계
    1. C에서 실행되는 메소드를 단순히 재코딩 하는것 으로는 성능향상에 큰 도움이 되지 않는다.
    2. NDK가 native-only 어플리케이션 개발은 힘들다.(Please note that the NDK does not enable you to develop native-only application)
    3. 안드로이드의 첫 번째 런타임은 Dalvik 가상머신에 있다.
  • 포함 되어 있는 C 헤더들
    • libc ( C 라이브러리 ) 헤더
    • libm ( math 라이브러리 ) 헤더
    • Jni Interface 헤더
  • 호환성을 보장하는 방법( application using a native library produced with the NDK)
    • 해당 매니페스트 파일에 android:minSdkVersion="3" 속성과 함께 <users-library> 엘리먼트를 반드시 선언한다.


읽어볼만해서 펌

출처 :http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=34284

이번 컬럼은 가상의 시나리오를 통해 실제 프로젝트 진행 중에 벌어질 수 있는 문제상황을 제시하고 동시에 이 문제를 해결해나가는 과정을 알아보도록 하겠다.

등장인물

● 허우대 대리 : 몇달전 경력사원 공채로 새로 입사한 인물로 Java를 사용해봤다는 이유만으로  난생 처음으로 안드로이드 플랫폼에 기존에 C++로 작성한 소스를 마이그레이션 하는 업무를 맞는다. 허우대는 멀쩡 하지만 성과가 없다고 일정만 차장으로 부터 허우대라는 별명을 얻었다.

● 신익한 선배 : 허우대 대리의 학교 선배로 모바일 분야에서 안해본게 없는 대단한 실력자 이지만, 일을 너무 사랑하는 나머지 인간관계는 별로 소질이 없어 보이는 시니컬한 성격의 소유자, 허우대 대리의 질문에 매번 퉁명스럽게 대답 하지만 그 속마음에는 후배를 스스로 깨우치게 하려는 나름의 철학을 가지고 있다.

● 일정만 차장 : 이번 프로젝트의 책임자로, 일에 대한 책임감이 투철하지만 상냥한 성격은 아니다. 특히 일정을 어기는 사람을 무척 싫어한다.


허우대 대리는 얼마전 일정만 차장으로 부터 1주일간의 시간동안 이전 누군가가 C++ 로 작성한 방대한 양의 xml 문서 parser를 이번 프로젝트에 재사용 할 수 있도록 안드로이드 플랫폼에 마이그레이션 하라는 지시를 받았다. 허우대 대리에게 주어진 시간은 고작 7일. 7일 동안 허우대 대리가 어떻게 이 프로젝트를 진행하는지 함께 지켜보도록 하자.

선배, 안드로이드 애플리케이션 개발은 Java로 하는거지?
생전 처음 안드로이드 플랫폼에서 개발을 시작하는 허우대 대리는 신익한 선배를 찾아가서 조언을 구하게 되었다.

“선배, 차장님께 안드로이드 플랫폼에 기존에 c++로 작성한 엄청난 양의 parser 소스를 마이그레이션 하라는 지시를 받았는데, 나 이거 다 java로 다시 만들어야 하는건가?”

선배는 한심하다는 반응으로 특유의 시니컬한 한숨을 내쉬며 이렇게 얘기한다.

“안드로이드= java의 공식은 아니야. 안드로이드 플랫폼이 어떻게 구성돼있는지 먼저 확인해 보는게 어때?”


안드로이드 플랫폼은 크게 리눅스 커널과, C/C++로 작성한 라이브러리(보통 네이티브 라이브러리라 부른다), 안드로이드 응용프로그램의 뼈대를 제공하는 애플리케이션 프레임워크와 기본 애플리케이션으로 구성돼 있다. 

리눅스 커널은 OS의 기본 기능인 태스크간의 스케줄링과 메모리, 디스크, 네트워크 등의 자원을 사용할 수 있도록 시스템 콜을 제공한다. 이는 리눅스 커널 윗부분에 초록색으로 표시된 네이티브 라이브러리를 통해 응용프로그램이 편하게 사용 할 수 있는 형태로 제공된다.

안드로이드 응용프로그램은 애플리케이션 프레임워크(응용프로그램의 구조를 제공하고 제작에 도움을 주는 Java class로 구성)을 이용하고 역시 Java언어로 작성한다.

이 Java파일을 안드로이드 SDK를 이용해 빌드하면 내부적으로 Java컴파일러와 dex converter를 거쳐 Dalvik VM(안드로이드 응용프로그램을 동작시키는 VM으로 일반적인 Java VM과는 다르다.) 위에서 동작하는 byte 코드로 변환하게 된다.

결론적으로 안드로이드 응용프로그램(Java로 작성)은 애플리케이션 프레임워크(.jar형태의 Java class.)를 이용해 VM에서 제공하는 코어 라이브러리(core library) 기능을 사용하게 되고, 이 코어 라이브러리는 리눅스 커널 위에서 동작하는 다양한 C/C++ 라이브러리(c/c++)를 호출하게 된다. 이 라이브러리는 필요에 따라 리눅스 커널의 system call을 호출하게 된다. 위 그림에서 화살표 방향을 따라 가면서 확인하자.

위 그림에서 안드로이드 런타임 계층과 라이브러리 계층을 연결하는 왼쪽 화살표로 표현된 부분에서 일반적인 함수호출 관계 외에 추가적으로 C/C++ ↔ java 호출 사이의 ‘glue’가 존재한다. 결국 이 덕분에 안드로이드 프레임워크는 자연스럽게 OS의 복잡한 기능을 리눅스 커널로부터 빌려 쓸 수 있는 것이다.

허우대 대리는 결국 C/C++ ↔ Java사이의 ‘glue’를 만들면 기존 소스를 리눅스 상에 포팅 하는 정도의 노력으로 재사용 할 수 있겠다는 결론을 내리고, 곧바로 Java ↔ C/C++ 를 호출 할 수 있는 방법을 찾아보기 시작했다. 


 
Java ↔ C/C++ 사이의 glue는 어떤것을 사용할까?
일반적으로 Java에서 C/C++ 모듈을 사용하기 위해서는 다음과 같은 방법을 사용한다.

1. Java Native Access (JNA)  

자바 코드에서 네이티브 코드와 같은 형태로 네이티브 함수를 호출하는 방식으로 기존 코드의 최소한의 수정으로 응용이 가능해, 최상의 방법으로 생각되나 현재 안드로이드 Dalvik VM에서 지원하지 않는 방법이다.

2. Java Native Interface (JNI)
Java 초창기부터 존재한 Java/Native interface 방법으로 안드로이드 Dalvik VM에서 지원하는 방법으로 실제 안드로이드 플랫폼 소스 여러 부분에서 사용되고 있다. 그러나 이 방식은 raw form을 다루는 형태로 개발해야 하기 때문에 개발 시간이 많이 소요되고 에러를 발생 시킬만한 요소가 많다.

3. Simplified Wrapper and Interface Generator (SWIG)
JNI를 좀 더 쉽게 사용 할수 있도록 해주는 도구로 C/C++ 인터페이스를 정의한 파일을 입력 받아 C/C++ JNI wrapper 코드를 생성한다. 이 방법 역시 Dalvik VM과는 호환성 문제가 존재한다.

Native library(C/C++) 로 개발할 것 인지에 대한 선택 기준

안드로이드에서는 J2SE의 대부분의 기능과, 애플리케이션 프레임워크를 통해 다양한 API 셋을 제공하고, Java를 사용하여 대부분의 응용프로그램을 제작 하는데 부족함이 없도록 배려하고 있다.

신규 개발시 애플리케이션 프레임워크를 통해 제공받을 수 없는 일부 기능(대부분 외부 기기 나 디바이스 종속적인) 부분과 성능이 아주 중요한 일부 기능을 제외하고는 Java를 사용하여 개발하는 것이 프로젝트 장기적인 관점에서는 나은 판단이라 생각된다.

C/C++로 코드로 개발은 유지보수와 디버깅 측면에서 번거롭고 불편한 부분이 있으며 JNI를 이용하여 Java 쪽으로 glue를 만드는 일이 예상하는 것만큼 간단한 일은 아니기 때문이다. 여기서 제시하는 가상의 시나리오 역시 주제를 이끌어 가기 위해 단순화시킨 예일 뿐이지 실제로 기존에 C/C++로 작성한 코드를 마이그레이션 하는 작업이 주어질 경우 얼마나 인터페이스가 단순한지, 기존코드에서 플랫폼 종속적인 부분이 얼마나 있으며 이것을 안드로이드(Linux kernel)에 포팅하기 위해서 얼마나 노력이 필요한지, 혹은 HTTP나 STL등 안드로이드 Native library단에서 충분히 제공하지 않는 기능을 사용한 부분이 있어서 재작업이나 추가 포팅 작업이 필요한지 충분히 고려하여 기존 모듈을 재활용할 것인지, Java코드로 다시 작업할 것인지 결정해야 할 것 이다.

허우대 대리는 Dalvik VM과의 호환성 문제와 보편적으로 사용하는 기술이라는 점을 감안해 JNI를 이용해 프로젝트를 진행하기로 했다.

점심시간에 허우대 대리는 자신이 JNI를 이용하여 기존 parser소스를 거의 재활용 할수있는 방법을 생각해 냈다고 신익한 선배에게 자랑을 늘어놓았다. 이때, 밥을 먹던 신익한 선배는 이런 이야기를 남긴 뒤, 아무일도 없었다는 듯 식사를 계속 했다. 신익한 선배가 남긴 말은 이렇다.

“그럼 안드로이드 시스템에 포함해서 빌드 하겠다는 얘기야? 배포는 어떻게 할건데?”

그렇다. 그림에서 보는 C/C++ 라이브러리는 안드로이드 시스템의 영역으로 허우대 대리는 배포방법에 대해 고려하지 못한 것이다. 안드로이드 에뮬레이터를 실행하고 <화면 1>에서 초록색으로 표시한 C/C++ 라이브러리 파일의 실제 위치를 확인해보자. 아래와 같이 커맨드 창에서 emulator를 실행하거나 이클립스 IDE를 통해 emulator를 실행한다.

에뮬레이터가 정상적으로 실행되면 위와 같이 에뮬레이터 화면을 확인할 수 있는데, 이때 콘솔에서는 <리스트 1>과 같이 adb 명령을 이용하여 shell을 실행한다.

<리스트 1> adb shell을 이용하여 네이티브 라이브러리 확인하기
>adb shell
adb shell 이 실행되면 아래와 같이 library path를 확인해보자
#echo $LD_LIBRARY_PATH
#/system/lib
system/lib가 library path로 잡혀있는 것을 확인하고 /system/lib directory로 이동한다.
#cd /system/lib

<리스트 1>의 내용을 <화면 3>를 통해 확인해보자.

<화면 3>에서는 안드로이드 플랫폼에서 제공하는 네이티브 라이브러리가 위치하는 것을 확인할 수 있다.

허우대 대리가 준비하는 프로젝트의 결과물은 다운로드 가능한 형태로 배포되어야 하는데 /system 디렉토리는 다운로드한 애플리케이션이 설치할 수 있는 영역이 아니기 때문에 /system 디렉토리 대신 /data 디렉토리에서 동작 할 수 있는 방법을 찾아야 한다.

안드로이드 시스템 이미지
안드로이드 시스템 이미지는 리눅스 운영체제에서 파일시스템은 단순히 자료의 저장기능 뿐 아니라 운영체제 동작에 필요한 필수 정보를 포함하고 장치 관리 등의 특별한 용도로도 사용하므로 선택항목이 아니라 필수 항목이다. 시스템 이미지는 안드로이드 프레임워크를 구동하기 위해 필수적인 항목으로 <안드로이드 SDK설치 디렉토리>/platforms/<플랫폼버전>/images/system. img 에서 확인 할 수 있다.

이 이미지파일은 실제 에뮬레이터가 구동될 때 /system directory에 마운트하게 되는데, 이내용은 adb shell을 실행시킨 후 최상위 디렉토리의 init.rc파일에서 확인 할 수 있다.

/system 디렉토리는 임베디드하여 탑재할 기본 응용프로그램을 포함해 안드로이드 프레임워크에 필요한 라이브러리 설정파일 등이 들어있는 영역으로 추가로 다운로드받은 응용프로그램과는 구별된다. 다운로드 받은 응용프로그램은 /data 디렉토리에 위치하게 된다.
 
안드로이드 SDK 설치
앞서 설명된 부분들을 실습하기 위해서는 안드로이드 SDK를 설치하고 버추얼 디바이스를 생성해야 하는데, 이 부분은 이번 컬럼의 주제와는 벗어나므로 아래 URL을 참고해 설치하기 바란다.

http://developer.android.com/sdk/index.html
 
허우대 대리, NDK와 만나다

다음날 배포문제로 고민하던 허우대 대리는 아래의 안드로이드 개발자 웹사이트를 통해 NDK에 대한 내용을 접했다. 
 
http://developer.android.com/sdk/ndk/1.6_r1/index.html

NDK는 C/C++로 작성한 코드로 리눅스 OS 기반의 ARM binary를 생성하기 위한 크로스 툴체인을 제공한다. 여기서 ‘크로스 툴체인’이란 타깃머신과는 다른 환경에서 타깃 머신용으로 바이너리를 생성할 수 있도록 제공되는 툴인데 컴파일러 링커 그리고 기타 컴파일에 필요한 유틸리티를 포함하고 있다.

<리스트 2> Application.mk 파일 

APP_PROJECT_PATH := $(call my-dir)/project
APP_MODULES      := hello-jni

일반적인 x86호환 CPU의 윈도우나 리눅스 PC기반 환경에서 ARM이나 MIPS등 임베디드 머신의 CPU에서 동작하는 바이너리를 컴파일 할 수 있도록 해준다.

그리고 아래와 같은 라이브러리를 사용할 수 있도록 header file을 제공하는데, 이 내용은 앞서 제시된 adb 쉘에서 확인한 /system/lib 디렉토리에 있는 라이브러리의 일부임을 확인할 수 있다.

(1)  libc(C library)
(2)  libm(math library)
(3)  JNI interface
(4)  libz (zip 압축 library)
(5)  liblog(Android logging library)
(6)  OpenGL ES library (3D graphic library, NDK 1.6버전부터)
 
추가적으로 안드로이드 응용프로그램 패키지 파일(.apk)에 native library를 포함할 수 있는 방법을 제공한다. 허우대 대리가 찾던 바로 그 방법이다. 그럼 이제 허우대 대리와 함께 NDK를 설치해 보도록 하자.
 
(1) http://developer.android.com/sdk/ndk/1.6_r1/index.html 에서 윈도우용 패키지를 다운로드 받는다.
(2) 적당한 위치에 (1)에서 다운로드 받은 압축파일을 푼다.
(3) www.cygwin.com에서 최신 cygwin 을 설치한다.
(4) 설치한 Cygwin bash shell을 실행하고 bash shell에서 /cygdrive/<NDK 설치 경로> 로 이동해 ‘Build/host-setup.sh’ 라고 명령을 수행한다.

이처럼 순차적으로 따라하고 나면, <화면 4>와 같이 ‘Host setup complete…’라는 문구가 나타나는데, 이럴 경우 셋업이 성공한 것이다.

이제 설치가 끝났으니 먼저 NDK에 샘플로 제공되는 hello-jni 예제를 빌드해 보자. 빌드시에는 $make APP=hello-jni라고 명령을 수행한다. 재빌드시에는 -B 옵션을 사용하고 실제 build command를 모두 확인하고 싶은 경우 V=1 옵션을 사용한다.

빌드결과로 apps/hello-jni/project/libs/areabi/libhello-jni.so가 생성되었는지 확인하자.

어떻게 NDK에서 제공하는 툴을 이용하여 예제가 빌드 되는지 알고 싶다면, apps/hello-jni/Application.mk 와 sources/ hello-jni/Android.mk 파일을 열어보자.
일반적인 make 파일과 같은 형태이며 빌드를 위한 변수를 설정하는 부분을 확인 할 수 있다. 또한 Application.mk 파일에는 응용프로그램 에서 어떤 네이티브 라이브러리 모듈을 사용할 것인지에 대해 기술해야 한다. 이 부분은 네이티브 라이브러리를 빌드하는 것과는 직접적인 관계가 없으나 이 파일을 참조해 빌드할 타깃 모듈을 결정하기 때문에 필수적으로 작성해야 한다.

<리스트 3> Android.mk 파일
… 중략…
 
LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES   := hello-jni.c
 
include $(BUILD_SHARED_LIBRARY)

Android.mk 파일에는 실제 shared library로 빌드할 c/c++ 파일 목록 과 사용하는 라이브러리 목록, compile/link option 등을 기술해야 하는데, 이때 변수 LOCAL_MODULE 에 모듈이름을 기술하게 된다. 이어 변수 LOCAL_SRC_FILES 에 빌드하려고 하는 소스 파일명을 기술하고, 파일명 사이는 스페이스로 구별한다.

마지막 줄이 shared library, 즉 .so 파일로 빌드 하겠다는 뜻이다. 이렇게 기술하면 자동으로 lib+모듈명+.so 형태의 파일을 빌드하게 된다.

추가로 특정 라이브러리를 사용하기 위해서는 아래와 같이 LOCAL_LDLIBS 변수에 내용을 기술한다. 아래는 안드로이드 로그 라이브러리를 사용하기 위한 예이다. 또한 이에 대한 추가적인 내용은 NDK document 폴더의 ‘ANDROID-MK.TXT’,’APPLICATION-MK.TXT’,’HOWTO.TXT’ 문서를 참고하기 바란다.

LOCAL_LDLIBS     :=-L$(SYSROOT)/usr/lib/ -llog

이제 네이티브 라이브러리 빌드가 끝났으니 네이티브 라이브러리를 사용하는 Java 코드를 살펴보자. <화면 6>과 같이 apps/hello-jni 프로젝트를 이클립스에서 열어보도록 하자. 왼쪽 패키지 익스플로러 창에서 마우스 오른쪽 버튼을 클릭한 뒤 Import 메뉴를 선택한다.

이어 Existing Projects into Workspace를 선택한후 app/hello-jni/ 디렉토리를 선택한다. 이를 실행하면 <화면 7>과 같이 Package Explorer에  HelloJni 프로젝트가 로딩되면 성공한것이다.

이클립스 메뉴에서 Run->Run을 선택하거나 단축키로 Ctrl+F11을 선택하면 에뮬레이터에서 Hello-jni 프로젝트의 결과를 확인할수 있다.

에뮬레이터가 실행되는데 제법 시간이 걸리므로 인내심을 가지고 기다리기 바란다. 에뮬레이터를 동작하기 위해서는 플랫폼 버전에 맞는 AVD 파일을 미리 생성해야 하는데 이 내용은 http://developer.android.com/guide/developing/eclipse-adt.html을 참고하길 바란다.

더불어 이전에 adb shell 명령을 이용한 안드로이드 파일시스템을 살펴보았는데, 이번에는 이클립스 IDE에서 DDMS툴을 이용하여 손쉽게 파일시스템을 확인해보자. 이때 이클립스 툴바 오른편에서 DDMS perspective 버튼을 선택한다. 오른편에 File Explorer 창을 이용하여 data/data/com.example.hellojni 폴더의 내용을 확인할 수 있으며, lib폴더아래에 libhello-jni.so 파일이 있는 것 또한 확인할 수 있다.

<화면 9>에서 보여지듯, 결국 안드로이드 응용프로그램은 system/lib 폴더의 라이브러리 이외에 /data/data/<자신의 package이름>/lib 아래 네이티브 라이브러리를 추가로 참고하는 것을 확인할 수 있다. 
 
네이티브 라이브러리를 사용하는 Java 코드 맛보기 

허우대 대리는 Java코드 에서 어떻게 C로 작성한 네이티브 코드를 참고하는지 궁금해졌다. 이번에는 허우대 대리와 함께 src/com/example/HelloJni.java 코드를 살펴보자

<리스트 4> HelloJni.java 파일
… 중략…
public class HelloJni extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        TextView  tv = new TextView(this);
       tv.setText( stringFromJNI() ); //-- (3) 
        setContentView(tv);
    }
 
public native String  stringFromJNI(); //-- (1) 
 
… 중략…
   static {
        System.loadLibrary("hello-jni"); //-(2) 
    }
}

<리스트 4>는 stringFromJNI 함수를 호출하여 얻은 문자열을 textView에 내용으로 넣어주는 간단한 소스로, 3가지 의미로 이용된다. 

1) native 키워드를 붙혀서 선언부만 작성한다. 네이티브 라이브러리에 구현부가 있음을 알려준다.

2) static {..} 부분은 이 class가 로딩될 때 호출되는 되는데 이때 ‘hello-jni’ 라는 이름의 네이티브 라이브러리를 로딩하라는 의미이다.

3) 네이티브 라이브러리 함수라도 보통의 Java 메소드와 같은 방식으로 사용한다.
 
이어 Source/samples/hello-jni.c 파일을 열어서 네이티브 라이브러리 구현부도 함께 살펴보도록 하자.

<리스트 5> 네이티브 라이브러리 구현부 코드 

Jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                            jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !")
}

<리스트 5>를 보면 함수이름 앞에 ‘Java_com_example_ hellojni_HelloJni_’ 가 붙어 있는데, 이 부분은 JNI 함수이름 규칙에 의해 package명_클래스명이 Java에서 선언한 함수 이름 앞에 붙게 된다. 함수의 내용은 단순히 “Hello from JNI!” 라는 문자열을 Java에서 사용하는 String 타입으로 변환하여 반환하는 내용이다.

 

참고자료 
1. http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html
2. http://developer.android.com/sdk/ndk/1.6_r1/index.html
3. http://www.aton.com/android-native-libraries-for-java-applications/
4. http://www.swig.org/

+ Recent posts