Search

'안드로이드'에 해당되는 글 4건

  1. 2019.02.16 px, dp, sp 개념 정리
  2. 2019.01.26 안드로이드 선 추가하기
  3. 2019.01.10 SharedPreference
  4. 2018.01.21 이기적인 총무 리팩토링 - Database 관리 부분

px, dp, sp 개념 정리

컴퓨터공부/안드로이드 2019.02.16 16:04 Posted by 아는 개발자

XML 파일 안에서 UI 컴포넌트의 크기를 정할 때 dp, sp, px 단위의 원리를 제대로 알지 않고 사용하는 경우가 있는데 이렇게 막 사용하다 보면 디바이스 해상도가 변경 될 때 화면이 깨지는 경우가 생긴다. 이번 포스트에서는 지금까지 막무가내로 사용해온(?) dp, sp, px 단위를 총 정리 해보려고 한다.


0. px (Pixel)



주소화 할 수 있는 화면의 가장 작은 단위. 점을 찍을 수 있는 가장 작은 단위라고 생각하면 된다. 화면 해상도와 관계 없이 컴포넌트의 크기를 절대값으로 세팅할 수 있으며 위의 그림으로 알 수 있듯이 오른쪽으로 갈 수록 폰의 해상도가 높아지면서 텍스트 상자와 이미지 아이콘이 작아진 것을 알 수 있는데 값을 픽셀 단위로 설정했기 때문에 그렇다. 이런 단위로는 여러 디바이스 환경마다 아이콘의 크기가 천차만별이 될 수 있어서 안드로이드 환경에서는 잘 사용하지 않는다.


1. dp (Density-independent Pixels)


화면의 해상도값에 따라서 크기가 조정되는 단위다. 160dpi 스크린 해상도에서는 1px만큼의 크기를 가지고 해상도 값이 커질수록 픽셀의 값이 커진다. 320dpi 해상도면 2px 만큼의 크기를 가지고 480dpi 면 3px만큼 가지는 원리다. 위의 그림에서도 보이듯이 해상도가 높아져도 텍스트상자와 이미지의 크기가 일정한 것을 알 수 있는데 이는 해상도에 따라서 크기가 재설정 됐기 때문이다. 해상도에 따라 크기가 자동으로 늘어나고 줄어들 수도 있으니 스펙이 다양한 안드로이드 디바이스 환경에서 적용할 때 유용한 단위다.


2.  sp (Scale-independent Pixels)


dp랑 비슷한 원리로 동작하며 사용자의 글짜 크기(font-size)를 조정할 때 사용되는 단위다. 

'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

안드로이드 선 추가하기

컴퓨터공부/안드로이드 2019.01.26 15:19 Posted by 아는 개발자

안드로이드 앱을 사용하다보면 기사나 댓글 화면에는 여러 개의 아이템을 구분짓기 위해서 빨간 상자로 표시한 것처럼 선을 사용한다.


1. 원리


안드로이드에선 따로 선에 해당하는 라이브러리가 있지는 않고 직사각형 두개를 겹쳐서 선처럼 보이게하는 트릭을 사용한다. 아래의 그림처럼 한 사각형은 선으로 사용할 색으로 채우고 다른 사각형은 배경색 채우는 식이다. 두 사각형을 겹칠 때 흰색 사각형을 선색 사각형 위에 올리고 위치를 위쪽을 조금 올리면 선색 사각형의 남은 부분이 선처럼 보이는 착시효과를 줄 수 있다.



2. 코드


코드상으로 표현하면 다음과 같다.

// res/drawable/line.xml


    
    
        
            
            
        
    

    
    
        
            
            
        
    


Line color rectangle은 앞의 그림에서 왼쪽 사각형에 해당하는 코드고 Background rectangle은 오른쪽 사각형에 해당한다. Background rectangle 코드를 보면 android:bottom 값이 1dp 가 설정돼있는데 이것은 사각형을 아래에서 1dp만큼 띄어두겠다는 뜻이다. 


각 사각형마다 stroke와 solid 속성값이 있다. stroke 는 선의 색을 의미하고 solid는 사각형 내부 색을 말한다. 왼쪽 사각형은 선과 내부 색이 회색 int 값인 #FF5E5E5E 으로, 왼쪽 사각형은 흰색인 #FFFFFFFF 으로 채워져있다.


