2019년 3월 8일 금요일

Room 아키텍처 예제

- 아키텍처 구조


- 구글 코드랩 예제 :
https://codelabs.developers.google.com/codelabs/android-room-with-a-view/index.html?index=..%2F..index#0




1. 기본 구성
- dependencies 구성
def room_version = "2.1.0-alpha04"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

def lifecycle_version = "2.0.0"
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

> 라이프사이클
https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ko#declaring_dependencies
> Room
https://developer.android.com/jetpack/androidx/releases/room?hl=ko



- Entity
@Entity(tableName = "user_table")
public class User {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    private int id;

    @NonNull
    @ColumnInfo(name = "userName")
    private String userName;

    @NonNull
    @ColumnInfo(name = "age")
    private int age;

}

> autoGenerate를 적용하면 insert 과정에서 id 값이 자동으로 증가한다.



- Dao
@Dao
public interface UserDao {
    @Insert
    long insert(User user);

    @Update
    int update(User user);

    @Query("DELETE FROM user_table")
    int deleteAll();

    @Query("DELETE FROM user_table WHERE id = :id")
    int deleteUser(int id);

    @Query("SELECT * from user_table ORDER BY userName ASC")
    LiveData<List<User>> getAllUsers();

}

> LiveData를 사용하지 않을 경우
    @Query("SELECT * from user_table ORDER BY userName ASC")
    List<User> getAllUsers();



- Database
@Database(entities = {User.class}, version = 1)
public abstract class UserRoomDatabase extends RoomDatabase {
    public abstract UserDao userDao();

    private static volatile UserRoomDatabase INSTANCE;

    static UserRoomDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (UserRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(), UserRoomDatabase.class, "user_database").build();
                }
            }
        }
        return INSTANCE;
    }
}


- 설명
androidx.room:room-compiler가 generatedJava 디렉토리의 소스코드를 자동 생성한다.

SQLiteOpenHelper를 사용했을 때 보다 더 생산성이 좋다.
링크 : 






2. 안드로이드 앱 아키텍처 적용



Room 아키텍처는 안드로이드 앱 아키텍처의 DB 연동 구성요소이다.


- Repository
class UserRepository {
    private static final String TAG = UserRepository.class.getSimpleName();

    private final UserDao userDao;
    private final LiveData<List<User>> allUsers;

    UserRepository(Application application) {
        UserRoomDatabase db = UserRoomDatabase.getDatabase(application);
        userDao = db.userDao();
        allUsers = userDao.getAllUsers();
    }

    public void insert(User user) {
        new AsyncTask<User, Void, Long>() {
            @Override
            protected Long doInBackground(User... users) {
                if (userDao == null)
                    return -1L;
                return userDao.insert(users[0]);
            }

            @Override
            protected void onPostExecute(Long aLong) {
                super.onPostExecute(aLong);
                Log.d(TAG, "insert : " + aLong);
            }
        }.execute(user);
    }

    public void update(User user) {
        new AsyncTask<User, Void, Integer>() {
            @Override
            protected Integer doInBackground(User... users) {
                if (userDao == null)
                    return -1;
                return userDao.update(users[0]);
            }

            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                Log.d(TAG, "update : " + integer);
            }
        }.execute(user);
    }

    public void deleteAll() {
        new AsyncTask<Void, Void, Integer>() {
            @Override
            protected Integer doInBackground(Void... voids) {
                if (userDao == null)
                    return -1;
                return userDao.deleteAll();
            }

            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                Log.d(TAG, "deleteAll : " + integer);
            }
        }.execute();
    }

    public void deleteUser(int id) {
        new AsyncTask<Integer, Void, Integer>() {
            @Override
            protected Integer doInBackground(Integer... integers) {
                if (userDao == null)
                    return -1;
                return userDao.deleteUser(integers[0]);
            }

            @Override
            protected void onPostExecute(Integer integer) {
                super.onPostExecute(integer);
                Log.d(TAG, "deleteUser : " + integer);
            }
        }.execute(id);
    }

    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }
}

> 메인쓰레드에서 DB 연동을 사용하지 않아야 한다.
> LiveData를 사용하지 않는 곳은 AsyncTask를 적용한다.



- AndroidViewModel 
public class UserViewModel extends AndroidViewModel {
    private static final String TAG = UserViewModel.class.getSimpleName();

    private final UserRepository repository;
    private final LiveData<List<User>> allUsers;

    public UserViewModel(Application application) {
        super(application);
        repository = new UserRepository(application);
        allUsers = repository.getAllUsers();
    }

    public void insert(User user) {
        repository.insert(user);
    }

    public void update(User user) {
        repository.update(user);
    }

    public void deleteAll() {
        repository.deleteAll();
    }

    public void deleteUser(int id) {
        repository.deleteUser(id);
    }

    public LiveData<List<User>> getAllUsers() {
        return allUsers;
    }
}

> AndroidViewModel를 상속하고 대부분의 메소드들은 델리게이트로 구성한다.



