안드로이드 4대 컴포넌트 - Content Provider_2(content provider 생성)

권민주·2024년 11월 24일

안드로이드

목록 보기
10/23
post-thumbnail

안드로이드에는 필수적인 앱 기본 구성 요소로 activity, service, broadcast receiver, content provider 4가지를 가집니다. 이전 글에 이어 content provider의 생성 방법에 대해 설명하겠습니다.


Content provider는 중앙 데이터 저장소에 대한 접근을 관리하는 역할을 합니다. Android 애플리케이션에서 이를 구현하려면 하나 이상의 클래스를 작성해야합니다. 작성한 클래스 중 하나는 ContentProvider의 서브클래스를 구현하며, 이 클래스가 바로 다른 애플리케이션과 content provider 간의 인터페이스 역할을 하게 됩니다. 이외 보조적인 클래스가 필요한 경우도 있습니다.

또한 manifest 파일에 content provider 요소들을 추가해야 합니다. Content provider는 주로 다른 애플리케이션의 데이터를 사용하지만, 애플리케이션 내의 activity에서도 provider가 관리하는 데이터를 조회하고 수정할 수있습니다.

1. 구현 시작 전

1)provider 사용 여부 결정

구현하기 전에 실제로 content provider가 필요한지 고려해야 합니다. 다음 기능 중 하나 이상을 사용하려면 content provider를 구현해야 합니다

  • 복잡한 데이터나 파일을 다른 애플리케이션에 제공.
  • 사용자가 앱에서 다른 앱으로 복잡한 데이터를 복사할 수 있도록 허용.
  • 검색 프레임워크를 사용하여 사용자 지정 검색 제안을 제공.
  • 애플리케이션 데이터를 위젯에 노출.
  • AbstractThreadedSyncAdapter, CursorAdapter 또는 CursorLoader 클래스를 구현.

🛑🛑🛑
데이터베이스나 기타 유형의 영구 저장소를 사용하는 경우, 자신의 애플리케이션 내부에서만 사용하고 위의 기능을 전혀 사용하지 않다면 content provider는 필요하지 않습니다.

2)구현 과정

이제 다음 단계에 따라 provider를 구현합니다.

ⅰ) 데이터 저장할 방식을 정하기

Content provider는 두 가지 방식으로 데이터를 제공합니다

  • 파일 데이터
    일반적으로 사진, 오디오 또는 비디오와 같은 파일 형태의 데이터입니다. 파일은 앱의 개인 공간에 저장합니다. 다른 앱의 파일 요청에 응답하여 provider가 파일에 대한 접근 수단을 제공할 수 있습니다.

  • 구조화된 데이터
    일반적으로 데이터베이스, array 또는 이와 유사한 구조 형태의 데이터입니다. 행과 열의 테이블과 호환되는 형태로 데이터를 저장합니다. 행은 사람이나 인벤토리의 항목과 같은 엔터티를 나타냅니다. 열은 사람의 이름이나 항목 가격과 같은 엔터티에 대한 일부 데이터를 나타냅니다. 이러한 유형의 데이터를 저장하는 일반적인 방법은 SQLite 데이터베이스에 있지만 모든 유형의 영구 저장소에서도 사용할 수 있습니다.

ⅱ) Content provider 클래스 구현과 메서드 정의

Content provider 클래스는 사용자의 데이터와 나머지 Android 시스템 간의 인터페이스입니다. 사용하기로 정했다면 구현하면 됩니다.

ⅲ) Provider의 권한 문자열, 콘텐츠 URI 및 열 이름을 정의

권한 문자열, 콘텐츠 URI, 열 이름 외에도 provider의 앱이 intent를 처리하도록 intent action, 추가 데이터 및 플래그도 정의할 수 있습니다. 또한 데이터에 접근하려는 앱에 필요한 권한을 정의합니다. 이 모든 값을 별도의 contract class에서 상수로 정의하세요. 나중에 이 클래스를 다른 개발자에게 보여줄 수 있습니다.

ⅳ) 선택 과정 추가

샘플 데이터 또는 provider와 클라우드 기반 데이터 간에 데이터를 동기화할 수 있는 AbstractThreadedSyncAdapter 구현과 같은 다른 선택적 과정도 추가할 수 있습니다.

2.데이터 저장소 설계

1)저장 방식

