본문 바로가기

프로그래밍/안드로이드

[안드로이드] 안드로이드 - 컨텐트 프로바이더

[안드로이드] 안드로이드 - 컨텐트 프로바이더

[펌]http://android15.tistory.com/category/SDK

컨텐트 URI의 구성

컨텐트프로바이더에 의해 제어되는 데이터임을 나타내기 위해서
컨텐트 URI는 "content://"로 시작합니다.

여기에 컨텐트프로바이더를 식별하는 authority 부분을 더합니다. authority는 <provider> 의 authorities 애트리뷰트로 선언됩니다.

<provider name=".TransportationProvider"
          authorities="com.example.transportationprovider"
          . . .  >

여기에 컨텐트프로바이더가 어떤 종류의 데이터가 요청되는지를 결정할 때 사용하는 경로를 더합니다.
이것이 없으면, 하위타입들을 포함한 여러개의 타입들이 작성됩니다.

여기에 만약 있으면, 마지막으로 요청된 특별한 레코드의 ID를 추가합니다. 레코드가 하나가 아닌 경우는 생략됩니다.

컨텐트프로바이더 기본사항
http://developer.android.com/guide/topics/providers/content-providers.html

안드로이드에는 모든 안드로이드 패키지들이 액세스할 수 있는 공통의 저장 장소가 없습니다. 컨텐트프로바이더들은 애플리케이션들 사이의 데이터를 공유할 수 있는 유일한 방법입니다. 컨텐트프로바이더는 데이터를 저장하고 검색하고, 모든 애플리케이션에서 액세스할 수 있도록 합니다.

안드로이드는 공통의 데이터타입들(오디오, 비디오, 이미지, personal contact information 등)에 대한 많은 컨텐트 프로바이더를 제공합니다.

여러분 자신의 데이터를 다른 애플리케이션에서 접근할 수 있게 하는 방법에는 두 가지 방법이 있습니다.
1. 여러분 자신의 컨텐트 프로바이더를 작성합니다.
2. 존재하는 프로바이더에 데이터를 추가합니다. 이렇게 하기 위해서는 같은 데이터타입을 제어하는 컨텐트프로바이더이고, 쓰기에 대한 접근 허용이 되어 있어야 합니다.


일반적으로 컨텐트프로바이더를 직접 사용하지 않고, ContentResolver를 사용합니다.
ContentResolver는 액티비티 또는 다른 컴포넌트의 getContentResolver 메소드를 통해서 얻을 수 있습니다.

ContentResolver cr = getContentResolver();



데이터 모델
컨텐트프로바이더들이 제공하는 데이터는 간단한 테이블 형태를 갖습니다. 각 행은 레코드이고 각 열은 특별한 타입과 의미를 가진 데이터입니다.

쿼리가 리턴하는 커서는 행에서 행으로 열에서 열로 이동하면서 필드 값을 읽습니다. 데이터 값을 데이터 타입에 맞게 읽는 메소드들을 제공하기 때문에, 필드를 읽을 때, 여러분은 필드가 포함하는 데이터가 무슨 타입인지를 알아야 합니다.  


URI
각각의 컨텐트프로바이더는 Uri 객체로 작성된 URI를 제공합니다. 다수의 데이터셋을 제어하는 컨텐트프로바이더는 각각에 대해 구별된 URI를 제공합니다.

URI는 "content://"로 시작합니다.  
컨텐트 URI에 대한 자세한 사항은 다음 링크를 참조합니다.

컨텐트프로바이더를 정의할 때, URI 상수를 정의하는 것은 좋은 생각입니다. 이것은 클라이언트 코드를 단순하게 하고, 변경을 쉽게 합니다. 안드로이드는 플랫폼과 함게 제공하는 모든 프로바이더들에 대해 CONTENT_URI를 정의합니다.  
예를 들어, 전화번호와 사람을 매치하는 테이블의 URI와 사람들의 사진을 가지고 있는 테이블에 대한 URI는 다음과 같습니다.

android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI

비슷하게, 최근 걸려온 번호들과 달력 엔트리 테이블에 대한 URI는 다음과 같습니다.

android.provider.CallLog.Calls.CONTENT_URI
android.provider.Calendar.CONTENT_URI

URI 상수는 컨텐트프로바이더와의 모든 상호작용에 사용됩니다. 모든 ContentResolver 메소드는 첫 번째 인자로 URI를 취합니다.


컨텐트프로바이더 쿼리하기


컨텐트 프로바이더를 쿼리(query)한다는 것?
쿼리 한다는 것은 데이터베이스를 쿼리하는 것과 비슷하지 않겠는가? 비슷하다.
URI를 사용한다는 점이 좀 다르다.


