2012년 5월 10일 목요일

Content Providers 01

Content Providers

  • 콘텐트 프로바이더는 구조적 데이터 집합에 대합 접근을 관리한다.
    데이터를 캡슐화하고 데이터 보안을 정의하는 메카니즘을 제공한다.
    콘텐트 프로바이더는 하나의 프로세스에서 데이터를 다른 프로세스의 코드와 연결한다.

  • Content Provider의 데이터를 접근할 때 프로바이더를 클라이언트로 통신하기 위해 어플의 Context 안에 ContentResolver 개체를 사용한다.
    ContentResolver 개체는 ContentProvider 인스턴스인 프로바이더 개체와 통신한다.
    프로바이더 개체는 클라이언트로부터 데이터 요구를 수신하고 요구된 액션을 수행한 뒤 결과를 리턴한다.

Content Provider Basics

  • 콘텐트 프로바이더는 데이터의 중앙 저장소에 대한 접근를 관리한다.
    애플러케이션는 프로바이더 클라이언트 개체를 사용하여 프로바이더를 접근하는데 콘텐트 프로바이더를 이용한다.

  • 프로바이더와 프로바이더 클라이언트는 프로세스간 통신(IPC)과 안전한 데이터 접근을 다루는 표준 인터페이스를 제공한다.

  • 요약
  • 콘텐트 프로바이더의 작동방식
    콘텐트 프로바이더로부터 데이터를 복원하는 API
    콘텐트 프로바이더에 데이터를 삽입, 갱신, 삭제하는 API
    프로바이더와 동작을 수생하는 다른 API

Overview

  • 콘텐트 프로바이더는 관계형 데이터베이스의 테이블과 유사한 하나이상의 테이블을 외부 어플에 데이터를 표현한다.
    row은 프로바이더가 모은 데이터의 특정 타입 인스턴스를 대표하고 column은 각각의 데이터의 개별 부분을 이룬다.

  • 예제

  • wordapp idfrequencylocale_ID
    mapreduceuser1100en_US1
    precompileruser14200fr_FR2
    appletuser2225fr_CA3
    constuser1255pt_BR4
    intuser5100en_UK5

프로바이더에 접근하기

  • 어플은 ContentResolver 클라이언트 개체로 콘텐트 프로바이더로부터 데이터에 접근한다.
    ContentResolver 메서드는 기본적으로 CRUD (create, retrieve, update, delete)을 가진다.

  • 클라이언트 어플 프로세스의 ContentResolver 개체와 프로바이더를 소유한 어플의 ContentProvider 개체는 자동적으로 프로세스간 통신을 다룬다.
    ContentProvider는 데이터의 저장소와 테이블로써 데이터의 외부 표현간에 추상적 레이어로 작동한다.

  • 예제
    사용자 사전 프로바이더로부터 단어와 로케일 관련 리스트를 얻기 위해 ContentResolver.query()를 호출한다.
    query() 메서드는 사용자 사전 프로바이더에 정의된 ContentProvider.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
    

  • query(Uri, projection, selection, selectionArgs, sortOrder)에 대한 인수들은 SQL SELECT 구문과 다음과 매칭된다.

  • query() argumentSELECT keyword/parameterNotes
    UriFROM table_nameUri는 table_name 이름의 프로바이더와 맵핑된다.
    projectioncol, col, col, ... projection은 복원된 각 행을 위해 포함되어야 할 열들의 배열이다.
    selectionWHERE col=value selection은 행들을 선택하는 것의 기준을 지정한다.
    selectionArgs

    sortOrderORDER BY col, col, ...

Content URIs

  • content URI는 프로바이더의 데이터를 확인하는 URI이다.
    Content URI들은 전체 프로바이더의 기호 이름(authority)과 테이블을 가리키는 이름(path)을 포함한다.

  • content://user_dictionary/words

  • user_dictionary프로바이더의 authority
    words테이블 경로
    content:scheme

  • 프로바이더는 URI의 뒷 부분에 ID값을 추가함으로써 테이블의 하나 row를 접근할 수 있게 한다.
    예를 들어 사용자 사전에서 _ID가 4인 row를 얻기 위해 다음과 같이 content URI를 사용한다.

  • Uri singleUri=ContentUri.withAppendedId(UserDictionary.Words.CONTENT_URI, 4) ;

프로바이더에서 데이터를 얻기

  • 프로바이더로부터 데이터를 얻기위해 다음과 같은 기본 단계를 따른다.

    1. 프로바이더로부터 읽기 접근 권한을 요청한다.

    2. 프로바이더에게 쿼리를 보내는 코드를 정의한다.