Content provider는 구조화된 형식으로 저장된 데이터에 대한 인터페이스입니다. 인터페이스를 만들기 전에 데이터를 저장하는 방법을 결정해야 합니다. 원하는 형식으로 데이터를 저장한 다음 필요에 따라 데이터를 읽고 쓸 수 있도록 인터페이스를 설계할 수 있습니다. 다음은 Android에서 사용할 수 있는 데이터 저장 기술 중 일부입니다:

  • 구조화된 데이터를 사용하는 경우 SQLite와 같은 관계형 데이터베이스 또는 LevelDB와 같은 비관계형 키값 datastore를 고려하세요. 오디오, 이미지 또는 비디오 미디어와 같은 비정형 데이터를 사용하는 경우 데이터를 파일로 저장하세요. 여러 유형의 저장 방식을 조합하여 사용할 수도 있고 필요한 경우 단일 content provider를 사용하여 노출할 수 있습니다.

  • Android 자체 provider가 테이블 지향 데이터를 저장하는 데 사용하는 SQLite 데이터베이스 API에 대한 접근을 제공하는 Room 지속성 라이브러리와 상호 작용할 수 있습니다. 이 라이브러리를 사용하여 데이터베이스를 만들려면 RoomDatabase의 하위 클래스를 인스턴스화합니다. 저장소를 구현하기 위해 데이터베이스를 꼭 사용할 필요는 없습니다. Provider는 관계형 데이터베이스와 유사한 테이블 집합으로 외부로 표시하지만 provider의 내부 구현을 위한 필요 요건은 아닙니다.

  • 파일 데이터를 저장하기 위해 Android에는 다양한 파일 지향 API가 있습니다. 음악이나 동영상과 같은 미디어 관련 데이터를 제공하는 provider를 설계하는 경우 테이블 데이터와 파일을 결합한 provider가 있습니다.

  • 가끔 단일 애플리케이션에 대해 두 개 이상의 content provider를 구현하면 이점을 얻을 수 있습니다. 예를 들어, 하나의 content provider를 사용하여 위젯과 일부 데이터를 공유하고 다른 애플리케이션과 공유하기 위해 다른 데이터 세트를 노출할 수 있습니다.

  • 네트워크 기반 데이터로 작업하려면 java.net 및 android.net 의 클래스를 사용하세요. 네트워크 기반 데이터를 데이터베이스와 같은 로컬 데이터 저장소에 동기화한 다음 데이터를 테이블 또는 파일로 제공할 수도 있습니다.

🛑🛑🛑
저장소 호환이 맞지 않아서 버전을 변경을 할 경우, 저장소에 새로운 버전 번호를 지정해야 합니다. 또한, 새로운 content provider를 구현하는 앱의 버전 번호도 증가시켜야 합니다. 앱 버전은 빌드 파일에 있는 버전 코드를 말합니다. 이 변경을 통해 시스템 다운그레이드가 발생할 때, 호환되지 않는 content provider를 가진 앱을 재설치하려고 할 때 시스템이 충돌하는 것을 방지할 수 있습니다.

2)데이터 설계 고려사항

Provider의 데이터 구조를 설계하기 위한 몇 가지 팁은 다음과 같습니다

  • 테이블 데이터에는 항상 provider가 각 행에 대해 고유한 숫자 값으로 유지하는 "기본 키" 열이 있어야 합니다. 이 값을 통해 외래 키를 사용하여 행을 다른 테이블의 관련 행에 연결할 수 있습니다. 기본키 열에는 어떤 이름도 사용할 수 있지만, provider 쿼리 결과를 ListView에 연결하려면 검색된 열 중 하나에 _ID라는 이름이 있어야 하므로 BaseColumns._ID를 사용하는 것이 가장 좋습니다.

  • 만약 비트맵 이미지나 파일 기반의 매우 큰 데이터를 제공하려면, 데이터를 테이블에 직접 저장하는 대신 파일에 저장하고 간접적으로 제공해야 합니다. 이렇게 할 경우, 사용자에게 provider의 데이터를 접근하기 위해 ContentResolver 파일 메서드를 사용해야 한다는 것을 알려야 합니다.

  • 이진 대형 객체(BLOB) 데이터 유형을 사용하면 크기가 다르거나 구조가 다른 데이터를 저장할 수 있습니다. 예를 들어 BLOB 열을 사용하여 프로토콜 버퍼 또는 JSON 구조를 저장할 수 있습니다.

🛑🛑🛑
BLOB를 사용하여 스키마 독립 테이블을 구현할 수도 있습니다. 이 유형의 테이블에서는 기본 키 열, MIME 유형 열, BLOB 유형의 하나 이상의 일반 열로 정의할 수 있습니다. BLOB 열의 데이터의 의미는 MIME 유형 열의 값으로 표시됩니다. 이렇게 하면 동일한 테이블에 다양한 행 유형을 저장할 수 있습니다. 연락처 provider의 "data" 테이블 ContactContract.Data는 스키마 독립 테이블의 예입니다.

3. 콘텐츠 URI 설계

콘텐츠 URI는 provider의 데이터를 식별하는 URI입니다. 콘텐츠 URI에는 전체 provider에서 유일한 이름(권한)과 테이블 또는 파일(경로)을 가리키는 이름이 포함됩니다. 옵션 ID 부분은 테이블의 개별 행을 가리킵니다. Content provider의 모든 데이터 접근 메서드에는 인수로 콘텐츠 URI를 받습니다. 이를 통해 접근할 테이블, 행 또는 파일을 결정할 수 있습니다.

1)권한 설계

