기존에는 그냥 연습용 앱이어서 해상도 별로 이미지를 생각하지 않고 drawable 에 다 때려 넣었다.

그러다보니 여러명에서 같이 하는 앱개발의 경우에, 각기 다른 Android Device 를 가지고 있어서, OutOfMemory  error 가 나는 경우가 많았다.

그래서 찾아봤는데 아래 사이트에서 각 해상도별 이미지를 생성 해준다.

https://romannurik.github.io/AndroidAssetStudio/nine-patches.html#&sourceDensity=320&name=example

 

Android Asset Studio - Simple nine-patch generator

Drag or select a source graphic to get started.

romannurik.github.io

 

생성된 이미지를 아래와 같이 적용해주기만 하면 된다.

splash_logo.png

 

그리고 불러다 쓸때는 기존에 @drawble/splash_logo 가 아닌 @mipmap/splash_logo 라고 해주면, 알아서 돌아가는 device 에 맞게 이미지를 로드 해준다. 

<ImageView
    android:id="@+id/splash_img"
    android:layout_width="0dp"
    android:layout_height="290dp"
    android:scaleType="fitCenter"
    android:contentDescription="@string/contentDescription_img"
    app:layout_constraintWidth_percent="0.7"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.5"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.276"
    app:srcCompat="@mipmap/splash_logo" />

 

굿굿 

1. File > New > Project from Version Control... 선택

 

2. Clone 하고자 하는 repository 의  URL 입력

waitpid 함수는 wait 함수처럼 자식 프로세스를 기다릴때 사용하는 함수입니다. 즉, 자식 프로세스의 종료상태를 회수할 때 사용합니다.

하지만 waitpid 함수는 자식 프로세스가 종료될 때 까지 차단되는 것을 원하지 않을 경우, 옵션을 사용하여 차단을 방지할 수 있습니다.

그리고 기다릴 자식 프로세스를 좀더 상세히 지정할 수 있습니다.


#include <sys/wait.h>

 pid_t waitpid(pid_t pid, int *statloc , int options);

 성공 : 프로세스 ID 반환

 오류 : -1 



waitpid 함수의 첫번째 인자에 대해서 알아 보겠습니다.

 wiatpid 함수의 첫 번째 인자

 의미

  pid가 -1 일 경우  (pid ==  -1)

   임의의 자식 프로세스를 기다림

  pid가 0 보다 클 경우 (pid > 0)

   프로세스 ID가 pid인 자식 프로세스를 기다림

  pid가 -1 보다 작을 경우 (pid < -1)

  프로세스 그룹 ID가 pid의 절댓값과 같은 자식 프로세스를 기다림

 pid가 0일 경우 (pid == 0)

 waitpid를 호출한 프로세스의 프로세스 그룹 PID와 같은 프로세스 그룹 ID를 가진 프로세스를 기다림 

waitpid 함수의 오류(-1)는 지정한 pid의 프로세스 또는 프로세스 그룹이 없는 경우에 발생하며 그리고 pid가 자식 프로세스가 아닐 때 발생합니다.


두번째 인자에 대해서 알아 보겠습니다.

 

 waitpid 함수 반환 값

 두 번째 인자 인 statloc 값

 자식 프로세스가 정상적으로 종료

 프로세스 ID

 - WIFEXITED(statloc) 매크로가 true를 반환

 - 하위 8비트를 참조하여 자식 프로세스가 exit, _exit, _Exit에 넘겨준 인자값을 얻을 수 있음, WEXITSTATUS(statloc)

  자식 프로세스가 비정상적으로 종료

 프로세스 ID

 - WIFSIGNALED(statloc) 매크로가 true를 반환

 - 비정상 종료 이유를 WTERMSIG(statloc) 매크로를 사용하여 구할 수 있음

 waitpid 함수 오류  -1

 - ECHILD : 호출자의 자식 프로세스가 없는 경우

 - EINTR : 시스템 콜이 인터럽트 되었을 때


세번째 인자에 대해서 알아 보겠습니다.

 세 번째 인자로 사용가능한 상수

 의미 

 WCONTINUED

 중단 되었다가 재개된 자식 프로세스의 상태를 받음

 WNOHANG 

 기다리는 PID가 종료되지 않아서 즉시 종료 상태를 회수 할 수 없는 상황에서 호출자는 차단되지 않고 반환값으로 0을 받음

 WUNTRACED 

 중단된 자식 프로세스의 상태를 받음