읽기 접근 권한을 요청하기

  • 권한은 실행시에 요청될 수 없고 manifest 파일안에서 엘러먼트와 프로바이더에 의해 정의되는 권한을 이용한다.

  • 어플 설치시 이 권한 요청은 수락된다.

  • 사용자 사전 프로바이더 예제에서 manifest 파일안에 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 = {""};
    

  • 다음 코드는 ContentResolver.query()를 사용하는 법을 보여준다.
    프로바이더 클라이언트 쿼리는 SQL 쿼리와 유사하고 리턴할 column들의 집합과 선택기준의 집합, 정렬순서를 포함한다. 쿼리가 리턴해야 하는 column들의 집합은 proejction이다.

    검색할 row를 지정하는 표현은 selection 구문과 selection 인수로 나뉜다.
    selection 구문은 논리/부울 표현과 column 이름들,값들로 이루어진 조합이다.
    값 대신 '?' 대체 매개변수를 지정하면 쿼리 메서드는 selection 인수 배열로부터 값을 검색한다.

    다음 예제에서 단어를 입력하지 않으면 selectin 구문은 null이고 쿼리는 프로바이더에서 모든 단어들을 리턴한다.
    사용자가 단어를 입력하면 selection 구문은 UserDictionary.Words.Word +"=?"로 지정되고 selection 인수 배열의 첫번째 원소는 사용자가 입력한 단어로 지정된다.

  • /*
     * 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 = " = ?";
    
        // 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
    
    }

  • 이 쿼리는 다음의 SQL 문장과 대응되고 cotract class constants 대신 실제 column 이름들이 사용된다.
  • SELECT _ID, word, frequency, locale FROM words WHERE word = ORDER BY word ASC;

악의적인 입력로부터 보호하기

  • '?' 매개변수와 selection 인수들의 개별 배열을 selection 구문에 사용한다.
    이럼으로써 사용자 입력은 SQL 문장의 부분으로 해석되지 않고 쿼리에 직접 연결된다.


  • // Constructs a selection clause with a replaceable parameter
    String mSelectionClause = "var = ?";


  • selection 인수들의 배열을 성정한다.

  • // Defines an array to contain the selection arguments
    String[] selectionArgs = {""};


쿼리 결과를 표현하기

  • ContentResolver.query() 클라이언트 메서드는 Cursor를 리턴한다.
    Cursor는 selection 기준과 합치하는 row들을 위한 쿼리 proejction에 의해 지정된 column들을 담고 있다.
    Cursor 메서드를 사용하여 row들을 반복 적용할 수 있고 각 column의 데이터 타입과 데이터을 판단, 얻어오며 결과들의 다른 속성들을 검사할 수 있다.

  • selection criteria와 합치하는 Row들이 없으면 프로바이더는 Cursor.getCount()가 0인 비어있는 Cursor 개체를 리턴한다.


  • Cursor는 Row들의 하나 리스트이기 때문에 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로부터 Row들을 반복 적용

  • // 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.
    }
    

Content Provider Permissions

  • 프로바이더 어플은 다른 어플들이 프로바이더의 데이터를 접근하기 위해 필요한 권한을 지정할 수 있다.
    이 권한들은 어플이 어떤 데이터를 접근하는 지를 사용자가 인지하는 것을 확인해준다.
    최종 사용자들은 어플 설치시 요구된 권한들 볼 수 있다.


  • 예제의 사용자 사전 프로바이더는 데이터를 검색하기 위해 android.permission.READ_USER_DICTIONARY 권한을 요구한다.
    프로바이더는 데이터에 대한 삽입, 갱신, 삭제(inserting, updating, deleting)들을 위한 별도의 android.permission.WRITE_USER_DICTIONARY 권한을 가진다.


  • 프로바이더에 접근하는 데 필요한 권한을 얻기 위해 어플은 manifest 파일안에 엘러먼트로 요청한다.
    Android Package Manager가 어플을 설치할 때 어플이 요구한 모든 권한들을 동의해야 한다.


  • <uses-permission android:name="android.permission.READ_USER_DICTIONARY">
    

데이터 삽입, 갱신, 삭제

  • 프로바이더에서 데이터를 검색하는 방식과 같이 데이터를 수정하기 위해 프로바이더 클라이언트와 프로바이더의 ContentProvider간의 상호동작을 이용한다.

데이터 삽입(Inserting data)

  • ContentResolver.insert()는 프로바이더에 새로운 레코드를 입력하고 content 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
    );
    


댓글 없음:

댓글 쓰기