- ViewModel 구현
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.getAllUsers().observe(this, new Observer<List<User>>() {
@Override
public void onChanged(List<User> users) {

}
});

> 라이프사이클을 이용해서 ViewModel을 통해 DB 연동을 한다.
> 라이프 사이클 :
https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ko#declaring_dependencies



3. 샘플코드
소스코드
APK




- Room의 구성을 더 강력하게 만드는 구성은 Room + ViewModel + Paging 조합이다.
추가적으로 Data Binding을 함께 구성하기도 하지만 반드시 필요한 경우만 사용해하 한다.

- Room을 사용하는 주요 목적은 안드로이드 앱 아키텍처 구조 적용과 SQLiteOpenHelper의 불편함을 줄이기 위함이다.

- 안드로이드와 SQLite의 관계를 이해하고 Room 아키텍처를 사용하도록 하자.

동기화 예제 - synchronized

- 자바 동기화(synchronized) 기본 예


1. 문제가 되는 코드
- 기본 코드
public class SimpleSynchronized {

int balance = 100000000;
int count1 = 0;
int count2 = 0;

public int getBalance() {
return balance;
}

public void addBalance(int balance) {
this.balance += balance;
}

public static void main(String[] args) {
final SimpleSynchronized simpleSynchronized = new SimpleSynchronized();
new Thread() {

@Override
public void run() {
while (simpleSynchronized.getBalance() >= 1000) {
simpleSynchronized.addBalance(-1000);
simpleSynchronized.count1++;
}

System.out.println("Thread 1 : " + simpleSynchronized.count1);
}

}.start();

new Thread() {

@Override
public void run() {
while (simpleSynchronized.getBalance() >= 1000) {
simpleSynchronized.addBalance(-1000);
simpleSynchronized.count2++;
}
System.out.println("Thread 2 : " + simpleSynchronized.count2);
}

}.start();

new Thread() {

@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("balance : " + simpleSynchronized.balance);
System.out.println("count total : " + (simpleSynchronized.count1 + simpleSynchronized.count2));
}

}.start();
}
}

- 결과
Thread 2 : 13312
Thread 1 : 89300
balance : -1000
count total : 102612

> 쓰레드1과 쓰레드2가 동일한 시간에 balance 값이 1000 보다 큰 경우만 -1000을 하면 결과적으로 1000 > 0 > -1000이 되는 문제가 발생한다.
> 루프 카운트도 100000000 / 1000 번을 하는 것이 아닌 것이 확인된다.


2. synchronized 적용
- 기본코드
public class SimpleSynchronized {

int balance = 100000000;
int count1 = 0;
int count2 = 0;

public int getBalance() {
return balance;
}

public void addBalance(int balance) {
this.balance += balance;
}

public static void main(String[] args) {
final SimpleSynchronized simpleSynchronized = new SimpleSynchronized();
new Thread() {

@Override
public void run() {
synchronized (simpleSynchronized) {
while (simpleSynchronized.getBalance() >= 1000) {
simpleSynchronized.addBalance(-1000);
simpleSynchronized.count1++;
}
}
System.out.println("Thread 1 : " + simpleSynchronized.count1);
}

}.start();

new Thread() {

@Override
public void run() {
synchronized (simpleSynchronized) {
while (simpleSynchronized.getBalance() >= 1000) {
simpleSynchronized.addBalance(-1000);
simpleSynchronized.count2++;
}
System.out.println("Thread 2 : " + simpleSynchronized.count2);
}
}

}.start();

new Thread() {

@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("balance : " + simpleSynchronized.balance);
System.out.println("count total : " + (simpleSynchronized.count1 + simpleSynchronized.count2));
}

}.start();
}
}

- 결과
Thread 2 : 0
Thread 1 : 100000
balance : 0
count total : 100000

> 쓰레드2는 쓰레드1이 simpleSynchronized 객체를 동기화 하기 때문에 쓰레드1의 작업이 완료되기 전까지 대기 한다.
> synchronized는 동기가 필요한 곳에 적절하게 적용해야 한다.
> 주된 예로 다중쓰레드 환경에서 Map 객체의 put과 remove를 synchronized없이 처리하면 문제가 되는 경우가 있다.





3. 동기화 기본 문법
- 객체 동기화
Object object = new Object();
synchronized (object) {

}

synchronized (this) {

}

> 기본변수(int, long, double 등) 타입은 적용 불가.

- 클래스 단위의 동기화
synchronized (Object.class) {

}

- 메소드 동기화
public synchronized void setObject(Object object) {

}

Restful API 예제코드 - HttpURLConnection

1. Restful API - HttpURLConnection
- 기본코드
String urlStr = "https://www.instagram.com/web/search/topsearch/?context=blended&query=java";
URL url = null;
HttpURLConnection connection = null;
BufferedReader reader = null;

