JuHyang

Android Content Provider - 안드로이드 공식 개발자 문서 본문

Android/Theory

Android Content Provider - 안드로이드 공식 개발자 문서

Ju_Hyang 2019. 12. 7. 22:49
 
 
  • Content Provider
    • 구조화된 데이터 세트의 엑세스를 관리합니다. 데이터를 캡슐화하여 데이터 보안을 정의하는데 필요한 메커니즘을 제공하기도 합니다.
    • 한 프로세스의 데이터에 다른 프로세스에서 실행 중인 코드를 연결하는 표준 인터페이스 입니다.
    • Content Provider 내의 데이터에 액세스하고자 하는 경우, 애플리케이션의 Context에 있는 ContentResolver 객체를 사용하여 클라이언트로서 제공자와 통신을 주고 받으면 됩니다.
    • ContentResolver 객체가 제공자 객체와 통신하며, 이 객체는 ContentProvider 를 구현하는 클래스의 인스턴스입니다.
    • 제공자 객체가 클라이언트로 부터 데이터 요청을 받아 요청된 작업을 수행하고 결과를 반환합니다.
    • 데이터를 다른 애플리케이션과 공유할 생각이 없으면 나름의 제공자를 개발하지 않아도 된다. 그러나, 자체 애플리케이션에서 사용자 지정 검색 제안을 제공하려면 나름의 제공자가 꼭 필요하다. 또한, 복잡한 데이터나 파일을 자신의 애플리케이션에서 다른 애플리케이션으로 복사하여 붙여넣고자 하는 경우에도 나름의 제공자가 필요합니다.
    • Android 자체에 오디오, 동영상, 이미지 및 개인 연락처 정보 등의 데이터를 관리하는 콘텐츠 제공자가 포함되어 있다. 그중 몇가지를 나열한 것을 android.provider 패키지에 대한 참조 문서에서 확인 할 수 있습니다.(https://developer.android.com/reference/android/provider/package-summary.html?hl=ko) 이와 같은 제공자에는 몇가지 제약이 있지만, 어느 Android 애플리케이션에나 액세스 할 수 있습니다.
  • Content provider basis
    • 컨텐츠 제공자는 중앙 리포지토리 로의 데이터 액세스를 관리합니다. 제공자는 Android 애플리케이션의 일부이며, 이는 흔히 데이터 작업을 위한 고유의 UI를 제공합니다. 그러나 콘텐츠 제공자는 기본적으로 다른 애플리케이션이 사용하도록 만들어진 것입니다. 이들은 제공자 클라이언트 객체를 사용하여 제공자에 액세스합니다. 제공자와 제공자 클라이언트가 결합되면 데이터에 하나의 인터페이스를 제공하여 이것이 프로세스간 통신과 보안 데이터 액세스도 처리합니다.
    • 개요
      • 콘텐츠 제공자는 외부 애플리케이션에 데이터를 표시하며, 이때 데이터는 관계형 데이터베이스에서 찾을 수 있는 테이블과 유사한 하나 이상의 테이블로서 표시됩니다. 한 행은 제공자가 수집하는 어떤 유형의 데이터 인스턴스를 나타내며, 행 안의 각 열은 인스턴스에 대해 수집된 개별적인 데이터를 나타냅니다.
      • 예를 들어 Android 플랫폼 안에 내장된 여러 제공자 중에 사용자 사전이라는 것이 있습니다. 이것은 사용자가 보관하고 싶어하는 비 표준 단어의 철자를 저장합니다. 
      • 위의 표에서 각 행은 일반 사전에 나오지 않는 단어의 인스턴스를 나타냅니다. 각 열은 해당 단어에 대한 일부 데이터를 나타냅니다. 예를 들어 단어가 처음 나온 로케일 등을 들 수 있습니다. 열 헤더는 제공자에 저장된 열 이름입니다. 행의 로케일을 참조 하려면 그 행의 locale 열을 참조합니다. 이 제공자의 경우, _ID 열은 제공자가 자동으로 유지하는 “기본 키”열 역할을 합니다.
    • 제공자 액세스
      • 애플리케이션은 콘텐츠 제공자로부터 데이터에 ContentResolver 클라이언트 객체를 이용해 액세스 합니다. 이 객체에는 제공자 객체 내의 같은 이름을 가진 메서드를 호출하는 메서드가 있습니다. 이는 ContentProvider의 구체적인 서브클래스 중 하나의 인스턴스 입니다. ContentResolver 메서드는 영구 저장소의 기본적인 “CRUD” (생성, 검색, 업데이트, 삭제) 기능을 제공합니다.
      • 클라이언트 애플리케이션의 프로세스 내에 있는 ContentResolver 객체와 제공자를 소유하는 애플리케이션 내의 ContentProvider 객체가 자동으로 프로세스간 통신을 처리합니다. ContentProvider 또한 제공자의 데이터 리포지토리와 외부에 테이블로 표시되는 뎅터 모습 사이에서 추상화 계층 역할을 합니다.
      • 예를 들어 사용자 사전 제공자로부터 단어와 그에 해당하는 로케일 목록을 가져오려면, ContentProvider.query() 를 호출하면 됩니다. query() 메서드는 사용자 사전 제공자가 정의한 ContentProvider.query() 메서드를 호출합니다. 다음의 몇줄의 코드는 ContentResolver.query() 호출을 보여줍니다.
 
// Queries the user dictionary and returns results
 
mCursor = getContentResolver().query(
 
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
 
    mProjection,                        // The columns to return for each row
 
    mSelectionClause                    // Selection criteria
 
    mSelectionArgs,                     // Selection criteria
 
    mSortOrder);                        // The sort order for the returned rows
 
 
    • 콘텐츠 URI
      • 콘텐츠 URI는 제공자에서 데이터를 식별하는 URI 입니다. 콘텐츠 URI 에는 전체 제공자의 상징적인 이름 (제공자의 권한)과 테이블을 가리키는 이름(경로)이 포합됩니다. 제공자 내의 테이블에 액세스 하기 위해 클라이언트 메서드를 호출하는 경우, 그 테이블에 대한 콘텐츠 URI 는 인수 중 하나입니다.
      • 앞서 몇 줄의 코드에는 상수 CONTENT_URI 에 사용자 사전 “단어” 테이블의 콘텐츠 URI가 들어 있습니다. ContentResolver 객체가 이 URI의 권한을 파싱한 다음, 이를 이용해 제공자를 “확인” 합니다. 즉 이 권한을 알려진 제공자로 이루엊니 시스템 테이블과 비교하는 것입니다. 그러면 ContentResolver 가 쿼리 인수를 올바른 제공자에게 발송할 수 있습니다.
      • ContentProvider 는 콘텐츠 URI의 경로 부분을 사용하여 액세스할 테이블을 선택합니다. 제공자에는 보통 제공자가 노출하는 테입르마다 경로가 있습니다.
      • 앞선 몇줄의 코드에서 “단어” 테이블에 대한 완전한 URI 는 다음과 같습니다
      • content:://user_directory/words
      • 여기에서 user_directory 문자열은 제공자의 권한이고 words 문자열은 테이블의 경로입니다. 문자열 content://(구성표) 는 언제나 표시되며, 콘텐츠 URI로 이를 식별합니다.
      • 대다수의 제공자에서는 URI의 맨 끝에 ID값을 추가하면 테이블 내 하나의 행에 액세스 할 수 있스빈다. 예를 들어 사용자 사전에서 _ID가 4인 행을 검색하려면, 이 콘텐츠 URI를 사용하면 됩니다.
      • Uri singleUri = ContentUris.withAppendedId(UserDirectory.words.CONTENT_URI, 4)
      • 일련의 행을 검색한 다음 그 중 하나를 업데이트 하거나 삭제하고자 하는 경우 종종 ID 값을 사용합니다.
    • 제공자에서 데이터 검색
      • 제공자에서 데이터를 검색하려면 다음과 같은 기본 단계를 따르세요.
        • 제공자에 대한 읽기 권한을 요청합니다.
        • 제공자에게 쿼리를 보내는 코드를 정의합니다.
      • 읽기 액세스 권한 요청
        • 제공자에서 데이터를 검색하려면 애플리케이션에 해당 제공자에 대한 “읽기 권한”이 필요합니다. 런타임에는 이 권한을 요청할 수 없습니다. 대신 이 권한이 필요하다는 것을 메니페스트에 나타내야 합니다. 이때, <uses-permission> 요소와 제공자가 정의한 정확한 권한 이름을 사용하면 됩니다. 메니페스트에서 이 요소를 지정하는 것은 사실상 애플리케이션을 위해 이 권한을 “요청” 하는 것과 같습니다. 사용자가 애플리케이션을 설치할 때면, 이 요청을 암시적으로 허용하게 됩니다.
        • 사용 중인 제공자에 대한 읽기 권한의 정확한 이름과 해당 제공자가 사용하는 다른 액세스 권한의 이름을 찾아보려면 제공자의 문서를 찾아보세요.
        • 제공자에 액세스 하는 데 있어 권한이 어떤 역할을 하는지는 콘텐츠 제공자 권한 섹션에 더 자세하게 설명되어 있습니다.
        • 사용자 사전 제공자는 android.permission.READ_USER_DICTIONARY 권한을 자신의 메니페스트 파일에 정의합니다. 따라서 해당 제공자에서 읽기 작업을 하고자 하는 애플리케이션은 반드시 이 권한을 요청해야 합니다.
      • 쿼리 구성
        • 제공자에서 데이터를 검색할 때 다음 단계는 쿼리를 구성하는 것입니다. 다음은 첫 번째 스니펫은 사용자 사전 제공자에 액세스 하는 데 필요한 몇가지 변수를 정의합니다.
 
// A "projection" defines the columns that will be returned for each row
 
String[] mProjection =
 
{
 
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
 
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
 
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
 
};
 
 
 
// Defines a string to contain the selection clause
 
String mSelectionClause = null;
 
 
 
// Initializes an array to contain selection arguments
 
String[] mSelectionArgs = {"”};
 
        • 다음 스니펫은 사용자 사전 제공자를 예시를 사용하여 ContentProvider.query() 를 사용하는 방법을 보여줍니다. 제공자 클라이언트 쿼리는 SQL 쿼리와 비슷하며, 반환할 열 집합과 선택 기준 집합, 그리고 정렬 순서가 이 안에 들어있습니다.
        • 쿼리가 반환해야 할 열의 세트를 프로젝션 (변수 mProjection) 이라고 합니다.
        • 검색할 행을 나타내는 식은 선택 절과 선택 인수 분할되어 있스빈다. 선택 절은 논리와 부울 식, 열 이름과 값 (변수 mSelectionClause ) 의 조합입니다. 값 대신 대체 가능한 매개변수 ? 를 지정하면, 쿼리 메서드가 그 값을 선택 인수 배열에서 검색합니다 (변수 mSelectionArgs).
        • 다음 스니펫의 경우, 사용자가 단어를 입력하지 않으면 선택 절이 null로 설정되고, 쿼리는 제공자 안의 모든 단어를 반환합니다. 사용자가 단어를 입력하면 선택 절은 UserDictionary.Words.WORD + “ = ?” 로 설정되며 선택 인수의 첫 번째 요소가 사용자가 입력한 단어로 설정 됩니다.
 
/*
 
* This defines a one-element String array to contain the selection argument.
 
*/
 
String[] mSelectionArgs = {""};
 
 
 
// Gets a word from the UI
 
mSearchString = mSearchWord.getText().toString();
 
 
 
// Remember to insert code here to check for invalid or malicious input.
 
 
 
// If the word is the empty string, gets everything
 
if (TextUtils.isEmpty(mSearchString)) {
 
    // Setting the selection clause to null will return all words
 
    mSelectionClause = null;
 
    mSelectionArgs[0] = "";
 
 
 
} else {
 
    // Constructs a selection clause that matches the word that the user entered.
 
    mSelectionClause = UserDictionary.Words.WORD + " = ?";
 
 
 
    // Moves the user's input string to the selection arguments.
 
    mSelectionArgs[0] = mSearchString;
 
 
 
}
 
 
 
// Does a query against the table and returns a Cursor object
 
mCursor = getContentResolver().query(
 
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
 
    mProjection,                       // The columns to return for each row
 
    mSelectionClause                   // Either null, or the word the user entered
 
    mSelectionArgs,                    // Either empty, or the string the user entered
 
    mSortOrder);                       // The sort order for the returned rows
 
 
 
// Some providers return null if an error occurs, others throw an exception
 
if (null == mCursor) {
 
    /*
 
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
 
     * call android.util.Log.e() to log this error.
 
     *
 
     */
 
// If the Cursor is empty, the provider found no matches
 
} else if (mCursor.getCount() < 1) {
 
 
 
    /*
 
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
 
     * an error. You may want to offer the user the option to insert a new row, or re-type the
 
     * search term.
 
     */
 
 
 
} else {
 
    // Insert code here to do something with the results
 
 
 
}
 
      • 쿼리 결과 표시
        • ContentResolver.query() 클라이언트 메서드는 언제나 쿼리 선택 기준과 일치하는 행에 대해 쿼리 프로젝션이 지정한 열을 포함하는 Cursor 를 반환합니다. Cursor 객체가 자신이 포함한 행과 열에 무작위 읽기 액세스를 제공합니다. Cursor 메서드를 사용하면 행을 결과에서 반복할 수 있고, 각 열의 데이터 유형을 결정하며 열에서 데이터를 꺼내거나 결과의 다른 속성을 검토할 수도 있습니다. 일부 Cursor 구현은 제공자의 데이터가 변경될 경우, Cursor가 변경될 때 관찰자 객체 내의 메서드를 트리거 하는 경우 또는 두 가지가 한 번에 발생할 경우 자동으로 객체를 업데이트 합니다.
        • 선택 기준에 일치하는 행이 없으면, 제공자는 Cursor 를 반환합니다. 이 객체의 Cursor.getCount() 는 0 입니다.
        • 내부 오류가 발생하는 경우, 쿼리 결과는 특정 제공자에 따라 달라집니다. null 을 반환하기로 선택할 수도 있고, Exception 을 발생시킬 수도 있습니다.
        • Curor 는 행의 ‘목록’이므로, Cursor 의 콘텐츠를 표시하는 좋은 방법은 SimpleCursorAdapter 를 통해 ListView 에 연결하는 것입니다.
        • 다음 스니펫은 이전 스니펫의 코드에서 계속된 것입니다. 이는 해당 쿼리가 검색한 Cursor 가 들어있는 SimpleCursorAdapter 객체를 생성하며, 이 객체를 ListView 에 대한 어뎁터로 설정합니다.
 
// Defines a list of columns to retrieve from the Cursor and load into an output row
 
String[] mWordListColumns =
 
{
 
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
 
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
 
};
 
 
 
// Defines a list of View IDs that will receive the Cursor columns for each row
 
int[] mWordListItems = { R.id.dictWord, R.id.locale};
 
 
 
// Creates a new SimpleCursorAdapter
 
mCursorAdapter = new SimpleCursorAdapter(
 
    getApplicationContext(),               // The application's Context object
 
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
 
    mCursor,                               // The result from the query
 
    mWordListColumns,                      // A string array of column names in the cursor
 
    mWordListItems,                        // An integer array of view IDs in the row layout
 
    0);                                    // Flags (usually none are needed)
 
 
 
// Sets the adapter for the ListView
 
mWordList.setAdapter(mCursorAdapter);
 
      • 쿼리 결과에서 데이터 가져오기
        • 쿼리 결과를 단순히 표시만 하는 것보다 이를 다른 작업에 사용할 수 있습니다. 예를 들어, 사용자 사전에서 철자를 검색한 다음 이것을 다른 제공자 내에서 찾아볼 수 있습니다. 이렇게 하려면, Cursor 에서 행을 계속 반복하면 됩니다.
 
// Determine the column index of the column named "word"
 
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);
 
 
 
/*
 
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
 
* an internal error occurs. Other providers may throw an Exception instead of returning null.
 
*/
 
 
 
if (mCursor != null) {
 
    /*
 
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
 
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
 
     * exception.
 
     */
 
    while (mCursor.moveToNext()) {
 
 
 
        // Gets the value from the column.
 
        newWord = mCursor.getString(index);
 
 
 
        // Insert code here to process the retrieved word.
 
 
 
        ...
 
 
 
        // end of while loop
 
    }
 
} else {
 
 
 
    // Insert code here to report an error if the cursor is null or the provider threw an exception.
 
}
 
 
        • Cursor 구현에는 여러 개의 ‘get’ 메서드가 들어 있어 객체로부터 여러 가지 유형의 데이터를 검색합니다. 예를 들어 이전 스니펫에서는 getString() 을 사용합니다. 여기에는 해당 열의 데이터 유형을 나타내는 값으로 반환하는 getType() 메서드도 있습니다.
      • 콘텐츠 제공자 권한
        • 제공자의 애플리케이션은 해당 제공자의 데이터에 액세스하려면 다른 애플리케이션이 반드시 가지고 있어야 하는 권한을 지정할 수 있습니다. 이와 같은 권한을 통해 사용자는 한 애플리케이션이 어느 데이터에 액세스하려 시도할지 알 수 있습니다. 다른 애플리케이션은 제공자의 요구사항을 근거로 해당 제공자에 액세스하기 위해 필요한 권한을 요청합니다. 최종 사용자는 애플리케이션을 설치할 때 요청된 권한을 보게 됩니다.
        • 제공자의 애플리케이션이 아무 권한도 지정하지 않은 경우, 다른 애플리케이션은 해당 제공자의 데이터에 액세스할 수 없습니다. 그러나 제공자의 애플리케이션 내에 있는 구성 요소는 지정된 권한과 무관하기 항상 읽기 및 쓰기 권한을 모두 가지고 있습니다.
        • 이전에 언급한 것과 같이 사용자 사전 제공자에서 데이터를 검색하려면 android.permission.READ_USER_DICTIONARY 권한이 필요합니다. 이 제공자에게는 데이터 삽입, 업데이트 또는 삭제에 각각 별도의 android.permission.WRITE_USER_DICTIONARY 권한이 있습니다.
        • 제공자에 액세스하는 데 필요한 권한을 얻으려면 애플리케이션은 자신의 메니페스트 파일에 있는 <uses-permission> 으로 그러한 권한을 요청합니다. Android 패키지 관리자가 애플리케이션을 설치하는 경우, 사용자는 애플리케이션이 요청하는 권한을 모두 승인해야 합니다. 사용자가 이를 모두 승인하면 패키지 관리자가 설치를 계속하지만, 사용자가 이를 승인하지 않으면 패키지 관리자는 설치를 중단합니다.
        • 다음 <uses-permission> 요소는 사용자 사전 제공자에 읽기 권한을 요청합니다.
        • <uses-permission android:name=“android.permission.READ_USER_DICTIONARY” >
    • 데이터 삽입, 업데이트 및 삭제
      • 제공자에서 데이터를 검색하는 방식과 마찬가지로 제공자 클라이언트와 제공자 ContentProvider 사이에 상호작용을 이용하여 데이터를 수정합니다. ContentProvider 의 해당하는 메서드에 전달된 인수로 ContentResolver 메서드를 호출합니다. 제공자와 제공자 클라이언트는 보안 및 프로세스 간 통신을 자동으로 처리합니다.
      • 데이터 삽입
        • 데이터를 제공자 안으로 삽입하려면, ContentResolver.insert() 메서드를 호출합니다. 이 메서드는 제공자에 새로운 행을 삽입하고 해당 열에 대한 콘텐츠 URI 를 반환합니다. 이 스니펫은 사용자 사전 제공자에 새 단어를 삽입하는 방법을 보여줍니다.
 
// Defines a new Uri object that receives the result of the insertion
 
Uri mNewUri;
 
 
 
...
 
 
 
// Defines an object to contain the new values to insert
 
ContentValues mNewValues = new ContentValues();
 
 
 
/*
 
* Sets the values of each column and inserts the word. The arguments to the "put"
 
* method are "column name" and "value"
 
*/
 
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
 
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
 
mNewValues.put(UserDictionary.Words.WORD, "insert");
 
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");
 
 
 
mNewUri = getContentResolver().insert(
 
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
 
    mNewValues                          // the values to insert
 
);
 
        • 새로운 행에 대한 데이터는 단일 행 커서와 형태가 유사한 단일 ContentVlaues 객체로 이동합니다. 이 객체 내의 열은 모두 같은 데이터 유형을 가지지 않아도 됩니다. 또한 아예 값을 지정하고 싶지 않은 경우라면 열을 null 로 설정할 수 있스빈다. 이때 ContentValue.putNull() 을 사용하면 됩니다.
        • 이 스니펫은 _ID 열을 추가하지 않습니다. 이 열은 자동으로 유지관리되기 떄문입니다. 제공자는 추가된 모든 열마다 고유한 _ID 값을 할당합니다. 제공자는 보통 이 값을 테이블의 기본 키로 사용합니다.
        • newUri에 반환된 콘텐츠 URI는 다음과 같은 형식으로 새로 추가된 행을 식별합니다.
        • content://user_dictionary/words/<id_value>
        • <id_value> 는 새로운 행에 대한 _ID 의 콘텐츠입니다. 대부분의 제공자는 이런 형태의 콘텐츠 URI 를 자동으로 감지할 수 있으며, 그런 다음 해당 행에서 요청된 작업을 수행합니다.
        • 반환된 Uri 에서 _ID 값을 가져오려면 ContentUris.parseId() 를 호출합니다.
      • 데이터 업데이트
        • 행을 업데이트 하려면 업데이트된 값과 함께 ContentValues 객체를 사용합니다. 이때 값은 삽입할 때와 똑같고, 선택 기준은 쿼리할 때와 같습니다. 사용하는 클라이언트 메서드는 ContentResolver.update() 입니다. 값을 추가하는 것은 업데이트 중인 열에 대한 ContentValues 객체에만 하면 됩니다. 열의 콘텐츠를 삭제하려면, 값을 null 로 설정하세요.
        • 다음 스니펫에서는 로케일 언어 ‘en’ 인 행 모두를 로케일 null 을 가지도록 변경합니다. 반환 값은 업데이트 된 행의 수 입니다.
 
// Defines an object to contain the updated values
 
ContentValues mUpdateValues = new ContentValues();
 
 
 
// Defines selection criteria for the rows you want to update
 
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
 
String[] mSelectionArgs = {"en_%"};
 
 
 
// Defines a variable to contain the number of updated rows
 
int mRowsUpdated = 0;
 
 
 
...
 
 
 
/*
 
* Sets the updated value and updates the selected words.
 
*/
 
mUpdateValues.putNull(UserDictionary.Words.LOCALE);
 
 
 
mRowsUpdated = getContentResolver().update(
 
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
 
    mUpdateValues                       // the columns to update
 
    mSelectionClause                    // the column to select on
 
    mSelectionArgs                      // the value to compare to
 
);
 
 
        • ContentResolver.update() 를 호출하는 경우에는 사용자 입력도 삭제해야 합니다.
 
 

'Android > Theory' 카테고리의 다른 글

Activity  (0) 2021.05.05
Android 4대 컴포넌트  (0) 2021.05.05
Android MVVM Pattern  (0) 2020.05.18
Android Content Provider  (0) 2019.12.07
Android MVP Pattern  (0) 2019.07.15