쿼리 만들기
컨텐트프로바이더를 쿼리하기 위해서,
ContentResolver.query 메소드 또는 Activity.managedQuery 메소드를 사용할 수 있습니다.
두 메소드는 인자와 리턴타입이 같습니다. Cursor를 리턴합니다. managedQuery의 경우, 액티비티가 커서의 생애주기를 관리합니다.
액티비티에서 관리되지 않는 커서에 대해 Activity.startManagingCursor 메소드를 사용해 액티비티가 커서를 관리하도록 할 수 있습니다.
query나 managerQuery의 첫 번째 인자는 프로바이더의 URI(CONTENT_URI 상수)입니다. 
단지 하나의 쿼리로 제약하고 싶다면, URI에 _ID 값을 추가하면 됩니다. 예를 들어, 만약 ID가 23이라면, URI는 다음과 같이 될 수 있습니다.
content://. . . ./23

URI를 작성을 돕는 몇 몇 메소드들이 있습니다.
ContentUris.withAppendedId와 Uri.withAppendedPath는 쉽게 ID와 Uri를 추가하도록 만듭니다. 둘 다 ID가 추가된 Uri객체를 리턴합니다. 예를 들어, 만약 레코드 23을 조회한다면, 다음과 같이 작성됩니다.

Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
Cursor cur = managedQuery(myPerson, null, null, null, null);


쿼리가 리턴하는 것
쿼리는 하나 이상의 데이터베이스 레코드들을 리턴합니다.
모든 프로바이더는 항상 _ID와 레코드 수를 나타내는 _COUNT 열을 포함합니다.  

검색된 데이터는 커서에 의해 접근됩니다. 커서는 결과 집합의 앞으로 또는 뒤로 iterate할 수 있습니다. 커서는 읽기 전용으로 사용됩니다. 추가와 수정과 삭제를 하고 싶다면 ContentResolver를 사용해야 합니다.


검색된 데이터를 읽기
Cursor.getColumnIndex를 통해 index를 얻고,
Cursor.get<타입>을 통해 값을 구합니다.

private void getColumnData(Cursor cur){
    if (cur.moveTo!First()) {

        String name;
        String phoneNumber;
        int nameColumn = cur.getColumnIndex(People.NAME);
        int phoneColumn = cur.getColumnIndex(People.NUMBER);
        String imagePath;
   
        do {
            // Get the field values
            name = cur.getString(nameColumn);
            phoneNumber = cur.getString(phoneColumn);
          
           // Do something with the values.
            ...

        } while (cur.moveTo!Next());

    }
}

쿼리가 바이너리 데이터를 리턴한다면, 데이터는 직접적으로 테이블안에 들어갈 수도 있고, URI만 들어갔을 수도 있습니다.
20에서 50K 정도되는 적은 양의 데이터는 일반적으로 직접적으로 테이블에 들어갑니다. 이 데이터를 읽을 때는 Cursor.getBlob 메소드를 사용합니다. 이 메소드는 바이트배열을 리턴합니다.
만약 URI를 사용할 경우라면, 결코 직접적으로 열고 읽으려고 해서는 안됩니다. 대신 여러분은 ContentResolver.openInputStream 메소드를 사용해야 합니다. 이 메소드는 InputStream을 리턴합니다.


컨텐트 프로바이더에서 데이터 수정

모든 데이터의 추가, 수정, 삭제는 ContentResolver를 사용해 달성됩니다.

데이터베이스의 CUD와 유사하지 않겠는가? 유사하다.
URI를 사용하는 점을 제외하면 거의 같다.

ContentValues values = new ContentValues();

values.put(People.NAME, "Abraham Lincoln");
values.put(People.STARRED, 1);

Uri uri = getContentResolver().insert(People.CONTENT_URI, values);

적은 양의 데이터는 ContentValues를 사용합니다. 그러나 큰 양의 바이너리 데이터를 추가해야 한다면,
테이블에 URI를 작성하고, 그 URI를 가지고 ContentResolver.openOutputStream를 호출합니다.

ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");

Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);

try {
    OutputStream outStream = getContentResolver().openOutputStream(uri);
    sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
    outStream.close();
} catch (Exception e) {
    Log.e(TAG, "exception while writing image", e);
}

갱신은 ContentResolver.update, 삭제는 ContentResolver.delete 메소드를 사용합니다.


컨텐트 프로바이더 만들기

