티스토리 뷰

6장에서는 안드로이드 단말 내에 데이터베이스를 사용해서 정보를 저장하고 이를 불러와서 사용하는 방법에 대해 나온다. 이번 포스팅에서는 이러한 데이터베이스 기능들을 정리하려고 한다.

 

[SQLite]

안드로이드에서 사용하는 데이터베이스는 SQLite로, RDB이기 때문에 SQL을 이용해 데이터를 다룬다. 단말에서 사용하는 데이터베이스이기 때문에 임베디드 데이터베이스 라고도 부른다.

 

RDB 테이블은 다음과 같은 구조를 가지고 있다. 세로 줄을 속성(Attribute)라고 부르고, 가로 줄을 튜플(Tuple)이라고 부른다. 속성과 튜플로 이루어진 전체를 Relation이라고 부른다.

 

SQLite의 특징은 다음과 같다.

  • SQLite는 다른 RDB와 다르게 하나의 파일로 구성된다. (확장자로 .db를 사용.)

  • 모바일용 데이터베이스이기 때문에 대용량 데이터가 아닌 적은 용량의 데이터를 다룬다.

 

SQLite를 사용하려면 4단계를 거쳐야 한다. (1) 데이터베이스 오픈, (2) 테이블 만들기, (3) 데이터 입력, (4) 데이터 조회 이다.

 

1단계

SQLite를 사용하기 위해 먼저 데이터베이스를 열어야 하는데 데이터베이스를 여는 메서드는 openOrCreateDatabase() 이다.

public abstract SQLiteDatabase openOrCreateDatabase (String name, int mode, SQLiteDatabase.CursorFactory factory)

 

데이터베이스 객체를 만들고 데이터베이스를 여는 코드로 다음과 같이 작성할 수 있다.

class DataManager {
    static SQLiteDatabase database;

    void openDatabase(Context context) {
        database = context.openOrCreateDatabase("DB_NAME.db", Context.MODE_PRIVATE, null);
    }
}

 

데이터베이스를 삭제하기 위해서는 deleteDatabase() 메서드를 사용하면 된다.

public abstract boolean deleteDatabase (String name)

 

2~4단계

데이터베이스를 열었으면 테이블을 만들고 데이터를 입력하면 테이블에 들어있는 데이터를 조회할 수 있다.

먼저, 결과가 없는 SQL문을 실행하는 메서드는 execSQL()이다. 결과가 없는 SQL문이라고 하면 '테이블 생성/삭제, 데이터 입력/삭제'와 같은 SQL문을 의미한다. execSQL() 메서드의 인자로 sql 문을 넣으면 그 sql 문이 실행된다.

public void execSQL (String sql) throws SQLException

 

이와 달리 결과가 있는 SQL문을 실행하기 위해서는 rawQuery() 메서드를 사용해야 한다. 결과가 있는 SQL문은 대표적으로 '데이터 조회'가 있다. rawQuery()의 실행 결과에 접근할 수 있는 cursor 객체가 반환되는데 이 객체를 이용해 데이터를 가져와서 사용할 수 있다. execSQL() 메서드와 마찬가지로 인자로 sql 문을 넣으면 그 sql 문이 실행된다.

public cursor rawQuery (String sql) throws SQLException

 

 

