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)));
...