3. 적용


완성한 xml 파일을 선을 표시할 영역의 backgroud로 설정하면 아래와 같이 적용된다. 타이틀 텍스트 뷰 밑에 선을 넣고자 LinearLayout의 background 값으로 설정했다.




        
    



'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

SharedPreference

컴퓨터공부/안드로이드 2019.01.10 21:52 Posted by 아는 개발자

애플리케이션을 종료한 이후에도 수정한 값을 유지하려면 데이터를 간단한 텍스트 파일에 써두거나 특정 데이터베이스의 형태로 저장해야 한다. 안드로이드에서는 관계형 데이터베이스로는 SQLite를, 그리고 단순히 key-value 값을 저장할 수 있는 용도인 Shared Preference를 제공한다.


Shared Preference 


애플리케이션의 설정 값처럼 데이터가 key-value의 형태이고 단순한 타입을 사용하며(boolean, int, float, long, string) 기록해야할 양이 얼마 되지 않는다면 관계형 데이터베이스를 사용하는 것보다 SharedPreference를 쓰는 것이 훨씬 편하다. SharedPreferece는 복잡하고 길고긴 쿼리문 없이 객체와 key값을 이용해 데이터를 쉽게 수정하고 가져올 수 있다.


1. 객체 생성


settings = Context.getSharedPreferences(PREF_NAME, MODE);


데이터를 수정할 수 있는 SharedPreference 객체는 Context 객체내에 내장된 getSharedPreference 함수를 통해 가져올 수 있다. 상황에 따라서 다르나 나는 주로 getApplicationContext를 사용한다.


인자 값으로는 두개를 받는데 첫번째는 PREF_NAME, SharedPreference로 저장되는 데이터베이스의 이름을 말한다. 이 값이 달라지면 서로 다른 데이터베이스 파일에 저장되게 되고 별도의 key값으로 관리된다. PREF_NAME은 storage에 XML파일의 이름으로 들어간다.


두번째 인자 값은 동작하는 형태다. 주로 0을 넣거나 MODE_PRIVATE, MODE_WORLD_READABLE, MODE_WORLD_WRITEABLE을 혼합해서 사용한다.


2. 기록


SharedPreferences.Editor editor = settings.edit();
editor.putLong(FILE_ID, fileId);
editor.commit();


값을 기록할 때는 생성한 SharedPreference 객체로부터 editor 객체를 가져온다. 이 객체내에는 putInt, putBoolean, putString ... 함수가 있는데  모두 key값에 해당하는 데이터가 없으면 데이터를 추가하고 있으면 업데이트 하는 함수다. 저장하는 데이터 타입은 함수 명으로 유추할 수 있다.


첫번쨰 인자 값은 key값이고 두번째 인자 값은 저장할 데이터의 값이다. key값은 나중에 데이터를 읽어올 때 필요하니 static 변수로 저장해두는 것이 편하다.


putXXX 함수를 호출한 다음에는 에디터 함수 내의 commit 또는 apply함수를 호출해서 데이터 값을 최종적으로 기록한다. 호출하지 않으면 수정한 값이 파일에 저장되지 않는다. commit은 동기 방식으로 apply는 비동기 방식으로 값을 업데이트하니 이 또한 상황에 따라서 적절히 활용하면 된다.


3. 읽어오기 


return settings.getLong(FILE_ID, -1);


값을 수정할 때 처럼 SharedPreference 함수에 내장된 getXXX 함수를 통해서 읽어올 수 있다. 첫번재 인자는 읽어올 key 값이고 두번째 인자는 만약 key값에 해당하는 데이터가 없거나 읽어오는데 실패했을 때 기본으로 얻게될 데이터 값이다. 


4. 주의사항


공식문서에는 SharedPreference의 동작 형태중 하나인 MODE_MULTI_PROCESS 가 API23 이후로 Deprecated 됐고 앞으로 안드로이드에서도 지원할 생각이 없다고 한다.


MODE_MULTI_PROCESS 에 해당하는 대표적인 예로 안드로이드 UI 컴포넌트와 안드로이드 서비스가 별도의 프로세스로 분리된 경우가 있다. 개발하다보면 안드로이드 액티비티와 서비스가 데이터를 공유해야하는 일이 종종 생기는데 Context와 데이터 이름만 지정하면 얻어올 수 있는 SharedPreference 객체를 사용하는 것이 가장 심플하게 구현할 수 있는 방법이라 자주 사용된다.


