ContentProvider再探——문서 제공자


이 섹션 소개:

이전 섹션을 공부한 후에는 시스템에서 제공하는 ContentProvider를 사용하는 방법이나 ContentProvider를 사용자 정의하는 방법을 이미 알고 계실 것입니다. 기본적으로 일상적인 개발 요구 사항을 충족했습니다. 흥미롭게도 공식 문서에서 다음과 같은 다른 공급자를 보았습니다.

1.png

Calendar Provider: 캘린더 공급자는 그가 제공하는 API를 통해 캘린더 관련 이벤트를 위한 리소스 라이브러리입니다. 우리 달력, 시간, 모임, 알림 등의 내용을 추가, 삭제, 수정, 확인할 수 있습니다!
Contacts Provider: Contact Provider, 말할 필요도 없이 가장 많이 쓰이는 글이에요~ 나중에 시간나면 돌아가서 이 글도 번역해볼게요!
SAF(Storage Access Framework): 4.4 이후에 도입된 새로운 기능인 Storage Access Framework를 사용하면 사용자가 휴대폰에서 파일을 탐색할 수 있습니다. 콘텐츠 저장은 편리성을 제공하며 액세스할 수 있는 콘텐츠에는 문서, 사진, 비디오, 오디오, 다운로드뿐만 아니라 모든 콘텐츠가 포함됩니다. 특정 ContentProvider에서 제공하는 콘텐츠(합의된 API가 있어야 함) 콘텐츠의 출처가 무엇이든, 어떤 애플리케이션이든 상관없습니다. 시스템 파일의 내용을 찾아보는 명령을 호출하면 시스템은 사용자가 찾아볼 수 있는 통합 인터페이스를 사용합니다.
사실 IntentFilter에 LAUNCHER가 없기 때문에 DocumentsUI라는 내장 애플리케이션이므로 데스크탑에서 이 물건을 찾아보세요! 여기에서는 비교를 위해 두 개의 휴대폰을 선택했습니다. Lenovo S898T 4.2와 Nexus 5 5.0.1을 비교하여 다음 코드를 실행합니다:

Intentintent(Intent.ACTION_OPEN_DOCUMENT);
                       intent.setType ("image/*");
       startActivity( 의도);
실행 결과는 다음과 같습니다.

2.png3.png

오른쪽은 4.4가 제공하는 새로운 기능입니다. 일반적으로 파일 URL을 얻을 때 사용할 수 있습니다~ 다음으로, 간단히 문서를 따라 내려가세요~


2. 간단히 문서를 따라 내려가 보세요:

1) SAF 프레임워크의 구성:

  • Document Provider: 저장 서비스(예: Google 드라이브) 가능 관리하는 파일을 외부에 표시하세요. DocumentsProvider의 하위 클래스입니다. 또한 문서 제공자의 저장 형식입니다. 콘텐츠를 저장하는 방법은 전적으로 귀하에게 달려 있습니다. Android 시스템에는 여러 가지가 내장되어 있습니다. 다운로드, 사진, 비디오를 위한 문서 제공자와 같은 문서 제공자!
  • 클라이언트 앱: 사진 선택과 같은 ACTION_OPEN_DOCUMENT 및/또는 ACTION_CREATE_DOCUMENT을 트리거하여 문서 제공자로부터 반환된 콘텐츠를 받을 수 있는 일반 클라이언트 소프트웨어입니다. 그런 다음 Uri를 반환합니다.
  • Picker: 파일 관리자와 유사한 인터페이스 및 클라이언트 필터링 조건에 대한 액세스를 제공하는 시스템 수준 인터페이스 문서 제공자 콘텐츠의 채널은 처음에 언급한 DocumentsUI 프로그램입니다!

일부 기능:

  • 사용자는 단일 애플리케이션이 아닌 모든 문서 제공자가 제공하는 콘텐츠를 찾아볼 수 있습니다.
  • 문서 제공자의 파일에 대한 장기적이고 지속적인 액세스와 데이터 변경의 지속성을 제공합니다. 사용자는 문서 제공자가 관리하는 콘텐츠를 추가, 삭제, 편집 및 저장할 수 있습니다.
  • 드라이버가 성공적으로 설치된 경우에만 표시되는 USB 저장소 제공자와 같은 다중 사용자 및 임시 콘텐츠 서비스를 지원합니다.

