Sharing Files筆記

Sharing Files筆記

Setting Up File Sharing

爲了安全地向別的app提供文件,使用URI是個很好的方法。FielProvider可以爲文件生成URI。

指定FileProvider:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application
        ...>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>
        ...
    </application>
</manifest>

android:exported表示你的app聲明的FileProvider能否被其他app使用。android:resource表示能夠分享文件的目錄。

指定可以分享的目錄:
在res/xm目錄下創建filepaths.xml,在文件中聲明目錄。<files-path>標籤,指定內部存儲的files目錄;類似的還有<external-path><cache-path>標籤。代碼片段如下:

<paths>
    <files-path path="images/" name="myimages" />
</paths>

在使用<external-cache-path>時,會報錯:Caused by: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/Android/data/com.addcn.car8891/cache/123.txt。這是什麼原因?
原因:FileProvider類支持的標籤有<root-path><files-path><cache-path><external-path>,並沒有<external-cache-path>。可能是文檔有錯誤,或是版本沒有跟上。

注意:XML文件是唯一指定分享目錄的方式。

上訴例子,FileProvider返回的URI爲content://com.example.myapp.fileprovider/myimages/default_image.jpg。

代碼示例

使用FileProvider將文件轉化成Uri:

Uri uri=FileProvider.getUriForFile(this,"com.addcn.car8891.fileprovider",new File(getExternalCacheDir(),"123.txt"));
    if(mShareActionProvider!=null){
        Intent intent=new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_STREAM,uri);
        mShareActionProvider.setShareIntent(intent);
    }

可分享文件目錄聲明:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!--<external-cache-path path="" name="haha"/>-->
    <external-path path="" name="heihei"/>
</paths>

Sharing File

Receive File Requests

爲了從客戶端app接收文件請求,並且用一個content URI作爲相應,你的app需要一個提供文件選擇的Activity。客戶端app用帶ACTION_PICK的action,調用startActivityForResult()方法。

Create a File Selection Activity

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    ...
        <application>
        ...
            <activity
                android:name=".FileSelectActivity"
                android:label="@File Selector" >
                <intent-filter>
                    <action
                        android:name="android.intent.action.PICK"/>
                    <category
                        android:name="android.intent.category.DEFAULT"/>
                    <category
                        android:name="android.intent.category.OPENABLE"/>
                    <data android:mimeType="text/plain"/>
                    <data android:mimeType="image/*"/>
                </intent-filter>
            </activity>

android.intent.category.OPENABLE表示intent的URI能夠被openFileDescriptor(Uri,String)方法開啓。

Define the file selection Activity in code

public class MainActivity extends Activity {
    // The path to the root of this app's internal storage
    private File mPrivateRootDir;
    // The path to the "images" subdirectory
    private File mImagesDir;
    // Array of files in the images subdirectory
    File[] mImageFiles;
    // Array of filenames corresponding to mImageFiles
    String[] mImageFilenames;
    // Initialize the Activity
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Set up an Intent to send back to apps that request a file
        mResultIntent =
                new Intent("com.example.myapp.ACTION_RETURN_FILE");
        // Get the files/ subdirectory of internal storage
        mPrivateRootDir = getFilesDir();
        // Get the files/images subdirectory;
        mImagesDir = new File(mPrivateRootDir, "images");
        // Get the files in the images subdirectory
        mImageFiles = mImagesDir.listFiles();
        // Set the Activity's result to null to begin with
        setResult(Activity.RESULT_CANCELED, null);
        /*
         * Display the file names in the ListView mFileListView.
         * Back the ListView with the array mImageFilenames, which
         * you can create by iterating through mImageFiles and
         * calling File.getAbsolutePath() for each File
         */
         ...
    }
    ...
}

Respond to a File Selection

URI權限不能用於Uri.fromFile()方法生成的file://URis,能用於與Content Providers相關的URIs。FileProvider API可以幫助創建這種URIs。

實例代碼:

protected void onCreate(Bundle savedInstanceState) {
    ...
    // Define a listener that responds to clicks on a file in the ListView
    mFileListView.setOnItemClickListener(
        new AdapterView.OnItemClickListener() {
        @Override
        /*
        * When a filename in the ListView is clicked, get its
         * content URI and send it to the requesting app
         */
        public void onItemClick(AdapterView<?> adapterView,
        View view,int position,long rowId) {
            /*
             * Get a File for the selected file name.
             * Assume that the file names are in the
             * mImageFilename array.
             */
            File requestFile = new File(mImageFilename[position]);
            /*
             * Most file-related method calls need to be in
             * try-catch blocks.
             */
            // Use the FileProvider to get a content URI
            try {
                fileUri = FileProvider.getUriForFile(
                    MainActivity.this,"com.example.myapp.fileprovider",
                        requestFile);
                } catch (IllegalArgumentException e) {
                    Log.e("File Selector",
                          "The selected file can't be shared: " +
                          clickedFilename);
                }
                ...
        }
    });
        ...
}