※ 테이블 생성, 입력, 조회 SQL은 일반적인 SQL과 문법이 거의 동일하다. 약간 다른 점은 자료형이 다르다는 점이다. 약간씩 다른 데이터는 첨부한 링크에서 확인할 수 있다. [SQLite 데이터 타입 링크: https://www.sqlite.org/datatype3.html ]

 

대표적으로 테이블을 생성하는 SQL문은 다음과 같이 사용할 수 있다.

String CREATE_QUERY = "CREATE TABLE [IF NOT EXISTS] table_name (" +
            _id + " INTEGER, " +
            name + " TEXT, " +
            age + " INTEGER, " +
            rating + " FLOAT, " +
            IMAGE + " BLOB)";
            
database..execSQL(sql);

[IF NOT EXISTS] 옵션은 이미 같은 이름의 테이블이 있다면 만들지 않겠다는 옵션이다. 이 옵션을 주면 테이블을 만들 때마다 테이블이 이미 존재하는지 확인할 필요가 없어 간편하게 테이블을 만들 수 있다.

 

 

테이블에 데이터를 입력하는 예제를 보자. 가장 일반적인 방법은 다음과 같다.

String sql = "INSERT INTO table_name VALUES (1, billy, 10, 4.5, null)";
database.execSQL(sql);

 

하지만 값을 일일히 대입하는 것이 번거롭다면 Object[] 배열을 사용하여 인자로 넣어주는 방법도 있다.

String sql = "INSERT INTO table_name VALUES (?, ?, ?, ?, ?)";
Object[] params = {1, "billy", 10, 4.5, null};

database.execSQL(sql, params);

이 방법은 BLOB 데이터와 같은 byte array 데이터를 sql 문자열로 만들어서 

 

 

테이블 데이터를 조회하는 방법은 다음과 같이 사용할 수 있다.

Cursor cursor = database.rawQuery("SELECT * FROM table_name");
int numOfMovieList = cursor.getCount();
Log.d("Log", "레코드 개수: " + numOfMovieList);

for (int i=0; i<numOfMovieList; ++i) {
    cursor.moveToNext();
    
    int id = cursor.getInt(cursor.getColumnIndex("id"));
    String name = cursor.getString(cursor.getColumnIndex("name"));
    float age = cursor.getFloat(cursor.getColumnIndex("age"));
}

cursor.close();

cursor.getCount()를 하면 결과 건수가 반환된다. moveToNext() 메서드를 호출해야 결과의 레코드를 하나씩 차례로 읽을 수 있다. getInt() 메서드는 인자로 속성(Attribute)의 index를 넣어야 하는데, index를 컬럼명으로 찾기 위해 getColumnIndex() 메서드를 사용할 수 있다. cursor도 하나의 리소스를 잡고 있기 때문에 사용하고 나서는 close()를 해서 반환해야 한다.

 

 

 

 

[헬퍼 클래스]

만약 플레이스토어에 앱을 배포하여 많은 유저가 사용하고 있을 때 데이터베이스의 테이블 구조가 변경되어야 한다면, 데이터베이스의 생성/오픈/업그레이드/다운그레이드를 할때 어떻게 해야 할까? 이런 기능을 편하게 도와주는 클래스가 바로 SQLiteOpenHelper이다.

 

SQLiteOpenHelper는 기존에 설치 되어 있는 데이터베이스가 있는지, 있다면 해당 버전이 몇인지를 확인할 수 있다.

 

헬퍼 클래스의 구조는 다음과 같다.

SQLiteOpenHelper의 구조

 

새로 만드는 CustomerDatabase 클래스는 DatabaseHelper 객체와 버전 정보를 관리한다. Helper클래스를 상속한 DatabaseHelper 클래스 안에서는 처음 데이터베이스가 만들어질 때는 onCreate(), 버전이 바뀌어 업그레이드될 때는 onUpgrade() 메소드가 호출된다. 이렇게 하면 버전에 따라 다른 기능을 하는 코드를 넣어줄 수 있다는 장점이 있다.

 

onCreate() 메서드는 데이터베이스가 처음 만들어지는 경우에 호출되므로 데이터베이스를 초기화(생성) 하는 SQL을 넣고, onUpgrade() 메서드는 버전이 바뀌어 업그레이될 때 호출되므로 만약 버전이 업그레이드되면서 단말에 저장된 데이터베이스의 구조가 바뀌어야 하는 경우라면 테이블을 변경하기 위한 ALTER 문 등을 넣을 수 있다. 또한 필요한 경우에 이미 저장되어 있는 데이터를 다른 곳에 복사했다가 새로 테이블을 만들고 그 테이블에 넣어주는 방식으로 처리하기도 한다.

 

 

 

 

[연결 상태 확인하기]

지금까지 데이터베이스를 사용하는 방법을 알아보았으니 데이터베이스를 언제 사용하는지 보자. 대표적으로 네트워크를 통해 얻어오는 데이터가 있는데 만약 네트워크가 연결되지 않은 상태일 때 단말에 저장되어 있는 데이터베이스에서 정보를 가져오는 경우를 예로 들 수 있다. 이런 상황에서는 네트워크가 연결되어 있을 때 데이터를 얻어온 뒤 데이터베이스에 넣고, 네트워크가 연결되어 있지 않을 때 데이터베이스에서 데이터를 가져오게 하면 될 것이다.

 

그렇다면 네트워크가 연결이 되어있는지 아닌지를 알 수 있어야 한다. 이번 내용은 네트워크 연결 상태를 알고 싶을 때 연결상태를 확인하는 방법에 대한 내용이다.

 

이를 위해 네트워크 연결 상태를 알 수 있도록 지원해주는 클래스가 있는데 그 클래스가 바로 ConnectivityManager이다. 이 클래스는 시스템 서비스에서 제공하는 기능이기 때문에 아래와 같이 코드를 작성하면 연결 상태를 체크할 수 있다. 아래 코드는 간단하게 네트워크 연결 상태를 체크하는 코드이다.

        CONNECTIVITY_MOBILE - 모바일 데이터 (3G, LTE)

        CONNECTIVITY_WIFI - 무선 랜 (WIFI)

boolean checkNetwork(Context context) {
    ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    // context.getSystemService() 메소드는 SystemService를 리턴하기 때문에 시스템 서비스의 종류인 ConnectivityManager로 캐스팅을 해서 사용한다.

    NetworkInfo networkInfo = manager.getActiveNetworkInfo();
    if (networkInfo != null) {
        int type = networkInfo.getType();

        if (type == ConnectivityManager.TYPE_MOBILE || type == ConnectivityManager.TYPE_WIFI) {
            return true;
        }
    }
    return false;  // No Connectivity
}

 

하지만!!! ConnectivityManager 의 TYPE_MOBILE, TYPE_WIFI, NetworkInfo 의 getActiveNetworkInfo() 등은 최신 API 에서 Deprecated 되었다. Deprecated 된 API 나 값들은 언제 없어지거나 동작하지 않아도 이상하지 않기 때문에 최신 API에서 지원하는 NetworkCapabilities 클래스를 사용하는 것이 좋다.

boolean checkNetwork(Context context) {
    ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

    Network network = connectivityManager.getActiveNetwork();
    NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);

    if (capabilities != null)
        return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);

    return false;
}

 