2) 개요 :

SAF의 핵심은 ContentProvider이기도 한 DocumentsProvider의 하위 클래스를 구현하는 것입니다. 문서 제공자에서 전통적인 파일 디렉터리 트리로 구성됩니다:

4.png

3) 흐름도:

위에서 언급한 것처럼 문서 제공자 데이터는 전통적인 파일 계층 구조를 기반으로 하지만 이는 외부 표현일 뿐입니다. DocumentsProvider의 API를 통해 해외 인터페이스에 액세스할 수 있는 한 데이터를 저장하는 방법은 귀하에게 달려 있습니다. 아래 순서도는 SAF를 사용하는 사진 애플리케이션의 가능한 구조를 보여줍니다.

5.png

분석:

위 그림에서 Picker는 발신자와 콘텐츠 제공자 사이의 다리임을 알 수 있습니다! 발신자에게 선택할 수 있음을 제공하고 알려줍니다. DriveDocProvider, UsbDocProvider, CloundDocProvider와 같은 콘텐츠 공급자는 무엇입니까?
클라이언트가 ACTION_OPEN_DOCUMENT 또는 ACTION_CREATE_DOCUMENT 의도를 트리거하면 위의 상호 작용이 발생합니다. 물론 MIME 유형을 "이미지"로 제한하는 등의 필터 조건을 인텐트에 추가할 수도 있습니다!

6.png

위의 내용입니다. 여기 도착! 간단히 말하면, 클라이언트가 위 두 작업의 인텐트를 보낸 후 선택기 UI가 열리고 여기에 사용 가능한 관련 옵션이 표시됩니다. 사용자가 선택할 수 있는 문서 제공자, 사용자는 파일을 선택한 후 파일에 대한 관련 정보를 얻을 수 있습니다!


4) 클라이언트가 호출하여 반환된 Uri

를 가져옵니다. 구현 코드는 다음과 같습니다.