Provider는 일반적으로 Android 내부 이름으로 사용되는 단일 권한을 가지고 있습니다. 다른 provider와의 충돌을 방지하려면 인터넷 도메인 소유권(반대로)을 provider 권한의 기본으로 사용합니다. 이 권장 사항은 Android 패키지 이름에도 적용되므로 provider 권한을 provider가 포함된 패키지 이름의 확장으로 정의할 수 있습니다. 예를 들어, Android 패키지 이름이 com.example.<appname> 인 경우 provider에 com.example.<appname>.provider 권한을 부여합니다.

2)경로 구조 설계

개발자는 일반적으로 개별 테이블을 가리키는 경로를 추가하여 권한에서 콘텐츠 URI를 생성합니다. 예를 들어, 테이블1과 테이블2의 두 개의 테이블이 있는 경우 이전 예제의 권한과 결합하여 com.example.<appname>.provider/table1com.example.<appname>.provider/table2의 콘텐츠 URI를 생성할 수 있습니다. 경로는 단일 세그먼트로 제한되지 않습니다. 여러 '/'를 사용하여 계층적인 경로를 만들 수 있습니다. 경로의 각 레벨은 반드시 테이블을 나타낼 필요는 없으며, 특정 항목이나 행을 식별하는 데 사용될 수도 있습니다

3)콘텐츠 URI ID 처리

관례에 따라 provider는 URI의 끝에 있는 행에 대한 ID 값을 가진 콘텐츠 URI를 수락하여 테이블의 단일 행에 대한 접근을 제공합니다. 또한 provider는 ID 값을 테이블의 _ID 열에 일치시키고 요청된 접근을 일치하는 행에 대해 수행합니다. 이 규칙은 provider에 접근하는 앱의 일반적인 설계 패턴을 용이하게 합니다.

앱은 provider에 대한 쿼리를 수행하고 CursorAdapter를 사용하여 결과 Cursor를 ListView에 표시합니다. CursorAdapter의 정의에 따르면 Cursor의 열 중 하나는 _ID여야 합니다. 그런 다음 사용자는 UI에서 표시된 행 중 하나를 선택하여 데이터를 조회하거나 수정합니다. 앱은 ListView를 지원하는 Cursor에서 해당 행을 가져오고, 이 행의 _ID값을 가져와 콘텐츠 URI에 추가한 뒤, 접근 요청을 provider에게 보냅니다. 그러면 provider는 사용자가 선택한 정확한 행에 대해 조회 또는 수정을 수행할 수 있습니다.다.

4)콘텐츠 URI 패턴

수신되는 콘텐츠 URI에 대해 수행할 작업을 선택하는 데 도움이 되도록 provider API에는 콘텐츠 URI 패턴을 정수 값에 매핑하는 편의 클래스 UriMatcher가 포함되어 있습니다. 특정 패턴과 일치하는 콘텐츠 URI 또는 URI에 대해 switch 문에서 정수 값을 이용하여 원하는 작업을 선택할 수 있습니다.

콘텐츠 URI 패턴은 와일드카드 문자를 사용하여 콘텐츠 URI와 일치합니다

  • '*' 는 모든 길이의 유효한 문자 문자열이면 일치.
  • '#' 는 모든 길이의 숫자 문자열이면 일치.

콘텐츠 URI 처리를 설계 및 코딩 예로, 테이블을 가리키는 다음의 콘텐츠 URI를 인식하고 com.example.app.provider 권한을 가진 provider를 가정하겠습니다.

  • content://com.example.app.provider/table1: table1 테이블
  • content://com.example.app.provider/table2/dataset1: dataset1 테이블
  • content://com.example.app.provider/table2/dataset2: dataset2 테이블
  • content://com.example.app.provider/table3: table3 테이블

Provider는 콘텐츠 URI에 행 ID가 추가된 경우도 인식합니다. 예를 들어, content://com.example.app.provider/table3/1은 table3에서 ID가 1인 행을 식별하는 URI입니다.

다음과 같은 콘텐츠 URI 패턴도 가능합니다.

content://com.example.app.provider/*
Provider의 모든 content URI 일치

content://com.example.app.provider/table2/*
dataset1과 dataset2 테이블의 콘텐츠 UIR과 일치하지만 table1 이나 table3 콘텐츠 URI과는 일치하지 않음.

content://com.example.app.provider/table3/#
content://com.example.app.provider/table3/6 같이 6으로 식별되는 행처럼 table3의 단일 행의 콘텐츠 URI과 일치.

다음 코드는 UriMatcher의 메서드들이 어떻게 작동하는지 보여줍니다. 이 코드는 URI를 처리할 때, 테이블 전체를 위한 URI와 단일 행을 위한 URI를 다르게 처리합니다. 테이블을 위한 URI는 content://<authority>/<path> 패턴을 사용하고 단일 행을 위한 URI content://<authority>/<path>/<id> 패턴을 사용합니다.

addURI() 메서드는 권한와 경로를 정수 값에 매핑합니다. match() 메서드는 특정 URI에 대해 해당하는 정수 값을 반환합니다. 그리고 switch 문을 사용해 반환된 정수 값을 기준으로 테이블 전체를 조회할지, 단일 레코드를 조회할지 선택합니다.