다음과 같은 일들을 해야 합니다.
1. 어떤 형태의 저장소를 사용할 것인지 결정합니다.
안드로이드에서는 대부분의 컨텐트프로바이더가 안드로이드 파일이나 SQLite 데이터베이스를 사용합니다.
2. ContentProvider 클래스를 확장한 컨텐트프로바이더 클래스를 작성합니다.  
3. 애플리케이션의 manifest 파일에 컨텐트프로바이더를 선언합니다.

ContentProvider를 확장한 클래스 작성
다음과 같은 6개의 추상 메소드를 구현합니다.
query()
커서를 리턴합니다. 안드로이드에서는 SQLiteCursor와 같은 커서 구현을 여러개 제공합니다.
insert()

update()
delete()
getType()
onCreate()

이들 ContentProvider 메소드들은 다른 프로세스나 쓰레드에서 다양한 ContentResolver로 부터 호출될 수 있습니다. 그들은 쓰레드-안전한 방법으로 구현되어야 합니다.

여러분은 또한 데이터에 수정이 있을 때,
ContentResolver.notifyChange를 호출하기 원할 수도 있습니다.

클라이언트에서의 작업을 간단히하고, 더 접근을 쉽게할 수 있도록 하기 위해 몇 가지 해야 할일들이 있습니다.
1. CONTENT_URI 이름으로 상수를 만듭니다.

public static final Uri CONTENT_URI =
               Uri.parse("content://com.example.codelab.transporationprovider");

2. 컨텐트프로바이더가 클라이언트에게 리턴해줄 열이름들을 String 상수로 정의합니다.
"_id" (상수 _ID를 가지고)이름을 갖는 정수 열을 포함했는지 확인해야 합니다. 여러분이 고유 값을 갖는 다른 필드를 가지고 있는지의 여부와 상관없이 이 필드를 가져야 합니다.
만약 SQLite 데이터베이스를 사용하고 있다면 _ID는 다음과 같이 정의합니다.

INTEGER PRIMARY KEY AUTOINCREMENT

3. 주의를 기울여 각 열에 대한 데이터타입을 문서화합니다. 클라이언트는 이 정보를 데이터를 읽기 위해 사용합니다.

4. 만약 여러분이 새로운 데이터타입을 다룬다면, 여러분은 ContentProvider.getType의 리턴을 위해 MIME 타입을 정의해야 합니다.
단일 레코드일 때와 다수의 레코드일 때 URI 작성이 달라집니다.
단일 레코드인 경우, 다음과 같은 형식으로 작성합니다.
vnd.android.cursor.item/vnd.yourcompanyname.contenttype< code> 

content://com.example.transportationprovider/trains/122
vnd.android.cursor.item/vnd.example.rail

단수의 레코드인 경우, 다음과 같은 형식으로 작성합니다.
vnd.android.cursor.dir/vnd.yourcompanyname.contenttype

content://com.example.transportationprovider/trains
vnd.android.cursor.dir/vnd.example.rail

5. 테이블 자체에 넣기에는 너무 큰 데이터라면, 클라이언트에게 주어지는 것은 URI 입니다. 이 경우 내부적으로 ContentResolver가 사용할 완전한 경로를 갖는 _data 필드를 만듭니다.


컨텐트프로바이더 선언하기
<provider> 요소로 추가합니다.
name 애트리뷰트는 컨텐트프로바이더 클래스의 이름(full name)입니다.
authorities 애트리뷰트는 authority 부분입니다.

<provider name="com.example.autos.AutoInfoProvider"
          authorities="com.example.autos.autoinfoprovider"
          . . . />
</provider>


authorities는 path를 포함하지 않습니다. authorities는 프로바이더를 식별합니다.
pemmission 애트리뷰트에서는 데이터를 읽고 쓰는 것을 허용할 것인지를 설정합니다.
multiprocess 애트리뷰트는 동기화시키고자 할 경우에는 true로 설정합니다.

[출처] 컨텐트 프로바이더 [펌]|작성자 프리맨

덧글]

개발하시다가 온몸이 찌뿌둥하시면 아래 동영상을 따라 스트레칭을 한번하세요.


1. 목디스크 예방을 위한 목운동    ☞  http://jwandroid.tistory.com/192 

2. 손목터널증후군 손목스트레칭으로 예방합시다.     ☞  http://jwandroid.tistory.com/193

3. 개발자 여러분 허리를 세우세요 - 척추체조 1번     ☞  http://jwandroid.tistory.com/194

4. 개발자 여러분 허리를 세우세요 - 척추체조 2번     ☞  http://jwandroid.tistory.com/195

5. 개발자 여러분 허리를 세우세요 - 척추체조 3번     ☞   http://jwandroid.tistory.com/196

6. 개발자 여러분 허리를 세우세요 - 척추체조 4번     ☞  http://jwandroid.tistory.com/197