액티비티와 서비스가 동일한 프로세스라면 상관 없지만 둘이 별도의 프로세스인 경우엔 실행도중에 동기화 문제가 발생한다. 글쓴이가 몸소 체험한 동기화 문제는.. 서비스를 먼저 실행시킨 다음 액티비티 에서 값을 변경했더니 백그라운드 서비스에서 수정한 값을 읽어오지 않고 예전 값을 계속 읽어오던 일이 있었다. 처음에는 데이터 수정이 제대로 되지 않았는지가 의심스러워 commit() 함수 리턴값만 확인 했었는데 알고보니 별도의 프로세스 환경에서는 사용할 수 없었다. 이것 때문에 몇시간을 잡았는지 모른다.



'컴퓨터공부 > 안드로이드' 카테고리의 다른 글

AsyncTask  (0) 2019.03.13
ViewPager와 PageAdapter  (0) 2019.03.05
px, dp, sp 개념 정리  (0) 2019.02.16
Device screen dpi 값에 따라 처리하기  (0) 2019.02.16
안드로이드 선 추가하기  (0) 2019.01.26
SharedPreference  (0) 2019.01.10

구조설계서에 시스템에서 사용할 클래스 다이어그램을 그리다 보니 작성한 내용 그대로 실제로 시스템이 적용 할 수 있을지 의문이었다. 문서의 내용은 그럴듯 한데 여기에 안드로이드 시스템 특유의 feature들을 넣다보면 중간에 엉키게 되는 부분이 상당수 있을 것 같은 느낌이 드었다. 나중에 반복해서 고치는것 보단 직접 먼저 코드를 수정한 후 현실 가능성을 검증하고 옮기는 것이 나을것 같아 예전부터 가장 먼저 손대기로 했었던 DatabaseHelper 클래스를 리팩토링 했다.


수정전 DatabaseHelper.java 클래스는 이랬다.


그림 1. 초기 DatabaseHelper 클래스 다이어그램


DatabaseHelper 클래스는 이름 그래도 이기적인 총무의 데이터베이스를 관리할 수 있는 기능을 제공하는 클래스다. 그런데 여기에 Party, Pay, Person, Pay-Participation 테이블에 대해서 각각 CRUD 명령을 모두 넣다보니 위 클래스가 갖는 함수가 너무 많다. 클래스는 하나의 Responsibility만 가져야 하는데 위 경우에는 4개의 Responsibility를 가지게 됐으며 이런 경우 한 영역의 수정이 다른 영역의 수정에 영향을 미치게 된다. 객체지향적 관점에서 부적합한 설계 방법론이다. 위 클래스가 갖고 있는 Responsibility를 최소화하기 위해 총 4개의 subclass로 분리했다.


1. 역할에 맞춰서 클래스를 분리한다


그림 2. 1차 수정 버전


DatabaseHelper 클래스를 abstract 클래스로 두고 Table 별로 서브클래스를 두었다. 이렇게 하니 각각이 기능성으로 분류돼 각 클래스 별로 하나의 기능만 존재하게 됐다. 그런데 위 디자인을 적용해서 설치해보니 실행중 에러가 발생한다. 테이블이 생성되지 않았다는 에러다.


테이블을 생성하는 작업은 onCreate() 함수 안에서 담당한다. 그림 1에선 onCreate 함수 내에 모든 테이블을 생성하도록 했고, 그림 2에서는 각 클래스내에 존재하는 onCreate 함수에 테이블을 생성하도록 했다. 그런데 이렇게하니 문제가 발생했다. onCreate 함수는 애플리케이션내에서 데이터 베이스 함수를 열 때(getWritableDatabase 함수를 실행할 때) 딱 한번만 실행 된다. 각각이 현재 분리돼 있지만 시스템 내에서 실행되는 경우는 딱 한번 뿐이다. 그래서 PartyDatabseHelper에서 먼저 onCreate 함수를 실행 했다면 나머지 클래스에선 모두 생략된다.