public class ExampleProvider extends ContentProvider {
...
    //UriMatcher 객체 생성
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         *여기에서 provider가 인식하는 모든 콘텐츠 URI 패턴을 처리하기 위해 addURI()호출
	     * 이 코드의 경우 테이블 3에 대한 호출만 표시됩니다.
         */

       //테이블 3의 여러 행에 대해 정수 1 설정. 여기서는 와일드카드 사용 안함.
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * 단일 행에 대해 2로 설정. 여기서는 # 와일드카드 사용
         * content://com.example.app.provider/table3/3 일치
         * content://com.example.app.provider/table3 불일치.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // ContentProvider.query() 구현
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * 조회할 테이블과 정렬 순서는 들어오는 URI 반환 코드에 따라 선택. 
	     * 여기에서도 table3에 대한 구문만 표시.
         */
        switch (uriMatcher.match(uri)) {


            // URI가 table3일 경우
            case 1:
                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // URI가 table3의 단일 행일 경우            
	        case 2:
                /*
                 * 해당 URI가 단일 행 포함하기에 _ID 값 존재.
                 * URI의 마지막 경로를 통해 _ID 값 얻음
                 * 쿼리의 WHERE 절에 값 추가
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // URI 인식 불가일 경우에 대한 에러 처리
        }
        // 실제 쿼리 실행 코드
    }

ContentUris 클래스콘텐츠 URI의 id 부분을 처리하기 위한 편의 메서드를 제공합니다. 예를 들어 ContentUris.appendId() 있습니다.

Uri 및 Uri.Builder 클래스는 기존 Uri 객체를 파싱하고 새로운 Uri 객체를 생성하기 위한 편의 메서드를 포함하고 있습니다. 위 코드에서 사용한 uri.getLastPathSegment()Uri.parse()등이 있습니다.

4. ContentProvider 클래스 구현

ContentProvider 인스턴스는 다른 애플리케이션의 요청을 처리하여 구조화된 데이터 세트에 대한 접근을 관리합니다. 모든 형태의 접근은 결국 ContentResolver를 호출한 다음 구체적인 메서드의 ContentProvider를 호출하여 접근을 얻습니다.

1)필요 메서드

추상 클래스 ContentProvider는 여섯 개의 추상 메서드를 정의하며, 이를 구체적인 서브클래스에서 구현해야 합니다. onCreate()를 제외한 이러한 모든 메서드는 content provider에 접근하려고 시도하는 클라이언트 애플리케이션에 의해 호출됩니다.

  • query()
    Provider를 통해 데이터 검색. 인수를 사용하여 쿼리할 테이블, 반환할 행과 열, 결과 정렬 순서 선택. 데이터를 cursor 개체로 반환.

  • insert()
    Provider에 새 행을 삽입. 인수를 사용하여 대상 테이블을 선택하고 사용할 열 값을 가져옴. 새로 삽입한 행의 콘텐츠 URI를 반환.

  • update()
    Provider의 기존 행을 업데이트. 인수를 사용하여 업데이트할 테이블과 행을 선택하고 업데이트된 열 값을 가져옴. 업데이트된 행의 수를 반환.

  • delete()
    Provider에서 행을 삭제. 인수를 사용하여 테이블과 삭제할 행을 선택. 삭제한 행의 수를 반환.

  • getType()
    콘텐츠 URI에 대응하는 MIME 타입 리턴

  • onCreate()
    Provider 초기화. Android 시스템은 provider 생성 즉후 이 메서드를 호출. Provider는 ContentResolver 개체가 접근을 시도할 때까지 생성되지 않음.

이 메서드들은 동일한 이름을 가진 ContentResolver 메서드들과 같은 구조를 가집니다. 이 메서드를 구현할 때는 다음 사항을 고려해야 합니다.

  • onCreate()를 제외한 모든 메서드는 여러 스레드에서 동시에 호출될 수 있기 때문에 스레드 안전성을 고려해야 함.
  • 시간이 오래 걸리는 작업onCreate()에서 처리하지 말고, 실제로 필요할 때 지연해서 처리해야 함.
  • 메서드를 구현할 때 반드시 처리할 내용이 없다면, 예상된 데이터 타입을 반환하는 것만 해도 충분. 예를 들어, 데이터를 삽입하지 않으려면 insert() 호출을 무시하고 0을 반환 가능.

2)query() 구현

ContentProvider.query() 메서드는 Cursor 개체를 반환하거나 실패할 경우 예외를 던져야 합니다. 데이터 저장소로 SQLite 데이터베이스를 사용하는 경우, SQLiteDatabase 클래스에서 오버로드된 여러 query() 메서드 중 어느 것을 선택하든 Cursor를 반환받을 수 있습니다. 쿼리가 행과 일치하지 않는 경우 getCount() 메서드가 0을 반환하는 cursor 인스턴스를 반환합니다. 쿼리 프로세스 중에 내부 오류가 발생한 경우에만 null을 반환합니다.

SQLite 데이터베이스를 데이터 저장소로 사용하지 않는 경우, Cursor의 하위 클래스 중 하나를 사용합니다. 예를 들어, MatrixCursor 클래스는 각 행이 Object 배열인 cursor를 구현합니다. 이 클래스에서는 adRow()를 사용하여 새 행을 추가합니다.

Android 시스템은 프로세스 경계를 넘어 예외를 통신할 수 있어야 합니다. Android에는 쿼리 오류를 처리하는 데 유용한 다음 예외가 있습니다.

  • IllegalArgumentException: Provider가 잘못된 콘텐츠 URI를 수신하는 경우 사용
  • NullPointerException

3)insert() 구현

insert() 메서드는 ContentValues 인수의 값을 사용하여 해당 테이블에 새 행을 추가합니다. 열 이름이 ContentValues 인수에 없는 경우를 대비하여 provider 코드 또는 데이터베이스 스키마에 기본값을 설정할 수 있습니다. 이 메서드는 새 행의 콘텐츠 URI를 반환합니다. 이를 구성하려면 withAppendId()를 사용하여 새 행의 기본 키(_ID)를 테이블의 콘텐츠 URI에 추가합니다.

4)delete() 구현

delete()를 통해 데이터 저장소에서 행을 삭제할 필요가 없습니다. Provider와 동기화 adapter를 사용하는 경우 삭제된 행을 완전히 제거하지 않고 "delete" 플래그로 표시할 수 있습니다. 동기화 adapter는 삭제된 행을 확인하고 서버에서 제거한 후 provider에서 삭제할 수 있습니다.

5)update() 구현

update() 메서드는 insert()에서 사용한 것과 동일한 ContentValues 인수와 delete()ContentProvider.query()에서 사용한 것과 동일한 선택 및 선택 Args 인수를 사용합니다. 이렇게 하면 이러한 메서드 간에 코드를 재사용할 수 있습니다.

6)onCreate() 구현

Android 시스템은 provider를 시작할 때 OnCreate()를 호출합니다. 이 메서드로 빠르게 실행되는 초기화 작업만 수행하고 provider가 실제로 데이터 요청을 받을 때까지 데이터베이스 생성 및 데이터 로딩을 연기합니다. onCreate()에서 긴 작업을 수행하면 provider의 시작 속도가 느려집니다. 그러면 provider 다른 애플리케이션에 대해 응답하는 속도가 느려집니다.

다음 코드는 ContentProvider.onCreate()Room.databaseBuilder() 간의 상호 작용을 보여줍니다. 데이터베이스 개체가 구축되고 데이터 접근 개체를 처리하는 ContentProvider.onCreate()의 구현을 보여줍니다:

public class ExampleProvider extends ContentProvider

    // Room 데이터베이스 참조 정의
    private AppDatabase appDatabase;
    //데이터 베이스 연산 수행을 위한 데이터 접근 객체 정의
    private UserDao userDao;
    // 데이터 베이스 이름 정의
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // 새로운 데이터베이스 객체 생성
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // 데이터 베이스 연산 수행을 위한 데이터 접근 객체 얻기
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // provider의 삽입 메서드 구현
    public Cursor insert(Uri uri, ContentValues values) {
	//데이터 삽입, 오류 조건 처리 등에 사용할 DAO를 결정할 코드
    }
}

5. ContentProvider MIME 유형 구현

ContentProvider 클래스는 MIME 유형을 반환하는 두 개의 메서드를 가집니다.

  • getType()
    모든 provider 구현에 대해 필수 메서드 중 하나

  • getStreamTypes()
    Provider가 파일을 제공할 때 구현해야하는 메소드

1)테이블의 MIME 유형

getType() 메서드는 매개변수의 콘텐츠 URI 인수가 나타내는 데이터 유형을 설명하는 문자열을 MIME 형식으로 반환합니다. URI 인수는 특정 URI 뿐만 아니라 패턴 형식일 수 있습니다. 이 경우 해당 패턴과 일치하는 콘텐츠 URI와 관련된 데이터 유형을 반환합니다. 글자, HTML 또는 JPEG와 같은 일반적인 유형의 데이터의 경우, getType()은 해당 데이터에 대한 표준 MIME 유형을 반환합니다. 이러한 표준 유형의 전체 목록은 아래의 웹사이트에서 확인할 수 있습니다.

🛑🛑🛑
IANA MIME Media Types
https://www.iana.org/assignments/media-types/media-types.xhtml

테이블 데이터의 행이나 여러 행을 가리키는 content URI의 경우, getType()은 Android의 사용자 지정 MIME 형식으로 MIME 유형을 반환합니다.

  • 유형 부분 : vnd
  • 하위 부분
    • URI 패턴이 단일 행을 가리키는 경우: android.cursor.item/
    • URI 패턴이 여러 행을 가리키는 경우: android.cursor.dir/
  • Provider 별 부분: vnd.<name>.<type>

<name>는 전역으로 고유한 값이며 회사 이름 또는 앱의 Android 패키지 이름의 일부를 추천합니다. <type>은 해당 URI 패턴에 대한 고유한 값이며 URI와 연결된 테이블을 식별하는 문자열을 추천합니다. 예를 들어 provider의 권한이 com.example.app.provider이면, 테이블의 이름은 table1이고 table1의 여러 행에 대한 MIME 유형은 다음과 같습니다
vnd.android.cursor.dir/vnd.com.example.provider.table1

테이블1의 단일 행에 대한 MIME 유형은 다음과 같습니다.
vnd.android.cursor.item/vnd.com.example.provider.table1

2)파일의 MIME 유형

Provider가 파일을 제공하는 경우 getStreamTypes()를 구현해야합니다. 이 메서드는 provider가 특정 콘텐츠 URI에 반환된 파일의 MIME 형식으로된 문자열 배열을 반환합니다. 필터 인수로 MIME 유형을 전달하면, MIME 유형을 필터링하여 클라이언트가 처리하고자 하는 MIME 유형만 반환합니다.

예를들어 provider는 JPG, PNG, 그리고 GIF 파일 형식의 이미지를 제공합니다. 만약 애플리케이션이 ContentResolver.getStreamTypes()를 호출하여 "image/*" 형태의 필터 문자열을 전달하면, ContentProvider.getStreamTypes()는 "image" 대해 다음과 같은 배열을 반환합니다.

{ "image/jpeg", "image/png", "image/gif"}

만약 앱이 JPG 파일만 원한다면 */jpeg 필터 문자열을 ContentResolver.getStreamTypes()에 전달하면 getStreamTypes()는 다음을 반환합니다.

{"image/jpeg"}

만약 provider가 필터 문자열에서 요청된 MIME 유형 중 아무것도 제공하지 않거나, 해당 데이터가 없거나 메서드를 구현하지 않았다면, getStreamTypes()null을 반환합니다.

6. Contract class 구현

Contract class는 provider와 관련된 URI, 열 이름, MIME 유형 및 기타 메타 데이터에 대한 상수 정의를 포함하는 public final class입니다. 클래스는 provider와 다른 앱 간에 URI, 열 이름 등의 실제 값이 변경되더라도 provider가 올바르게 접근할 수 있도록 보장하는 contract을 설정합니다.

Contract class는 일반적으로 상수에 대해 기억하기 쉬운 이름을 가지고 있으므로 개발자가 열 이름이나 URI에 잘못된 값을 사용할 가능성이 적기 때문에 개발자에게 도움이 됩니다. 클래스이기 때문에 이를 설명할 Javadoc 문서가 포함될 수 있습니다. Android Studio와 같은 통합 개발 환경에서는 contract class에서 상수 이름을 자동으로 완성하고 상수에 대한 Javadoc을 표시할 수 있습니다.

개발자는 애플리케이션에서 contract class의 클래스 파일에 직접 접근할 수 없지만, 제공된 JAR 파일에서 해당 클래스를 정적으로 컴파일할 수 있습니다. 즉, 파일 자체에는 접근할 수 없지만 파일 안에 정의된 상수로 통해 접근할 수 있습니다.

Contract class의 예시로는 ContactsContract 클래스와 ContactsContract의 중첩 클래스입니다.

7. Content provider 권한 구현

1)보안

Content provider에 관한 보안에 대해 다음과 같이 간단히 설명할 수 있습니다.