public 클래스 MainActivity는 AppCompatActivity를 확장하여 View.OnClickListener를 구현합니다. super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main );
        버튼 btn_show = (버튼) findViewById(R.id.btn_show);
        btn_show.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        인텐트 인텐트 = 새 인텐트(Intent. ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/*");
        startActivityForResult(intent, READ_REQUEST_CODE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, 인텐트 데이터) {
        if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
            Uri uri;
           if (데이터 != null) {
                uri = data.getData();
                Log.e("HeHe" , "Uri: " + uri.toString());
            }
        }
    }
}

실행 결과:예를 들어 개를 선택하면 Picker UI가 저절로 꺼지고 Logcat에서 이러한 URI를 볼 수 있습니다.

7.png


5) 파일 매개변수 가져오기 기반 uri

핵심 코드는 다음과 같습니다 :

public void dumpImageMetaData(Uri uri) {
커서 커서 = getContentResolver()
.query(uri, null, null, null, null, null);
{
                                                                                                         {
           String displayName =cursor.getString(
             cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 
          Log.e("HeHe", "표시 이름: " + displayName);
int sizeIndex =cursor.getColumnIndex(OpenableColumns.SIZE);
              문자열 크기 = null; 
                                      ~ ~ ~                

실행 결과: 메서드를 호출한 후에도 파일 이름과 파일 크기가 바이트 단위로 입력됩니다.

8.png


6) 코어를 기반으로 비트맵을 얻습니다. 코드는 다음과 같습니다:

private Bitmap getBitmapFromUri(Uri uri)가 IOException을 발생시킵니다. >  소포파일 설명자 .Close ();

리턴 이미지







:

7) uri에 따라 입력 스트림을 가져옵니다. ( (line = reader .readLine()) != null) {9.gif                 stringBuilder.                                                                                             

위 내용은 Uri를 통해 알 수 있는 내용만 알려드리고 있으며, Uri는 SAF를 통해 획득합니다!


8) 새 파일 생성 및 파일 삭제:

파일 생성:

private void createFile(String mimeType, String fileName) {
Intentintent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory( Intent.CATEGORY_OPENABLE);
intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_TITLE, fileName);
startActivityForResult(intent, WRITE_REQUEST_CODE);
}

onActivityResult()에서 찾을 수 있으며 URI를 가져올 수 있습니다. 생성된 파일

파일 삭제:

전제는 Document.COLUMN_FLAGS에 SUPPORTS_DELETE

DocumentsContract.deleteDocument(getContentResolver(), uri);
가 포함되어 있다는 것입니다.

9) 사용자 정의 문서 제공자 작성

애플리케이션 데이터가 documentui에서 열리도록 하려면 고유한 문서 제공자를 작성해야 합니다. 다음은 DocumentsProvider를 사용자 정의하는 단계를 설명합니다.

  • API 버전이 19 이상입니다.
  • manifest.xml에 공급자를 등록합니다.
  • 공급자의 이름은 클래스 이름과 패키지 이름을 합친 것입니다(예: com). example.android.storageprovider.MyCloudProvider
  • Authority는 패키지 이름 + 공급자 유형 이름입니다. 예: com.example.android.storageprovider.documents
  • android: 내보낸 속성의 값은 true

다음은 Provider 작성 방법의 예입니다.

<manifest... >
...
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="19" / >
....
                                                     제공되는 Android: 이름 = "com.example.android.storageProvider.myCloudProvider"
Android: 당국 = "com.examPle.android.dorageProvider.doc .doc uments "d Android: Granturipermissions =" ​​​​참 "
Android: 내보냄 =" TRUE "
android:permission="android.permission.MANAGE_DOCUMENTS"
                                                                       > >

10) DocumentsProvider

의 하위 클래스는 최소한 다음 메서드를 구현합니다.

  • queryRoots()
  • queryChildDocuments()
  • queryDocument()
  • openDocument()

다른 메서드도 있지만 그리고 필요하지 않음 . 다음은 파일 시스템에 액세스하는 구현을 보여줍니다. DocumentsProvider를 작성하는 대략적인 방법입니다.

queryRoots 구현

@Override
public Cursor queryRoots(String[] projection)는 FileNotFoundException을 발생시킵니다. 최종 MatrixCursor 결과 =
new MatrixCursor(resolveRootProjection(projection));

    // 사용자가 로그인되어 있지 않으면 빈 루트 커서를 반환합니다.  이렇게 하면 목록에서 우리의
    // 제공업체가 완전히 삭제됩니다.
    if (!isUserLoggedIn()) {
        return result;
    }

    // 여러 개의 루트를 가질 수 있습니다(예:
    // 동일한 앱의 여러 계정) -- 여러 개의 커서 행을 추가하세요.
    // "MyCloud"라는 루트에 대해 하나의 행을 구성하세요.
    최종 MatrixCursor.RowBuilder 행 = 결과.newRow();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    행. add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary));

    // FLAG_SUPPORTS_CREATE는 루트 아래에 하나 이상의 디렉터리가 문서 생성을 지원
    // 한다는 의미입니다. FLAG_SUPPORTS_RECENTS는 애플리케이션에서 가장 많이 사용된 문서가 '최근' 카테고리에 표시된다는 의미입니다.
    // FLAG_SUPPORTS_SEARCH를 사용하면 사용자가 애플리케이션
공유의 모든 문서를 검색할 수 있습니다.
    row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE |
           Root.FLAG_SUPPORTS_RECENTS |
           Root.FLAG_SUPPORTS_SEARCH);

    // COLUMN_TITLE은 루트 제목(예: 갤러리, 드라이브)입니다.
    row.add( Root.COLUMN_TITLE, getContext().getString(R.string.title)) ;

    // 이 문서 ID는 공유되면 변경할 수 없습니다.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

    // 하위 MIME 유형은 루트를 필터링하는 데 사용되며 해당
에만 표시됩니다. //  사용자 루트 파일 계층 구조 어딘가에 원하는 유형이 포함되어 있습니다.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
    row. 추가(루트.COLUMN_ICON , R.drawable.ic_launcher);

    반환 결과;
}

queryChildDocuments

public Cursor queryChildDocuments(String parentDocumentId, String[] 투영,
     구현                        String sortOrder) FileNotFoundException {

    최종 MatrixCursor 결과 = 발생 new
           MatrixCursor(resolveDocumentProjection(projection));
    final File parent = getFileForDocId(parentDocumentId);
    for (File file : parent.listFiles()) {
        // 파일의 추가 표시 이름, MIME 유형, 크기 등 ... 문자열[] 프로젝션) throws
        FileNotFoundException {

// 요청된 투영 또는 기본 투영으로 커서를 만듭니다.
    최종 MatrixCursor 결과 = new
           MatrixCursor(resolveDocumentProjection(projection));
    includeFile(result, documentId, null);

    반품 결과;}

그렇습니다. 문서에는 그 내용이 나와 있습니다. 처음에는 직접 번역하고 싶었으나 온라인에서 시간을 보내다가 이 문서의 중국어 번역을 발견해서 너무 게을러졌습니다~

중국어 번역 링크: android Storage Access Framework


3. Android 4.4 받기 리소스 경로 문제:

사실 우리가 이 SAF를 더 많이 사용하는 곳은 이미지의 Uri를 얻는 것 외에는 아무것도 아니며 위의 예에서 우리는 또한 다음을 발견했습니다. 우리가 얻는 링크는 다음과 같습니다:

content://com.android.providers.media.documents/document/image%3A69983

이러한 링크의 경우 위의 방법을 통해 uri를 직접 얻을 수 있습니다!

물론 4.4버전 이상입니다~!

이전 버전인 경우: uri는 다음과 같을 수 있습니다:

content://media/external/images/media/image%3A69983

다음은 다른 곳에서 본 포괄적인 솔루션입니다. 원본 링크: Android 4.4에서 리소스 경로를 얻는 문제

public static String getPath(최종 컨텍스트 컨텍스트, 최종 Uri uri) {
    최종 부울 isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    // DocumentProvider  
    if (isKitKat & & DocumentsContract.isDocumentUri(컨텍스트, uri)) {
        //ExternalStorageProvider  
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            최종 문자열[] split = docId.split(":");
            최종 문자열 유형 = 분할[0];

            if ("primary".equalsIgnoreCase(type)) {
              다시 설정 Environment.getExternalStorageDirectory() + "/" + split[1];
           }

           // TODO 기본이 아닌 볼륨 처리  
        } ㅋㅋㅋ = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads" ), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider  
        else if (isMediaDocument(uri)) {
            최종 문자열 docId = DocumentsContract.getDocumentId(uri );
            final String[] split = docId.split(":");
            final String type = split[0];
            Uri contentUri = null;
           if ("image".equals(type)) {
                contentUri = MediaStore.Images .Media.EXTERNAL_CONTENT_URI;
           } else if ("video".equals(type)) {
              contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore .Audio.Media.EXTERNAL_CONTENT_URI;
            }
            final String selection = "_id=?";
           final String[] selectionArgs = new String[] {
                    split[1]
            };
            return getDataColumn(context, contentUri, selection, SelectionArgs);
        }
    }
    // MediaStore (및 일반)  
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    / / 파일  
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }
    return null;
}

/**
 * 데이터 값 가져오기 기둥 이 Uri에 대해 이는
에 유용합니다. * MediaStore Uris 및 기타 파일 기반 ContentProvider. 
 *
 * @param context 컨텍스트입니다. 
 * @param uri 쿼리할 Uri입니다. 
 * @param 선택 (선택사항) 쿼리에 사용되는 필터입니다. 
 * @param selectionArgs (선택사항) 쿼리에 사용되는 선택 인수입니다. 
*@return _data 열의 값. 일반적으로 파일 경로입니다. 
 */
public static String getDataColumn(Context context, Uri uri, String selection,
                              String[] selectionArgs) {

    커서 커서 = null;
    최종 문자열 열 = "_data";
    최종 문자열[] 투영 = {
column
    };

    시도해 보세요 {
        커서 = context.getContentResolver().query(uri, projection, selection, selectionArgs,
              null);
        if (커서 != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } 마지막으로 {
        if (cursor != null)
           curs or.close();
    }
    null 반환;
}


/ **
 * @param uri 확인할 Uri입니다. 
 * @return Uri 기관이 ExternalStorageProvider인지 여부입니다. 
 */
public 정적 부울 isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
* @param uri 확인할 Uri
* @return Uri 권한이 무엇이든 DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri .getAuthority());
}

/**
 * @param uri 확인할 Uri입니다. 
 * @return Uri 기관이 MediaProvider인지 여부입니다. 
 */
public static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority()) ;
}

이 섹션 요약:

이번 섹션은 Android 저장소 액세스 프레임워크 SAF에 대한 것입니다. 나중에 사용하여 자세히 연구하겠습니다. 알아두세요. 4.4 이후에는 파일 경로를 얻는 것이 훨씬 쉬울 것입니다~