위 문제는 시스템을 시작할 때 테이블을 생성하는 작업을 실행하도록 해 간단히 해결 할 수 있다. 그런데 테이블을 업그레이드 해야하는 경우는 문제가 생긴다. 업그레이드 명령인 경우 현재 데이터베이스 버전 번호와 최신 데이터베이스 버전 정보가 필요한데 이런 경우에는 기존 onUpgrade 루틴을 벗어나서 버전 정보를 읽어오는 것이 불가능하며 설사 가능하더라도 기존에 있는 기능을 중복해서 만드는것도 바람직한 코딩이 아니다.


마침 다른 블로그에 여러개의 테이블을 관리하는 코드가 있어 그 코드를 참조해서 리팩토링 했다.


2. 기존 create/upgrade 루틴과 호환되도록 한다.


그림 3. 2차 수정 버전


Party 데이터 베이스 관리 클래스가 DatabaseHelper 클래스로의 서브 클래스였는데 이제는 모두 별도의 클래스가 됐고 onCreate/onUpgrade 함수 대신 createTable(), upgradeTable()이 생겼다. 이 함수들은 static으로 선언해 외부에서도 불러줄 수 있도록 했다. DatabaseHelper에서는 기존대로 onCreate 루틴을 타는데 이때 각 서브클래스에서 선언된 createTable를 불러주도록 했다. onUpgrade 일때는 각 서브클래스에서 선언된 upgradeTable을 부른다. 


    @Override
    public void onCreate(SQLiteDatabase db) {
        PartyDatabaseManager.createTable(db);
        PayDatabaseManager.createTable(db);
        PersonDatabaseManager.createTable(db);
        PayParticipateDatabaseManager.createTable(db);

        Log.d(LOGTAG, "Create table is called");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        PartyDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PayDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PersonDatabaseManager.upgradeTable(db, oldVersion, newVersion);
        PayParticipateDatabaseManager.upgradeTable(db, oldVersion,newVersion);

        onCreate(db);
    }


기존 생성/업데이트 루틴을 타게 됐으니 앞서 말한 문제점이 깔끔하게 해결 된 것 같지만 이 코드도 문제점이 없는건 아니다. 만약 시스템에서 관리하는 테이블이 추가된다면 DatabaseHelper 클래스에 함수의 테이블 생성 작업과 업그레이드 작업을 추가해야 한다. 그런데 DatabaseHelper가 시스템 테이블의 구체적인 정보까지 알고 있는 것은 바람직하지 않다. DatabaseHelper는 각 테이블에게 추가 또는 업데이트를 명령할 뿐이지 구체적으로 어떤 테이블이 있는지는 알 필요가 없다.


차라리 외부에서 DatabaseHelper가 관리 해야하는 테이블을 등록하고 생성/업데이트 할 때 등록된 테이블만 관리하는 방법이 깔끔한 것 같다. 그래서 다음과 같이 변경했다.


3. DatabaseHelper에는 세부적인 내용을 넣지 않는다.


그림 4. 3차 수정 버전


 
// startActivity.java 내에 관리할 테이블 정보를 등록하는 코드.
        DatabaseHelper databaseHelper = new DatabaseHelper(this);

        databaseHelper.registerTableManager(new PartyTableManager());
        databaseHelper.registerTableManager(new PayJoinTableManager());
        databaseHelper.registerTableManager(new PayTableManager());
        databaseHelper.registerTableManager(new PersonTableManager());
 
// DatabaseHelper 클래스는 등록된 테이블 별로 create/update 함수를 부른다
    @Override
    public void onCreate(SQLiteDatabase db) {
        for (TableManager tableManager : tableManagerList)
            tableManager.createTable(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        for (TableManager tableManager : tableManagerList)
            tableManager.upgradeTable(db, oldVersion, newVersion);

        onCreate(db);
    }


TableManager라는 인터페이스를 둬 DatabaseHelper가 각 클래스의 세부 내용을 모르더라도 함수를 호출 할 수 있도록 했고 각각의 Table을 관리하는 클래스는 TableManager 인터페이스를 구현하도록 했다. DatabaeHelper 생성시 관리할 테이블을 등록해 안에 세부적으로 구현하지 않고 생성/업그레이드 작업을 수행할 수 있도록 했다. 관리할 테이블이 추가되면 startActivity에 테이블을 추가로 등록하면 되고 테이블을 삭제할 경우에는 등록하는 부분의 테이블을 지우면 된다. 이전보다 훨씬 쉽게 변경이 가능한 코드가 됐다.