  • 기본적으로 장치의 내부 저장소에 저장된 데이터 파일은 앱과 provider에게 비공개로 제공
  • 생성한 SQLiteDatabase 데이터베이스는 애플리케이션 및 provider에 비공개로 제공.
  • 기본적으로 외부 저장소에 저장하는 데이터 파일은 public 및 world-readable. 다른 앱에서 API 호출을 사용하여 파일을 읽고 쓸 수 있으므로 content provider를 사용하여 외부 저장소의 파일에 대한 접근을 제한할 수 없음.
  • 장치의 내부 저장소에서 파일 또는 SQLite 데이터베이스를 열거나 생성하는 메소드는 개발자의 실수로 인해 다른 모든 앱에 대한 읽기 및 쓰기 접근 권한을 모두 부여할 수 있음. 내부 파일이나 데이터베이스를 content provider의 저장소로 사용하고 해당 파일 또는 데이터베이스에 "world-readable" 또는 "world-writeable" 권한을 부여하면, manifest에서 content provider에 대해 설정한 권한이 데이터를 보호하지 못함. 내부 저장소의 파일과 데이터베이스에 대한 기본 접근 권한은 "private". 이를 content provider의 저장소용으로 변경하면 안 됨.

2) 권한

기본적으로 provider에는 권한이 설정되어 있지 않으므로 기본 데이터가 비공개인 경우에도 모든 앱이 provider로부터 읽거나 쓸 수 있습니다. 이를 변경하려면 manifest 파일에서 <provider> 요소의 속성 또는 하위 요소를 사용하여 provider에 대한 권한을 설정합니다. 전체 provider, 특정 테이블, 특정 레코드 또는 세 가지 모두에 적용되는 권한을 설정할 수 있습니다.

