[태그:] java

  • 안드로이드 앱 개발 일지, 3차

    안드로이드 앱 개발기 3

    파일 입출력

    수집된 데이터를 저장할 경우, 파일에 저장하면 된다. 전에 누가 개발한 코드를 보니 쉽게 사용하던데, android developer에서 보니 설명이 어렵게 되어 있다. stack overflow에서 찾아, 되는 코드를 만들 수 있었다.

    android developer에는 internal storage는 아무 제한이 없이 사용하는데, 코드로 만들면 permission 문제인지 파일과 폴더를 만들 수 없다. internal storage의 기본 위치는 /data/…인데, 안드로이드에서 직접 만들어보니 안되었다. 갤럭시 노트2의 경우 권한으로 막아놓은 듯 하다. 이런 설명이 없으니, 하루 삽질을 하게 되었다.

    결국 외부 저장소로 만들기로 했다. sd카드 슬롯이 없는데도 외부 저장소로 저장이 되었다. 꼭 sd카드일 필요는 없어 보인다. stack overflow에 있는 내용을 요약하면..
    First, get a file object

    You’ll need the storage path. For the internal storage, use:

    File path = context.getFilesDir();

    For the external storage (SD card), use:

    File path = context.getExternalFilesDir(null);

    위 부분을 아래와 같이 사용하면 앱별 폴더 이름을 특성화 할 수 있다.

     File directory = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "GapnFlush");
                if (!directory.mkdirs()) {
                    Log.d("file", "Directory was created");
                }

    이렇게 하면 갤럭시 노트의 경우,
    /storage/emulated/0/Android/data/app이름/files/Documents에 GapnFlush란 폴더를 만든다.

    Then create your file object:

    File file = new File(path, "my-file-name.txt");

    Write a string to the file

    FileOutputStream stream = new FileOutputStream(file);
    try {
        stream.write("text-to-write".getBytes());
    } finally {
        stream.close();
    }

    Or with Google Guava

    Files.write("text-to-write", file, "UTF-8");

    앱 실행 전 권한도 설정을 해줘야 된다.

        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    이제 파일은 저장이 되니까, 입맛대로 쓰면 된다..

  • 안드로이드 앱 개발 일지, 2차

    안드로이드 앱 개발기 2

    FSM 모델 수정

    batch 입력에서 에러가 발생하면 초기 상태로 돌아가는데, 그렇게 되면 나중에 데이터를 저장하거나, 초기화 할 경우, 항상 초기 상태에서 전이가 된다. 별로 좋지 않아 보여 아래와 같이 fsm을 수정 했다.

    #stateBatchRec에 한번 들어가면 에러가 발생할 경우, #stateDecision 상태로 들어가게 했다. 그 부분에서 #stateSave나 #stateModi로 전이를 일어나게 할 생각이다. 뒤 부분은 아직 못 그렸지만..

    보완할 사항으로는 #stateBatchRec 상태에서 일정 시간동안 지나면 #stateDecision으로 돌아가야 한다. timer를 쓰려고 보니, 좀 복잡하여, int를 계속 더해서 일정수가 지나면 전이가 일어나게 하려고 했는데, 잘 안된다. 하위 상태인 #stateRec_Gap에서 int를 초기화 시켜야되는데, 하위 state에서 상위 state에 접근할 수 있는 방법이 없어 보인다.

    버튼 두번 입력시 SpeechRecognizer 오류 발생 문데

    인식을 시작 하라고 버튼을 누른 상태에서 다시 버튼을 누르면 에러가 발생한다. 이럴 경우, 적당한 초기화를 해야 하는데, 딱히 없어 보인다. #stateDecision에 들어가면 기존의 SpeechRecognizer를 취소하게금 아래와 같이 했다.

        State stateDecision = new State(s0) {
            @Override
            public State fireInit() {
                Log.d("FSM", "Init>>stateDecision;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateDecision;");
                myHandle.sendEmptyMessage(2);
            }
    

    Main Activity는 아래와 같이 handler 처리를 했다.

    public class MainActivity extends AppCompatActivity {
        Intent i;
        SpeechRecognizer mRecognizer;
        int TvIndex = 0;
        TextView[] TvGap = new TextView[10];
        TextView[] TvFlush = new TextView[10];
        //hsm에서 SpeechListener를 제어하기 위해서.
    
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //음성인식 시작부분..
                if (msg.what == 1)
                    mRecognizer.startListening(i);
                if (msg.what == 2)
                    mRecognizer.cancel();
            }
        };
    

    thread 사용 문제

    주기적으로 이벤트를 감지하기 위해서 thread를 사용했는데, 딱히 필요해 보이지 않는다. 각 이벤트마다 fsm.dispatch형식으로 이벤트를 새로 정의해서 보내는 경우가 대다수다.

  • 안드로이드 앱 개발 일지, 1차

    안드로이드 앱 개발 일지, 1차

    안드로이드 앱 개발기 1

    구글 음성인식 API를 활용한 기록기

    일하는 주변에 필요한 도구를 만들어 쓰기로 했다. 휴대폰에 사용되는 안드로이드 os가 휴대용 PC인데 그 기능을 잘 활용하면 현장의 많은 삽질을 줄일 수 있다. 나의 코딩 등급 및 JAVA에 대한 이해도는 미미하지만, stack overflow와 구글신을 믿어 해보기로 했다. 이 개발이 끝나면 JAVA 사용 방법, HSM(fsm) 적용, 파일 저장 등의 기술이 늘 것 같다.

    옛날에 fsm을 공부하기 위해 퀀텀 프로그래밍을 사서 읽었다. 이 책에 HSM에 대한 소개가 있었다. fsm을 확장하여 hierarchy로 정의한 fsm인데, 그런 방법으로 개발하면 쉽다고 했다. 문제는 이 사람이 C로 코드를 소개를 했다. 이를 JAVA로 옮겨야 되는데, 내 능력으로는 불가능 했다. 3년전에 인터넷을 열심히 뒤진 결과 Alexei Krasnopolski가 자바로 변경한 결과를 찾았다. fsm을 잘 사용하려면 uml을 정확하게 알아야 되는데, 대충 알고 있다. 나중에 문제되는 부분을 따로 찾아 보기로 했다.

    내가 원하는 기능

    아래의 기능을 갖는 앱을 개발하려고 한다.

    1. 구글의 음성인식을 활용하여, 말하는 내용을 텍스트로 변환
    2. layout에 번호를 할당하여 처음부터 끝까지 연속적인 인식
    3. 사용자 입력에 대한 android 판단은 소리로 표시
    4. 연속 작업 도중 틀린 부분은 버튼을 눌러 수정
    5. 수정 작업이 완료되면 다음 연속 작업으로 다시 복귀
    6. 모든 작업이 끝나면 파일로 저장

    아래의 방향으로 접근 하기로 했다.

    1. 구글 음성인식 API 사용
    2. hsm의 구성 및 설정
    3. event 전달 방법
    4. thread 사용 방법
    5. handler
    6. 파일 저장
    7. android studio 사용

    구글 음성 인식은 speech to text라고 하는데, 과거 text to speech의 역버전이다. 한국의 네이버도 이런 기능을 지원한다. 그러나 안드로이드에서 적절하게 사용이 가능한지 모르겠고, 별도 API를 알아야 된다. 게다가 전세계 개발자들이 구글을 많이 사용하여 내가 원하는 답이 많다. 이런 저런 이유로 구글을 사용했다. 안드로이드 내부에서 바로 구현이 가능하다. 샘플 코드는 인터넷에 구했다.

    전에는 eclipse를 안드로이드 개발이 가능하도록 세팅했는데, 요새 무슨 변화가 있었는지 최신 버전은 지원하지 않았다. 테마 관련 에러도 많이 뜨고..구글이 공식앱 개발툴이라고 밀고 있는 android studio를 사용했다. 인터넷에서 다운로드 하여 사용 하면 된다.

    Hierarchy-finite State Machine 정의

    일단 아래 그림과 같이 접근 하기로 했다.

    이 그림이 정확한지 해보지 않고는 정확한 판단을 할 수 없어, 일단 해보고 나중에 다시 보완하기로 했다.

    package com.example.now0930.myapplication;
    
    /**
     * Created by now0930 on 17. 2. 26.
     */
    
    import android.app.Activity;
    import android.os.Handler;
    import android.os.Message;
    import android.provider.ContactsContract;
    import android.speech.SpeechRecognizer;
    import android.util.Log;
    
    import com.example.now0930.myapplication.hsm.Event;
    import com.example.now0930.myapplication.hsm.Hsm;
    import com.example.now0930.myapplication.hsm.State;
    
    /**
     * This class represents a test HSM implementation.
     *
     * @author Alexei Krasnopolski (krasnop@bellsouth.net)
     */
    public class myHsm extends Hsm {
        int myFoo;
        DataGapFlush myGapFlush;
        Handler myHandle;
    
    
        public myHsm(DataGapFlush myDataInst, Handler handle) {
            this.myGapFlush = myDataInst;
            this.myHandle = handle;
        }
    
        public void init() {
            //System.out.print("Top-INIT;");
            Log.d("FSM", "Top-INIT;");
            myFoo = 0;
            super.init();
        }
    
        ;
    
        public State fireInit() {
            Log.d("FSM", "INIT>>s0;");
            return stateStandby;
        }
    
        ;
    
        public State fireEvent(Event e) {
    
            return getParent();
        }
    
        ;
    
        public void enter() {
            Log.d("FSM", "ENTRY>>s0;");
        }
    
        ;
    
        public void exit() {
            Log.d("FSM", "EXIT<<s0;"); } Hsm s0 = this; // This is just alias of current instance State stateStandby = new State(s0) { @Override public State fireInit() { Log.d("FSM", "INIT>>stateStandby;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "ENTRY>>stateStandby;");
            }
    
            @Override
            public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 5:
                        printMessage(e, "stateBatchRec");
                        //myHandle.sendMessage(myHandle.obtainMessage());
                        myHandle.sendEmptyMessage(1);
                        myHsm.this.transition(stateBatchRec);
                        return null;
                    default:
                        break;
                }
                return getParent();
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "EXIT<<stateStandby;"); } }; State stateBatchRec = new State(s0) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec;");
                return stateBatchRec_Gap;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateBatchRec;");
            }
    
            @Override
            public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 6:
                        printMessage(e, "stateStandby");
                        myHsm.this.transition(stateStandby);
                        return null;
                    case 11:
                        printMessage(e, "stateModify");
                        myHsm.this.transition(stateModify);
                        return null;
                    default:
                        break;
    
                }
                return getParent();
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "EXIT<<stateBatchRec;"); } }; State stateBatchRec_Gap = new State(stateBatchRec) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec_Gap;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateBatchRec_Gap;");
                //myHandle.sendEmptyMessage(1);
            }
    
            @Override
    
            public State fireEvent(Event e) {
                //아래 e.getID를 실행하면
                //이벤트 정의시 내부 데이터에 의한 기준으로 하면
                //일정 시점 이후로는 그 동작만 계속됨..
                //내부 이벤트, 이부 이벤트로 분리.
                //내부 이벤트는 그 state에서만 실행되도록 정의
                boolean gapFlag = myGapFlush.checkGapFulled();
                if(gapFlag == true){
                    myHsm.this.transition(stateBatchRec_Flush);
                    return null;
                }
    
                switch (e.getID()) {
                    case 1:
                        printMessage(e, "stateBatchRec_Gap");
                        int i = myGapFlush.getGapIndex();
                        myGapFlush.setGapIthwithN(i, myGapFlush.getTmpWord());
                        myGapFlush.emptyTmpWord();
                        myHsm.this.transition(stateBatchRec_Gap);
                        myHandle.sendEmptyMessage(1);
                        return null;
                    /***
                    case 2:
                        myHsm.this.transition(stateBatchRec_Flush);
                        return null;
                     ***/
                    default:
                        break;
                }
                return getParent();
            }
    
            ;
    
            @Override
            public void exit() {
                Log.d("FSM", "Exit<<stateBatchRec_Gap;"); } State stateBatchRec_Flush = new State(stateBatchRec) { @Override public State fireInit() { Log.d("FSM", "Init>>stateBatchRec_Flush;");
                    return null;
                }
    
                @Override
                public void enter() {
                    Log.d("FSM", "Entry>>stateBatchRec_Flush;");
                }
    
                @Override
                public State fireEvent(Event e) {
                    switch (e.getID()) {
                        case 3:
                            printMessage(e, "stateBatchRec_Flush");
                            int i = myGapFlush.getFlushIndex();
                            myGapFlush.setFlushIthwithN(i, myGapFlush.getTmpWord());
                            myGapFlush.emptyTmpWord();
                            myHsm.this.transition(stateBatchRec_Flush);
                            myHandle.sendEmptyMessage(1);
                            return null;
                        default:
                            break;
                    }
                    return getParent();
    
                }
    
                @Override
                public void exit() {
                    Log.d("FSM", "Exit<<stateBatchRec_Flush;"); } }; }; State stateModify = new State(s0) { @Override public State fireInit() { Log.d("FSM", "Init>>stateModify;");
                return null;
            }
    
            @Override
            public void enter() {
                Log.d("FSM", "Entry>>stateModify;");
            }
    
            @Override
            public State fireEvent(Event e) {
                return null;
            }
    
            @Override
            public void exit() {
                Log.d("FSM", "Exit<<stateModify;"); } }; public static void printMessage(Event e, String stateName) { Log.d("FSM", "Event-" + e.getID() + ">>" + stateName + ";");
        }
    
    };
    

    HSM 내부에 gap과 flush를 같이 관리가 되도록 데이터 형식으로 같이 정의 했다. 핸들러는 MainActivity의 startListener를 제어하기 위한 핸들러이다.
    MainActivity에서 생성자를 만들경우 자동으로 관련 주소를 넘겨오도록 했다.

    State간의 Action은 전이가 있을 때 실행된다. transition과 별개의 action을 실행하려면, state 내부에 넣어주면 될것 같다.

    DataGapFlush Class 정의

    DataGapFlush는 다음과 같이 정의 했다.

    public class DataGapFlush {
        private String[] gap;
        private String[] flush;
        private int gapIndex = 0;
        private int flushIndex = 0;
        private String VIN = "";
        private String tmpSpokenWord = "";
    
        public DataGapFlush() {
            gap = new String[10];
            flush = new String[10];
            //0부터 10까지 "Empty"를 입력..
    
            for (int i = 0; i < 10; i++) {
                gap[i] = "Empty";
                flush[i] = "Empty";
            }
    
            //DEBUG
            for (int i = 0; i < 8; i++) {
                gap[i] = "가";
            }
            //DEBUG
        }
    
        //gap을 0에서 10까지 "Empty"로 들어가 있는지 확인
        //모두 차있으면 true을 return..
        //중간에 비어 있으면 flase를 return
    
        public boolean checkGapFulled() {
            int i;
            for (i = 0; i < 10; i++) {
                if (gap[i] != "Empty") {
                    this.gapIndex = i;
                } else
                    break;
            }
    
            setGapIndex(i);
            if (this.gapIndex == 10)
                return true;
            else
                return false;
        }
    
        public boolean checkFlushFulled() {
            int i;
            for (i = 0; i < 10; i++) { if (flush[i] != "Empty") { this.flushIndex = i; } else break; } setFlushIndex(i); if (this.flushIndex == 10) return true; else return false; } public int getGapIndex() { checkGapFulled(); return this.gapIndex; } public int getFlushIndex() { checkFlushFulled(); return this.flushIndex; } public void setGapIndex(int i) { this.gapIndex = i; } public void setFlushIndex(int i) { this.flushIndex = i; } public boolean setGapIthwithN(int i, String value) { if (i >= 10)
                return false; //failed..
            else {
                gap[i] = value;
            }
            return true; //success
        }
    
        public boolean setFlushIthwithN(int i, String value) {
            if (i >= 10)
                return false; //failed..
            else {
                flush[i] = value;
            }
            return true; //success
        }
    
    
        public void setTmpWord(String words) {
            this.tmpSpokenWord = words;
        }
    
        public String getTmpWord() {
            return this.tmpSpokenWord;
        }
    
        public void emptyTmpWord() {
            this.tmpSpokenWord = "";
        }
    
        public String getData(boolean GAPFLUSH_FLAG, int NthData) {
            String tmp = "";
            if (NthData <= 9) {
                if (GAPFLUSH_FLAG == true) {
                    //GAP을 가져옴..
                    tmp = this.gap[NthData];
                } else
                    //Flush를 가져옴..
                    tmp = this.flush[NthData];
            }
            return tmp;
        }
    
    }
    
    

    구글 음성인식이 숫자로 자동으로 변환되면 좋은데, 텍스트로 입력이 들어온다. 10개까지 저장할 수 있도록 String으로 배열을 잡았다. gap, flush index는 어느 부분에 데이터를 넣을지 결정한는 부분이다. Hsm에서 DataGapFlush를 만들 때, 내부에 Empty로 10개를 만든다. Empty 문자열을 확인 후, 0번부터 입력을 한다.

    Event 정의

    package com.example.now0930.myapplication.hsm;
    
    /**
     * Created by now0930 on 17. 2. 26.
     */
    
    import com.example.now0930.myapplication.DataGapFlush;
    
    import java.util.EventObject;
    import java.util.Objects;
    import java.util.logging.Handler;
    
    public class Event extends EventObject {
        private int id;
        private int gapIndex = 0;
        private int flushIndex = 0;
        private DataGapFlush thisData;
        boolean SPEECHFLAG = false;
    
        private enum ID {JUSTONRESULT, GAPISFULLED, FLUSHISFULLED}
    
        ;
    
        public Event(Object o, DataGapFlush myDataIns, boolean flag) {
            super(o);
            this.thisData = myDataIns;
            this.SPEECHFLAG = flag;   //false이면 음성 입력이 없음..
        }
    
        //버튼을 눌렀을 경우, 이벤트 정의 함수..
        public Event(Object o, DataGapFlush myDataIns, int eventInstance) {
            super(o);
            this.thisData = myDataIns;
            this.id = eventInstance;
        }
    
    
        public Event(Object o, int index) {
            super(o);
            this.id = index;
        }
    
        //event 정의..
        //gap부터 채우고 flush를 채움..
        //gap not full, flush not full,onResult : id 1
        //gap full, flush not full : id 2
        //gap full, flush not full, onResult : id 3
        //gap not full, flush full : id not defined
        //gap full, flush full : id 4
    
    
        //음성인식 버튼을 누를 경우 : id 5
        //startListening 후, 입력이 없을 경우.: id6
        //startListening 후, 에러 발생. : id 6
    
        //버튼 눌름 정의..
        //1번을 눌렀을 때, Gap을 수정할 때. : 11
        //2번을 눌렀을 때, Gap을 수정할 때. : 12
        //3번을 눌렀을 때, Gap을 수정할 때. : 13
        //4번을 눌렀을 때, Gap을 수정할 때. : 14
        //5번을 눌렀을 때, Gap을 수정할 때. : 15
        //6번을 눌렀을 때, Gap을 수정할 때. : 16
        //7번을 눌렀을 때, Gap을 수정할 때. : 17
        //8번을 눌렀을 때, Gap을 수정할 때. : 18
        //9번을 눌렀을 때, Gap을 수정할 때. : 19
        //10번을 눌렀을 때, Gap을 수정할 때. : 20
        //1번을 눌렀을 때, Flush을 수정할 때. : 21
        //2번을 눌렀을 때, Flush을 수정할 때. : 22
        //3번을 눌렀을 때, Flush을 수정할 때. : 23
        //4번을 눌렀을 때, Flush을 수정할 때. : 24
        //5번을 눌렀을 때, Flush을 수정할 때. : 25
        //6번을 눌렀을 때, Flush을 수정할 때. : 26
        //7번을 눌렀을 때, Flush을 수정할 때. : 27
        //8번을 눌렀을 때, Flush을 수정할 때. : 28
        //9번을 눌렀을 때, Flush을 수정할 때. : 29
        //10번을 눌렀을 때, Flush을수정할 때. : 30
    
        public int getID() {
    
            //외부에서 미리 설정한 event id를 아래 부분에서 바꾸지 않도록 설정..
            if(this.id == 0) {
                if (SPEECHFLAG == true) {
                    //gap이 모두 차있음..
                    if (thisData.checkGapFulled() == false && thisData.checkFlushFulled() == false)
                        this.id = 1;
                    else if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == false)
                        this.id = 3;
                }
    
                /***
                 * 아래 부분은 데이터 기준으로 이벤트가 설정됨..
                else if (SPEECHFLAG == false) {
                    if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == false)
                        this.id = 2;
                    else if (thisData.checkGapFulled() == true && thisData.checkFlushFulled() == true)
                        this.id = 4;
                }
                 */
            }
            return this.id;
        }
    }
    
    

    이벤트는 위와 같이 했다. 책에서는 이벤트를 판단하는 함수가 간략해야 된다고 했는데, 내 능력으로는 이런 구현이 어려울 듯 하다. 사용할 모든 이벤트를 넣어 버렸다. 이벤트는 Hsm에서 e.getID() 함수를 call할 경우, 판단이 된다.
    내가 원하는 Event 처리는 다음과 같다.

    1. 음성인식 버튼을 눌렀을 경우 : e.id = 5
    2. 음성인식 후, 결과가 제대로 나왔을 경우 : e.id = 1, 3
    3. 음성인식 후, 결과가 에러가 발생할 경우 : e.id = 6
    4. gap의 데이터 10개를 모두 입력했을 경우 : e.id = 2
    5. flush의 데이터 10개를 모두 입력했을 경우 : e.id = 4

    이벤트는 MainActivity에서 thread로 실행을 했다. 위에서 문데되는 부분이 e.id가 2, 4일 경우이다. gap의 모든 데이터를 채워 버리면 다른 이벤트는 e.getID()를 실행할 때, 인식이 되지 않는다. 위 부분을 처리하기 위해서, 특정 state에서 한번만 일어나도록 했다.

    public State fireEvent(Event e) {
                //아래 e.getID를 실행하면
                //이벤트 정의시 내부 데이터에 의한 기준으로 하면
                //일정 시점 이후로는 그 동작만 계속됨..
                //내부 이벤트, 이부 이벤트로 분리.
                //내부 이벤트는 그 state에서만 실행되도록 정의
                boolean gapFlag = myGapFlush.checkGapFulled();
                if(gapFlag == true){
                    myHsm.this.transition(stateBatchRec_Flush);
                    return null;
                }
    
                switch (e.getID()) {
                    case 1:
                        printMessage(e, "stateBatchRec_Gap");
                        int i = myGapFlush.getGapIndex();
                        myGapFlush.setGapIthwithN(i, myGapFlush.getTmpWord());
                        myGapFlush.emptyTmpWord();
                        myHsm.this.transition(stateBatchRec_Gap);
                        myHandle.sendEmptyMessage(1);
                        return null;
                    /***
                    case 2:
                        myHsm.this.transition(stateBatchRec_Flush);
                        return null;
                     ***/
                    default:
                        break;
                }
                return getParent();
            }
    

    HSM 내부의 이벤트와 외부의 이벤트를 구분해야 할 듯 한데, 딱히 어떻게 할지를 모르겠다.

    Handler 정의 및 Message 전송

    JAVA는 어떤지 모르겠는데, android는 MainActivity내부에 handler를 정의하여 외부에서 메세지를 보내는 구조로 되어 있다. HSM에서 일정한 전이가 일어나면 MainActivity에 있는 SpeechRecognizer로 정의 되어잇는 startListening을 실행하도록 했다.

    MainActivity handler 정의

    public class MainActivity extends AppCompatActivity {
        Intent i;
        SpeechRecognizer mRecognizer;
        int TvIndex = 0;
        TextView[] TvGap = new TextView[10];
        TextView[] TvFlush = new TextView[10];
        //hsm에서 SpeechListener를 제어하기 위해서.
    
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //음성인식 시작부분..
                if (msg.what == 1)
                    mRecognizer.startListening(i);
            }
        };

    Hsm에서 handler 초기화 및 message 전송

    HSM을 만들 떄, MainActvity에서 만든 handler로 초기화 했다. 메세지는 구조는 잘 모르겠으나, 간단한 메세지를 sendEmptyMessage로 보냈다.

    public class myHsm extends Hsm {
        int myFoo;
        DataGapFlush myGapFlush;
        Handler myHandle;
    
    
        public myHsm(DataGapFlush myDataInst, Handler handle) {
            this.myGapFlush = myDataInst;
            this.myHandle = handle;
        }
    
    
    
    public State fireEvent(Event e) {
                switch (e.getID()) {
                    case 5:
                        printMessage(e, "stateBatchRec");
                        //myHandle.sendMessage(myHandle.obtainMessage());
                        myHandle.sendEmptyMessage(1);
                        myHsm.this.transition(stateBatchRec);
                        return null;
                    default:
                        break;
                }
                return getParent();
            }
    
  • 안드로이드 가속도 센서 구현, 칼만필터 적용

    추석때 집에 가니 엄마 구형 노트1이 놀고 있었다. 내가 가진 구형폰도 있었으나, 레고에 붙이기에는 좀 아까운듯하여 이 폰을 붙여보려고 한다.

    일단 내장된 센서의 값을 읽는 부분을 java로 구현했다. 여기에서 대부분 참조했고, 이번엔 좀 쉽게 따라할 수 있었다. 가속도센서값의 raw data는 도저히 그대로 사용할 수 없어 보인다.

    다시 찾아보니, 칼만 필터가 좋다고 하다니 적용해 보았다.

    eclipse에 지원하는 logcat에서 센서값을 파일로 저장 후, 필터 적용 전후 비교를 해 보았다. 전에는 데이터값을 파일로 저장하고 다시 읽었는데, eclipse에서 파일로 저장을 지원하니 엄청 편하다..
    rawdata
    위 그림이 raw data..

    filtereddata
    위 그림이 Kalman 필터를 통과한 그림..
    Kalman 필터가 제대로 적용되는 듯 하다.

  • Java 에서 Interface를 사용하여 Callback 구현하기

    스크랩 : http://blog.saltfactory.net/java/implement-java-callback.html

    서론

    프로그래밍에서 꽤 유용한 기능들이 있는데 그중에서 하나가 바로 Callback 이라는 것이다. Callback은 Windows 개발자라면 익히들 알고 있지만 Java 개발자라면 어쩌면 낯선 단어일수도 있다. 하지만 Java 개발자들에게 Listner와 비슷한거라고 하면 대략적인 Callback의 의미를 상상할 수 있을거라 예상된다. 보통 Callback과 Listener는 어떠한 일을 처리하기 위해서 프로세스가 진행하는 도중에 다른 이벤트 처리에 사용하기 때문이다. 하지만 은밀히 말하면 이 두가지는 디자인 패턴(Pattern)이 다르다. Callback은 Command Pattern을 따르고 있고 Listener는 Observer Pattern을 따르고 있기 때문이다. 이 두가지의 차이점은 나중에 다른 포스팅에서 Listener를 설명하면서 다시 한번 자세히 언급하겠다.

    일반적으로 프로그래밍에서 Callback 이라는 용어를 다음과 같이 이야기 한다.

    호출자(Caller)가 피호출자(Callee)를 호출하는 것이 아니라 피호출자(Callee)가 호출자(Caller)를 호출하는 것을 말한다.

    (참조 http://cafe.naver.com/devctrl.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=1727)

    위키에서는 다음과 같이 정의하고 있다.

    프로그래밍에서 콜백(callback)은 다른 코드의 인수로서 넘겨받는 서브루틴이다. 이를 통해 높은 수준의 층에 정의된 서브루틴(또는 함수)을 낮은 수준의 추상화층이 호출할 수 있게 된다. 일반적으로 먼저 높은 수준의 코드가 낮은 수준의 코드에 있는 함수를 호출할 때, 다른 함수의 포인터나 핸들을 넘겨준다. 낮은 수준의 함수를 실행하는 동안에 그 넘겨받은 함수를 적당히 회수, 호출하고, 부분 작업을 실행하는 경우도 있다. 다른 방식으로는 낮은 수준의 함수는 넘겨받은 함수를 ‘핸들러’로서 등록하고, 낮은 수준의 층에서 비동기적으로(어떠한 반응의 일부로서) 다음에 호출하는데 사용한다. 콜백은 폴리모피즘과 제네릭프로그래밍의 단순화된 대체 수법이며, 어떤 함수의 정확한 동작은 그 낮은 수준의 함수에 넘겨주는 함수 포인터(핸들러)에 의해 바뀐다. 이것은 코드 재사용을 하는 매우 강력한 기법이라고 말할 수 있다.

    위의 정의에서 보듯 callback은 포인터나 핸들러를 넘겨줘서 피호출자(Callee)가 호출자(Caller)를 호출하는 기법으로 코드 재상용이 가능하고, 비동기적으로 처리할 수 있으며 함수를 추상화 할 수 있기 때문에 UI나 비동기 처리 시스템에서 callback 기법을 많이 사용한다.

    Android에서 Callback

    왜 Java에서 Callback을 포스팅하는가 하면 바로 Android 앱을 개발할 때 Fragment를 테스트하기 위한 샘플 코드를 만들게 되면 Activity와 Fragment가 바로 Callback을 사용하고 있기 때문이다. 아래는 이클립스에서 Fragment를 이용한 예제를 샘플로 만들면 만들어지는 코드이다.

    package net.saltfactory.tutorial;
    
    import net.saltfactory.tutorial.dummy.DummyContent;
    
    import android.R;
    import android.app.Activity;
    import android.os.Bundle;
    import android.support.v4.app.ListFragment;
    import android.view.View;
    import android.widget.ArrayAdapter;
    import android.widget.ListView;
    
    
    public class ItemListFragment extends ListFragment {
    
        private static final String STATE_ACTIVATED_POSITION = "activated_position";
    
        private Callbacks mCallbacks = sDummyCallbacks;
        private int mActivatedPosition = ListView.INVALID_POSITION;
    
        public interface Callbacks {
    
            public void onItemSelected(String id);
        }
    
        private static Callbacks sDummyCallbacks = new Callbacks() {
            @Override
            public void onItemSelected(String id) {
            }
        };
    
        public ItemListFragment() {
        }
    
       ... 생략 ...
    
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            if (!(activity instanceof Callbacks)) {
                throw new IllegalStateException("Activity must implement fragment's callbacks.");
            }
    
            mCallbacks = (Callbacks) activity;
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            mCallbacks = sDummyCallbacks;
        }
    
        @Override
        public void onListItemClick(ListView listView, View view, int position, long id) {
            super.onListItemClick(listView, view, position, id);
            mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
        }
    
        ... 생략

    위 코드를 자세히 살펴보면 Callback을 사용하기 위해서 Java의 Interface를 사용한 것을 확인 할 수 있다.

    Interface를 사용하여 Callback 구현

    Java에서는 Callback을 사용하기 위해서 interface를 사용한다. 좀더 이해를 돕기 위해서 다음 코드를 살펴보자.

    처음으로 살펴볼 것은 Callback을 사용할 수 있는 Interface를 만드는 것이다. 그리고 그 안에는 callbackMethod를 추가한다.

    /**
     * filename : CallbackEvent.java
     *
     */
    package net.saltfactory.tutorial;
    
    public interface CallbackEvent {
    	public void callbackMethod();
    }

    다음은 Callback을 외부에서 Callback method를 등록할 수 있는 EventRegistration 을 만든다. 이때 생성자에서 Callback으로 구현된 객체를 외부에서 전달 받아서 EventRegistration의 doWork() 메소드에서 외부에서 정의한 callbackMethod를 실행하게 한다.

    /**
     * filename : EventRegistration.java
     *
     */
    
    package net.saltfactory.tutorial;
    
    public class EventRegistration {
    	private CallbackEvent callbackEvent;
    
    	public EventRegistration(CallbackEvent event){
    		callbackEvent = event;
    	}
    
    	public void doWork(){
    		callbackEvent.callbackMethod();
    	}
    }

    다음은 Main에서 호출자(Caller)와 피호출자(Callee)를 만들어서 콜백을 테스트한다. 아래와 같이 호출자(caller)에 구현된 callbackMethod를 등록해서 피호출자(callee)가 호출자(caller)에 구현된 callbackMethod를 실행할 수 있게 되었다.

    /**
     * filename :EventApplication.java
     *  
     */
    
    package net.saltfactory.tutorial;
    
    public class EventApplication {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		CallbackEvent callbackEvent = new CallbackEvent(){
    
    			@Override
    			public void callbackMethod() {
    				// TODO Auto-generated method stub
    				System.out.println("call callback method from callee");
    			}
    
    		};
    
    		EventRegistration eventRegistration = new EventRegistration(callbackEvent);
    		eventRegistration.doWork();
    	}
    
    }

    이 코드를 실행하면 다음과 같은 결과를 확인 할 수 있다.

    이 코드를 유사하게 Android에서는 Activity와 Fragment에 Callback을 사용하고 있는데 이는 Fragment는 반드시 Activity를 가져야하고 Fragment는 Activity의 메소드를 비동기적으로 요청해야하기 때문이다. 이와 같은 상황을 콜백메소드를 이용해서 비동기적인 문제를 해결하고 코드를 재사용할 수 있게 Java의 Interface를 사용해서 Callback을 구현한 것이다.

    결론

    Callback은 호출자(Caller)에서 구현한 메소드를 피호출자(Callee)가 호출해서 사용할 수 있다. 이렇게 외부에서 메소드를 구현화 시키기 때문에 코드의 재사용성이 높아진다. 그리고 Callback는 피호출자(Callee)가 호출자(Caller)에게 비동기적으로 메세지를 보내어서 데이터처리를 비동기 적으로 처리할 수 있는 장점을 가진다. 자바에서 이러한 Callback 구현은 Java의 Interface의 특징을 이용하여 구현할 수 있다.

    참고

    1. http://blog.danieldee.com/2009/06/callback-vs-listener.html
    2. http://cafe.naver.com/devctrl.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=1727
    3. http://ko.wikipedia.org/wiki/콜백
    4. http://www.javaworld.com/javatips/jw-javatip10.html