위 코드에서 getActivityNetworkInfo() 메소드를 사용하기 위해서는 AndroidMenifest.xml 파일에 아래 권한을 추가 해 주어야 한다. 

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

우리가 만든 어플에서 단말의 네트워크 상태를 변경하고자 한다면 CHANGE_NETWORK_STATE 권한도 있어야 하지만, 단순히 네트워크의 상태를 체크하고 싶다면 ACCESS_NETWORK_STATE 권한만 있어도 된다.

 

 

마지막으로 네트워크 연결 상태를 실시간으로 감지하는 방법에 대해 설명하고 마무리 지으려고 한다.

네트워크 연결 상태를 알아보기 위해 위에서 정의한 checkNetwork() 메서드를 직접 호출을 하는 방법이 있는데 이는 굉장히 비효율적이다. 네트워크를 통해 데이터를 주고 받기 전에 항상 checkNetwork() 메서드를 호출하면 성능이 느려질 수 있다. 그렇기 때문에 시스템에서 네트워크 변경이 감지될 때마다 알아서 함수를 호출하게 한다면 이런 문제를 해결할 수 있다.

 

이럴 때 사용하는 것이 BroadcastReceiver이다.

BroadcastReceiver를 상속 받은 클래스에서 onReceive() 메서드를 오버라이드 하면 네트워크의 변경이 감지될 때마다 onReceive() 메서드가 자동으로 호출된다. 이를 위한 예시 코드를 보자.

public class NetworkManager extends BroadcastReceiver {
    static boolean networkState = false;

    @Override
    public void onReceive(Context context, Intent intent) {
        String action= intent.getAction();

        if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
            if (NetworkManager.checkNetwork(context))
                NetworkManager.networkState = true;
            else 
                NetworkManager.networkState = false;
        }
    }
    
    boolean checkNetwork(Context context) { ... }
}

이렇게 하면 네트워크의 변경이 감지되면 onReceive() 메서드가 콜백되면서 네트워크를 체크하고 NetworkManager의 클래스 변수인 networkState를 변경해 주기 때문에 연결이 되어있는지 아닌지 확인하기 위해 networkState의 값만 확인하면 된다.

 

이제, 이렇게 만든 NetworkManager를 생성해서 브로드캐스트 리시버로 등록해 주어야 한다. 리시버를 등록하는 방법으로는 Manifest 파일에 직접 등록해 주는 방법이 있고, Java 파일에서 동적으로 NetworkManager 객체를 만들어 등록해 주는 방법이 있다.

 

먼저, Manifest 파일에 직접 등록하는 방법은 <activity> 태그 안에 아래의 코드를 넣으면 된다.

<receiver
    android:name="(NetworkManager 클래스 패키지명)"
    android:enabled="true"
    android:exported="false">
    <intent-filter >
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

 

동적으로 NetworkManager 객체를 만들어 등록해 주는 방법은 아래 함수를 만들어 호출하는 것이다.

// 네트워크 리시버에 만들어 놓은 NetworkManager() 등록하기
void registerNetworkReceiver(Context context) {
    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    NetworkManager receiver = new NetworkManager();
    context.registerReceiver(receiver, filter);
}

 

이렇게 브로드캐스트 리시버까지 등록해 주었다면 이제는 실시간으로 네트워크 변경을 감지하여 네트워크가 연결되어 있을 때는 네트워크 요청을 보내고, 네트워크가 연결되어있지 않을 때는 데이터베이스에서 데이터를 가져오는 동작을 구현할 수 있다.

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함