Manifest 파일에 하나 이상의 <permission> 요소가 있는 provider에 대한 권한을 정의합니다. Provider에게 고유한 권한을 만들려면 android:name 속성에 대한 Java 스타일 범위를 사용합니다. 예를 들어 읽기 권한 이름을 다음과 같이 정할 수 있습니다.
com.example.apppprovider.permission.READ_PROVIDER

다음 목록은 전체 provider에게 적용되는 권한부터 시작하여 세분화되는 provider 권한의 범위를 설명합니다. 세분화된 권한이 더 큰 권한보다 우선합니다.

ⅰ)단일 읽기-쓰기 provider 수준 권한

전체 provider에 대한 읽기 및 쓰기 접근을 모두 제어하는 하나의 권한으로, <provider> 요소의 android:permission 속성으로 지정되어 있습니다.

ⅱ)분리된 읽기-쓰기 provider 수준 권한

전체 provider 대한 읽기 권한과 쓰기 권한을 각각 설정할 수 있습니다. <provider> 요소의 adroid:readPermissionandroid:writePermission 속성을 사용하여 지정합니다. 이 속성은ⅰ)의 android:permission 권한보다 우선합니다.

ⅲ)경로 수준 권한

콘텐츠 URI에 대해 읽기, 쓰기, 또는 읽기/쓰기 권한을 설정하는 것을 말합니다. 이는 <provider> 요소의 하위 요소인 <path-permission>을 사용하여 지정할 수 있습니다. 제어하려는 각 콘텐츠 URI에 대해 총 3가지의 읽기와 쓰기 권한, 읽기 권한, 쓰기 권한을 설정할 수 있습니다. 읽기 및 쓰기 권한이 읽기와 쓰기 권한보다 우선 적용됩니다. 경로 수준 권한은 공급자 수준 권한보다 우선합니다.