아래 예제는 세 번째 인자에 아무것도 주지 않았을 때 wait 함수와 동일한 동작하는 것을 확인해 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,0); // 세번째 인자에 0을 넣었으므로 wait 함수와 동일한 동작을 함 printf("부모 종료 %d %d %d\n",ret,WIFEXITED(status),WEXITSTATUS(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }


실행 결과를 보겠습니다.

부모 PID : 13444, pid : 13445

자식 시작 PID : 13445

자식 종료

부모 종료 13445 1 0 

부모 프로세스는 3초 동안 동작하고 자식 프로세스는 8초 동안 동작하는 예제 코드입니다. 하지만 결과는 자식 프로세스가 먼저 종료되고 부모 프로세스가 종료되었습니다.

waitpid 함수는 세번째 인자로 0을 주었기 때문에 자식 프로세스가 종료될 때 까지 blocking 되었기 때문입니다.

WIFEXITED 매크로는 1(true)를 리턴하였으며 WEXITSTATUS는 자식 프로세스가 0을 리턴 한 것을 알려주고 있습니다.


다음 예제는 세 번째 인자에 WNOHANG 옵션을 넣어보도록 하겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(childPid,&status,WNOHANG); // WNOHANG 옵션을 사용하면 자식 프로세스가 종료되지 않았을 경우 blocking 되지 않고 즉시 0값을 리턴합니다. printf("부모 종료 %d %d\n",ret,WIFEXITED(status)); exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 확인해 보겠습니다.

부모 PID : 14289, pid : 14290

자식 시작 PID : 14290

부모 종료 0 0 127

자식 종료 

waitpid 함수는 0을 리턴하였으며 WIFEXITED 매크로도 false를 리턴하였습니다. WNOHANG 옵션을 사용하였기 때문에 자식 프로세스가 종료될 때 까지 기다리지 않고 waitpid 함수가 바로 0값을 리턴하였기 때문입니다.


마지막으로 존재하지 않는 자식 프로세스의 PID를 waitpid 첫 번째 인자로 넣어 보겠습니다.

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/error.h> #include <sys/string.h> int main() { pid_t childPid; int status; childPid = fork(); if(childPid > 0) { // 부모 프로세스 int ret; printf("부모 PID : %ld, pid : %d\n",(long)getpid(), childPid); sleep(3); ret = waitpid(99999,&status,WNOHANG); // pid로 99999(존재하지 않는 프로세스 ID) printf("부모 종료 %d %d %s\n",ret,errno,strerror(errno)); // 오류가 발생하였을 경우 errno값을 확인 exit(0); } else if(childPid == 0){ // 자식 코드 printf("자식 시작 PID : %ld\n", (long)getpid()); sleep(8); printf("자식 종료\n"); exit(0); } else { // fork 실패 perror("fork Fail! \n"); return -1; } return 0; }

실행 결과를 보도록 하겠습니다.

부모 PID : 17859, pid : 17860

자식 시작 PID : 17860

부모 종료 -1 10 No child processes

자식 종료 

waitpid 함수는 -1 오류 값을 반환하였습니다. 그리고 에러 넘버(errno) 10, 즉 NO child processes라는 에러를 출력하였습니다. 

99999라는 자식 프로세스가 존재하지 않는다는 의미입니다. 그 이유는 waitpid 함수의 첫 번째 인자로 존재하지 않는 99999 PID값을 입력하였기 때문입니다.


지금까지 wait 함수보다 좀더 다양한 기능을 제공하는 waitpid 함수에 대해서 알아 보았습니다.

오늘은 근거리 무선 통신기술인 NFC(Near Field Communication) 에 대해 알아보겠습니다.

NFC 는 13.56MHz 주파수 대역을 사용해 데이터를 주고 받는 근거리 무선 통신기술 중의 한 종류입니다.

이렇게 이야기하면 어려운 개념 같지만, 실제로 우리는 스마트폰에서 NFC 기술을 자주 접하고 있습니다.

NFC 를 이용해서 스마트폰끼리 간단한 Text 나 URL 정보등을 교환할 수 있고, NFC 태그에 스마트폰을 접촉하면 저장되어 있는 정보들을 읽어들일 수 있습니다.