Grant Permission for the File

要使content URI能夠訪問或修改數據,還必須對客戶端app進行授權。授權的方式有:

  • <provider/>裏通過android:grantUriPermissions、android:readPermission、android:writePermission屬性,或<grant-uri-permission>標籤進行授權。
  • Intent設置了URI,比如調用setData(Uri),調用Intent的addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)進行授權。接收app的任務棧結束時,權限自動撤銷。
  • 通過Context.grantUriPermission()方法給content URI授權。有個缺點是撤銷權限需要調用Context.revokeUriPermission()方法。

Share the File with the Requesting App

代碼片段如下:

protected void onCreate(Bundle savedInstanceState) {
    ...
    // Define a listener that responds to clicks on a file in the ListView
    mFileListView.setOnItemClickListener(
        new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView,
            View view,int position,long rowId) {
                ...
                if (fileUri != null) {
                    ...
                    // Put the Uri and MIME type in the result Intent
                    mResultIntent.setDataAndType(
                            fileUri,
                            getContentResolver().getType(fileUri));
                    // Set the result
                    MainActivity.this.setResult(Activity.RESULT_OK,
                            mResultIntent);
                    } else {
                        mResultIntent.setDataAndType(null, "");
                        MainActivity.this.setResult(RESULT_CANCELED,
                                mResultIntent);
                    }
                }
    });

Requesting a Shared File

代碼示例:

public class MainActivity extends Activity {
    private Intent mRequestFileIntent;
    private ParcelFileDescriptor mInputPFD;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRequestFileIntent = new Intent(Intent.ACTION_PICK);
        mRequestFileIntent.setType("image/jpg");
        ...
    }
    ...
    protected void requestFile() {
        /**
         * When the user requests a file, send an Intent to the
         * server app.
         * files.
         */
            startActivityForResult(mRequestFileIntent, 0);
        ...
    }
    ...
}

Access the Requested File

代碼示例:

/*
     * When the Activity of the app that hosts files sets a result and calls
     * finish(), this method is invoked. The returned Intent contains the
     * content URI of a selected file. The result code indicates if the
     * selection worked or not.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode,
            Intent returnIntent) {
        // If the selection didn't work
        if (resultCode != RESULT_OK) {
            // Exit without doing anything else
            return;
        } else {
            // Get the file's content URI from the incoming Intent
            Uri returnUri = returnIntent.getData();
            /*
             * Try to open the file for "read" access using the
             * returned URI. If the file isn't found, write to the
             * error log and return.
             */
            try {
                /*
                 * Get the content resolver instance for this context, and use it
                 * to get a ParcelFileDescriptor for the file.
                 */
                mInputPFD = getContentResolver().openFileDescriptor(returnUri, "r");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                Log.e("MainActivity", "File not found.");
                return;
            }
            // Get a regular file descriptor for the file
            FileDescriptor fd = mInputPFD.getFileDescriptor();
            ...
        }
    }

openFileDescriptor()方法的第二個參數是使用的文件模式,r表示讀,w表示寫,rw表示讀寫。

Retrieving File Information

Retrieve a File’s MIME Type

代碼示例:

...
/*
 * Get the file's content URI from the incoming Intent, then
 * get the file's MIME type
 */
Uri returnUri = returnIntent.getData();
String mimeType = getContentResolver().getType(returnUri);
...

Retrieve a File’s Name and Size

FileProvider類默認實現的query()方法返回的Cursor有連個列名:

  • DISPLAY_NAME 文件名。
  • SIZE 文件字節數。

示例代碼:

...
/*
 * Get the file's content URI from the incoming Intent,
 * then query the server app to get the file's display name
 * and size.
 */
Uri returnUri = returnIntent.getData();
Cursor returnCursor =
    getContentResolver().query(returnUri, null, null, null, null);
/*
 * Get the column indexes of the data in the Cursor,
 * move to the first row in the Cursor, get the data,
 * and display it.
 */
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
returnCursor.moveToFirst();
TextView nameView = (TextView) findViewById(R.id.filename_text);
TextView sizeView = (TextView) findViewById(R.id.filesize_text);
nameView.setText(returnCursor.getString(nameIndex));
sizeView.setText(Long.toString(returnCursor.getLong(sizeIndex)));
...
發佈了32 篇原創文章 · 獲贊 7 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章