ⅳ)임시 권한

애플리케이션이 일반적으로 요구되는 권한 없이도 임시로 접근할 수 있도록 허용하는 권한 수준입니다. 이 임시 접근 기능은 애플리케이션이 manifest에서 요청해야 하는 권한의 수를 줄여줍니다. 임시 권한을 활성화하면, provider 데이터를 지속적으로 접근하는 애플리케이션만 영구적인 권한으로 설장할 수 있습니다.

예를 들어 이메일 provider과 앱을 구현하고 외부 이미지 뷰어 애플리케이션이 provider의 사진 첨부 파일을 표시하도록 허용하려는 경우, 필요한 권한을 요구하지 않고도 이미지 뷰어에 필요한 접근 권한을 제공할 수 있습니다. 이를 위해 사진의 콘텐츠 URI에 대한 임시 권한을 설정할 수 있습니다. 사용자가 사진을 표시하려고 할 때 이메일 앱이 사진의 콘텐츠 URI와 권한 플래그를 포함한 intent을 이미지 뷰어로 전송하면 됩니다. 그러면 이미지 뷰어는 일반적으로 provider에 대한 읽기 권한이 없어도 이메일 provider에 쿼리를 실행하여 사진을 가져올 수 있습니다.

임시 권한을 활성화하려면, <provider> 요소의 android:grantUriPermissions 속성을 설정하거나 <provider> 요소에 하나 이상의 <grant-uri-permission> 자식 요소를 추가하세요. 콘텐츠 URI와 관련된 임시 권한에 대한 지원을 제거할 때는 Context.revokeUriPermission()을 호출하세요. `

android:grantUriPermissions 속성의 값은 provider의 어느 정도가 접근 가능해지는지를 결정합니다. 속성이 "true"로 설정된 경우, 시스템은 provider의 전체에 대해 임시 권한을 부여하며, provider 수준 또는 경로 수준 권한에 의해 요구되는 다른 권한을 무시합니다. 이 플래그가 "false"로 설정된 경우, <provider> 요소에 <grant-uri-permission> 자식 요소를 추가하세요. 각 자식 요소는 임시 접근 권한이 부여되는 콘텐츠 URI 또는 URI를 지정합니다. android:grantUriPermissions 속성이 존재하지 않으면 기본적으로 "false"로 간주됩니다.

애플리케이션에 임시 접근 권한을 위임하려면 intent가 FLAG_GRANT_READ_URI_PERMISSION 플래그, FLAG_GRANT_WRITE_URI_PERMISSION 플래그, 또는 두 플래그 모두를 포함해야 합니다. 이들은 setFlags() 메서드를 사용해 설정됩니다.

8. <provider> 요소

Activity 및 Service 구성 요소와 마찬가지로 ContentProvider의 하위 클래스는 <provider> 요소를 사용하여 애플리케이션 manifest 파일에 정의됩니다. Android 시스템은 요소에서 다음 정보를 가져옵니다

1)권한(android:authorities)

시스템 내의 전체 provider를 식별하는 이름입니다.

2)Provider 클래스 이름(android:name)

ContentProvider 구현 클래스 이름입니다.

3)권한

Provider의 데이터에 접근하기 위해 다른 앱이 가져야 하는 권한을 지정하는 속성입니다

  • android:grantUriPermissions: 임시 권한 플래그
  • android:permission: 단일 provider 범위의 읽기/쓰기 권한
  • android:readPermission: 단일 provider 범위의 읽기 권한
  • android:writePermission: 단일 provider 범위의 쓰기 권한

4)시작 및 제어 속성

이러한 속성은 Android 시스템이 provider를 시작하는 방법과 시기, provider의 프로세스 특성 및 기타 런타임 설정을 결정합니다.

  • android:enabled: 시스템이 provider를 시작하도록 허용하는 플래그
  • android:exported: 다른 앱이 이 provider를 사용하도록 허용하는 플래그
  • android:initOrder: 동일한 프로세스의 다른 provider와 비교하여 해당 provider가 시작되는 순서
  • android:multiProcess: 시스템이 호출 클라이언트와 동일한 프로세스에서 provider를 시작하도록 허용하는 플래그
  • android:process: provider가 실행되는 프로세스의 이름
  • android:syncable: provider의 데이터가 서버의 데이터와 동기화되어야 함을 나타내는 플래그

🛑🛑🛑
이러한 속성의 전체는 <provider> 요소 문서에서 확인할 수 있습니다.
https://developer.android.com/guide/topics/manifest/provider-element

5)정보 속성

Provider의 아이콘 및 레이블를 선택할 수 있습니다.

  • android:icon: Provider의 아이콘이 포함된 drawable 리소스입니다. 아이콘은 Settings > Apps > All의 앱 목록에서 provider의 레이블 옆에 나타납니다.

  • android:label: Provider, 해당 데이터 또는 둘 다를 설명하는 정보 라벨입니다. 이 라벨은 Settings > Apps > All 앱 목록에 표시됩니다.

해당 정보 속성의 전체 사항도 <provider> 요소 문서에서 확인할 수 있습니다.

6)패키지 가시성

Android 11 이상을 대상으로 하는 경우, 패키지 가시성에 대해 추가 구성 필요 사항을 확인해야 합니다. Content provider에 쿼리해야 하지만 특정 패키지 이름을 모르는 경우, 다음 코드에 표시된 것처럼 <provider> 요소에서 provider 권한을 선언할 수 있습니다.

<manifest package="com.example.suite.enterprise">
    <queries>
        <provider android:authorities="com.example.settings.files" />
    </queries>
    ...
</manifest>

만약 <queries> 요소에 <provider> 요소를 포함한다면, Android Studio에서 <provider> 요소와 관련된 편집기 경고를 볼 수 있습니다. 하지만 Android 11 이상을 사용 중이라면 빌드에는 영향이 없으므로 경고를 무시해도 됩니다. 앱이 <queries>에 권한을 잘못 설정하거나 누락했을 경우, 다른 앱의 content provider에 접근할 수 없게 됩니다. 하지만 Android 11(API 30)부터 앱이 다른 앱에 접근하거나 content provider를 사용할 수 있는 권한이 명시적으로 제한되었기 때문입니다.

하나의 <queries> 요소 내에 여러 provider 선언할 수 있습니다. <queries> 요소 내에서 하나 이상의 <provider> 요소를 선언할 수 있으며, <provider> 요소는 단일 provider authority 또는 세미콜론(;)으로 구분된 provider authority 목록을 포함할 수 있습니다.

9. Intent와 데이터 접근

애플리케이션은 intent를 가지고 content provider에 간접적으로 접근할 수 있습니다. 이 애플리케이션은 ContentResolver 또는 ContentProvider의 어떤 메서드도 호출하지 않습니다. 대신에 activity를 시작하는 intent를 전송하며, 해당 activity는 외부 앱의 activity 뿐만 아니라 provider을 포함하는 앱 내의 activity인 경우도 많습니다. 목적지 activity는 UI에서 데이터를 검색하고 표시하는 역할을 합니다. Intent의 action에 따라 목적지 activity는 사용자가 provider의 데이터를 수정하도록 유도할 수도 있습니다. Intent에는 목적지 activity가 UI에 표시하는 "extras" 데이터가 포함될 수도 있습니다. 사용자는 변경된 데이터를 extras를 사용하여 provider의 데이터를 수정할 수도 있습니다.

Intent 접근를 사용하여 데이터 무결성을 도울 수 있습니다. Provider는 엄격하게 정의된 비즈니스 논리에 따라 데이터를 삽입, 업데이트 및 삭제해야 할 수도 있습니다. 이 경우 다른 애플리케이션이 사용자의 데이터를 직접 수정하도록 허용하면 잘못된 데이터로 이어질 수 있습니다.

개발자가 intent 접근을 사용하려면 문서화를 철저히 해야 합니다. 애플리케이션의 UI를 사용한 intent 접근이 코드로 데이터를 수정하는 것보다 나은 이유를 설명해야 합니다.

출처 : https://developer.android.com/

이전 CONTENT PROVIDER 포스트
<contentProvider_1(개념)>

다음 CONTENT PROVIDER 포스트
<contentProvider_3(예제)>
<contentProvider_4(calendar 개념)>
<contentProvider_5(calendar 예제)>
<contentProvider_6(비동기 방식)>
<contentProvider_7(contact)>

profile
안드로이드 개발자:D

0개의 댓글