그래서 요즘에는 명함에도 NFC 기능이 탑재되어, 간단히 태그만 하더라도 스마트폰으로 그 명함에 적혀있는 이름이나 핸드폰 번호를 간단하게 읽어들일 수 있습니다.

무선통신 종류 별로 Speed 와 Range 를 살펴보면, NFC 의 경우 10cm 이하의 거리에서 굉장히 느린 속도로 데이터를 주고 받을 수 있는 프로토콜이라고 볼 수 있습니다.

그렇기 때문에 간단한 Text 만을 전송하는데 주로 사용되는데,  예를들어 카드번호/전화번호/특정 URL Link 등을 전송하는데 주로 사용 됩니다.

그에 대비해서 Bluetooth 의 경우는 30m 이하. 즉, NFC 보다는 비교적 먼 거리에서의 통신이 가능 합니다.

데이터 전송 속도도 느린 편에 속하고 있지만, NFC 보다는 2배 가까이 빠른 속도를 가집니다.  저화질의 사진 정도는 전송할 수 있는 수준입니다.

우리가 비교적 많이 사용하고 있는 Wi-Fi 의 경우 최대 100Mbs 정도의 속도로 Multimedia 컨텐츠도 전송 할 수 있는 수준입니다.  위 표에 표기된 무선통신 프로토콜 중에는 WiFi 의 속도가 가장 빠른 것으로 확인 되네요.

 

NFC 는 굉장히 다양한 분야에 활용되고 있습니다. 제일 많이 사용되는 분야가 '결제' 시스템인데,

스마트폰에 기본으로 탑재되어있는 NFC 기능을 이용하여 교통카드/신용카드/각종 페이 간편결제 등으로 활용할 수 있습니다.

 

LG전자에서 2013년에 출시한 '트롬' 세탁기에도 NFC 기능이 탑재되었는데,

예를들어 사용자가 스마트폰 앱에서 원하는 세탁 코스를 선택한 후 스마트폰을 세탁기의 NFC 태그에 접촉 하게 되면

즉시 해당 코스가 세탁기에 저장되어 제품에 탑재된 기본 12가지 세탁코스 외에도 다양한 세탁코스를 사용할 수 있습니다.


삼성전자에서 나온 프린터에도 NFC 기능이 탑재되어

별도로 컴퓨터를 킬 필요 없이, 스마트폰에 저장된 문서를 프린터에 부탁된 NFC 태그에 터치만 해도 바로 프린트가 된다고 합니다.


최근 평창동계올림픽에서도 NFC 방식의 결제 시스템이 도입되었는데,

스티커나 배지, 장갑의 형태로 카드나 지갑 없이도 간단히 몸에 부착해 결제가 가능한 것이 특징입니다.

스티커의 경우 3,5,10,20 만원이 충전된 형태로, 배지와 장갑은 3,5 만원이 충전된 형태로 구매할 수 있었으며,

간단한 Tag 만으로 결제가 가능 하도록 NFC 기능이 도입되었습니다.


NFC 스티커는 가격도 저렴하여 400원 정도로 인터넷에서 쉽게 구매할 수 있기 때문에

여러 기능들을 실현해 보기 쉬운 방법 중 하나인 것 같습니다.

 

다음 포스팅에서는 실제 Android 에서 NFC Tag 를 Read 하고 Write 하는 방법에 대해 알아보도록 하겠습니다.

 

앞선 포스팅에서는 Internal/External 저장소에 사용자의 데이터를 저장하는 방법에 대해 살펴 보았습니다.

오늘은 데이터베이스를 활용하여 원하는 데이터를 저장하는 방법에 대해 알아보겠습니다.

Intenrnal/External 저장소에 저장하는 것 보다 데이터베이스를 이용하여 저장할 때는, 동일한 형태의 데이터를 저장할 수 있다는 점에서 더 편리합니다.

회원정보를 저장할 경우가 대표적인 예인데, 이 경우 회원의 이름, 성별, 전화번호, 주소 등등이 각각 DB의 속성(Attribute) 가 될 것이고,

실제 들어가는 데이터 (김태희, 여, 010-0000-0000, 경기도.. ) 들이 값(Value) 이 될 것입니다.

 

1. SQLiteOpenHelper 를 이용한 DB 생성

SQLiteOpenHelper Class 는 Database 를 생성하고 해당 Database 의 Version 을 관리합니다.