try {
url = new URL(urlStr);
connection = urlStr.startsWith("https://") ? (HttpsURLConnection) url.openConnection() : (HttpURLConnection) url.openConnection();

connection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
connection.setRequestProperty("cache-control", "no-cache");

connection.connect();

int code = connection.getResponseCode();
String message = connection.getResponseMessage();

StringBuffer buffer = null;
if (code == HttpURLConnection.HTTP_OK) {
buffer = new StringBuffer();

reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String temp = null;
while ((temp = reader.readLine()) != null) {
buffer.append(temp);
}

reader.close();
}
connection.disconnect();

System.out.println(String.format("Response : %d, %s", code, message));
System.out.println("Response DATA : ");
System.out.println(buffer == null ? "NULL " : buffer.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


- 결과
Response : 200, OK
Response DATA :
{}


> URL 문자열에서 HTTPS를 판단해서 캐스팅 할 클래스를 선택
> Restful API 사양에 맞는 RequestHeader 설정
> 예제코드에서 사용한 Restful API 주소는 인스타그램 검색 API

2019년 3월 6일 수요일

칼만필터 예제

1. 기본코드
public class Kalman {
    private static final double Q = 0.00001;
    private static final double R = 0.001;

    private double P = 1;
    private double X = 0;
    private double K;


    public Kalman(double value) {
        X = value;
    }

    // 계산
    private void measurementUpdate() {
        K = (P + Q) / (P + Q + R);
        P = R * (P + Q) / (P + Q + R);
    }

    // 현재값을 받아 계산된 공식을 적용하고 반환한다
    public double update(double measurement) {
        measurementUpdate();
        X = X + (measurement - X) * K;
        return X;
    }

}

- 상수 Q와 R의 이해가 필요
- 주 사용처 : 센서(온도, 습도, 자기장, 가속도계), RSSI 시그널 등의 오차 계산
- 칼만필터 위키백과 :
https://ko.wikipedia.org/wiki/%EC%B9%BC%EB%A7%8C_%ED%95%84%ED%84%B0

2019년 3월 5일 화요일

외부 프로세스 실행하기 - 자바


1. 프로세스 실행 - Process
- 메모장 실행 코드
try {
String command = "notepad";
Process process = Runtime.getRuntime().exec(command);
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
process.destroy();
} catch (IOException e) {
e.printStackTrace();
}


- 결과
메모장 프로세스가 호출되면 process.waitFor(); 메소드에서 대기.
메모장 프로세스가 종료되면 process.destroy(); 메소드를 호출하고 종료.




2. 프로세스 출력 가져오기
- 실행코드
try {
String command = "cmd";
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "949"));

String temp = null;
while ((temp = reader.readLine()) != null) {
System.out.println(temp);

}
reader.close();
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
process.destroy();
} catch (IOException e) {
e.printStackTrace();
}

- 결과
cmd는 출력 후 종료되는 방식이 아니므로 process.waitFor(); 메소드에서 대기.
keytool 실행 프로세스를 이용해서 테스트 해보면 process.destroy(); 까지 정상적으로 실행됩니다.

안드로이드 APK 정보 조회 - 서명, AndroidManifast.xml

1. 매니페스트 정보 조회 - AndroidManifast.xml
- 방법
안드로이드 SDK의 포함된 aapt를 통해 확인가능

- 명령어
c:\> aapt.exe dump badging [apk 파일 경로]
예) aapt.exe dump badging app-debug.apk

- 주요 항목 :
package name
versionCode
versionName
적용된 uses-permission
언어별 : application-label
적용된 uses-feature

> 그외 데이터는 출력결과를 직접 확인해서 필요한 데이터를 사용할 것.




2. 서명
- 방법
설치된 JDK의 keytool을 통해 확인 가능

- 명령어
c:\> keytool.exe -printcert -jarfile [apk 파일 경로]
예) keytool.exe -printcert -jarfile app-debug.apk

- 결과 항목 :
소유자
발행자
일련번호
키 생성일
키 만료일
서명 알고리즘 이름
공용키 알고리즘
버전
MD5
SHA1
SHA256

> keytool을 이용해서 키스토어 서명정보와 apk 서명정보를 확인 할 수 있음.


크롬 PIP 예제

- 구글 PIP 예제 :
https://developers.google.com/web/updates/2017/09/picture-in-picture


1. HTML 코드
- 샘플영상 출처 :
http://www.ithinknext.com/mydata/board/files/F201308021823010.mp4

- 코드
<html>
<head>
</head>

<body>
<video id="video" src="http://www.ithinknext.com/mydata/board/files/F201308021823010.mp4"></video>

<button id="pipButton">PIP</button>

<script>
  // Hide button if Picture-in-Picture is not supported.
  pipButton.hidden = !document.pictureInPictureEnabled;

  pipButton.addEventListener('click', function() {
    // If there is no element in Picture-in-Picture yet, let's request Picture
    // In Picture for the video, otherwise leave it.
    if (!document.pictureInPictureElement) {
      video.requestPictureInPicture()
      .catch(error => {
        // Video failed to enter Picture-in-Picture mode.
      });
    } else {
      document.exitPictureInPicture()
      .catch(error => {
        // Video failed to leave Picture-in-Picture mode.
      });
    }
  });
</script>

</body>
</html>

> 결과