해당 Class 를 사용하기 위해서 Database가 처음 생성될 때 불리는 onCreate(..) 와 Database 가 Upgrade 될 경우에 불리는 onUpgrade(..) callback method 를 구현해 주어야 하며, 필요에 따라 onOpen(..) 함수와 onDowngrade(..) 함수도 구현하여 사용할 수 있습니다.

저는 SQLiteOpenHelper를 상속 받은 MySQLiteOpenHelper 를 생성해서 간단하게 onCreate/onUpgrade 만 구현해 주었습니다.

 

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

    private final String TAG = "MySQLiteOpenHelper";

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        String sql = "create table student (_id integer primary key autoincrement, name text, age integer, address text)";
        sqLiteDatabase.execSQL(sql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        String sql="drop table if exists student";
        sqLiteDatabase.execSQL(sql);
        onCreate(sqLiteDatabase);
    }
}

 

onCreate 에서는 Database를 생성해 주었고, 각 Field 는 name, age, address 로 구성될 수 있도록 하였습니다.

"create table student (_id integer primary key autoincrement, name text, age integer, address text)";

위 Query 로 DB생성 후 실제 만들어진 DB 를 열어보면 다음과 같이 data 가 저장됩니다.

 

그리고 onUpgrade 함수에는 현재 생성되어있는 Database 를 지우고 onCreate 를 통해 다시 생성될 수 있도록만 구현을 해 두었습니다.

 

2. DB에 데이터 삽입 및 삭제

DB에 데이터 삽입/삭제를 위해 MyDBHandler 클래스를 생성하였습니다.

MyDBHandler 클래스에서는 DBActivity 로 부터 사용자 정보를 받아 MySQLiteOpenHelper 로 전달 합니다.

데이터를 저장 할 때 ContentValues 를 이용했는데, ContentValues 는 Data 를 Key와 Value의 Set 으로 저장할 수 있습니다.

그래서 Database 에 값을 줄때 "name, 홍길동" ,"age, 24" 등과 같이 Key 와 Value 의 형태로 쉽게 전달 가능합니다.

 

public class MyDBHandler {

    private final String TAG = "MyDBHandler";

    SQLiteOpenHelper mHelper = null;
    SQLiteDatabase mDB = null;

    public MyDBHandler(Context context, String name) {
        mHelper = new MySQLiteOpenHelper(context, name, null, 1);
    }

    public static MyDBHandler open(Context context, String name) {
        return new MyDBHandler(context, name);
    }

    public Cursor select()
    {
        mDB = mHelper.getReadableDatabase();
        Cursor c = mDB.query("student", null, null, null, null, null, null);
        return c;
    }

    public void insert(String name, int age, String address) {

        Log.d(TAG, "insert");

        mDB = mHelper.getWritableDatabase();

        ContentValues value = new ContentValues();
        value.put("name", name);
        value.put("age", age);
        value.put("address", address);

        mDB.insert("student", null, value);

    }

    public void delete(String name)
    {
        Log.d(TAG, "delete");
        mDB = mHelper.getWritableDatabase();
        mDB.delete("student", "name=?", new String[]{name});
    }

    public void close() {
        mHelper.close();
    }
}

select 함수는 Database 의 전체 내용을 return 해주고, insert/delete 함수는 각각 Database에 데이터를 추가/삭제 시 호출 됩니다.

delete 의 경우 name 을 인자로 받아서 이름이 동일 하면 해당 row 를 모두 삭제하도록 하였습니다.

 

3. 동작 확인

기존에 작성하였던 MainActivity 에 Database 버튼을 하나 추가한다음에, 이 버튼을 눌렀을 때 Activity 를 전환하여 Database 의 내용을 뿌려주도록 하였습니다.

가장 상단에는 Database 에 내용을 추가할 수 있도록 Editbox 를 3개 두었고, 각각 이름/나이/주소 정보를 저장할 수 있도록 하였습니다.

Update Database 를 누르게 되면 Editbox 에 추가한 내용이 Database 에 업데이트 되고 리스트의 내용이 업데이트 됩니다.

리스트의 각 항목을 롱클릭 하게 되면 해당 Field 가 삭제 됩니다. ^^

    

여기까지 Database 를 이용한 아주 간단한 사용자 데이터 저장에 대해서 살펴보았습니다.

모든 소스는 https://github.com/bettercho/MyGithub/tree/master/storeuserdata 를 참고하시면 됩니다. 

 

+ Recent posts