您的位置:首页 > 其它

pAdTy_-2 构建内容共享的应用程序

2015-11-11 10:27 288 查看
2015.11.02-11.07

个人英文阅读练习笔记(低水准)。原文地址:http://developer.android.com/training/building-content-sharing.html

2015.11.02

此笔记分享如何创建与应用程序、与设备共享数据的应用程序。

1. 共享简单的数据

通过与其它程序共享数据、从其它程序接收数据以及根据用户内容提供简单且可扩展的方式执行共享的操作来让应用程序互动到下一级。

1.1 给其它的应用程序发送简单的数据

当构建目的时,必须指定目的所要触发的动作。安卓定义了几种动作,包括 ACTION_SEND,此动作表示从一个活动发送数据到另外一个活动,设置会跨越边界。将数据发送给另外一个活动,只需要指定数据及其类型,系统会识别出能够兼容接收的活动并将它们呈现给用户(如果有多个选择的话)或者直接启动活动(若只有一个选择)。类似的,也可以在您的应用程序的清单文件中声明活动能从其它应用程序接收的数据类型。

在应用程序之间用目的来发送和接收数据来达到共享内容是最常用的方法。用户可以通过他所喜爱的应用程序使用目的来快速、简单的分享数据。

注:增加共享动作条目到 ActionBar的最佳方式是使用 ShareActionProvider,在API 级别14上可用。在笔记中 Adding an Easy Share Action讨论了ShareActionProvider。

(1) 发送文本内容

从一个应用程序发送文本到另外一个应用程序最直接最常见的方式是使用ACTION_SEND动作。例如,内建的浏览器应用程序能够将当前网页的URL作为文本分享给其它的应用程序。这对通过邮件或社交网络分享文章或网页给朋友分厂有用。以下是实现这种分享的代码:

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent.setType("text/plain");
startActivity(sendIntent);


如果系统中有一个安装的应用程序且其过滤器能匹配ACTION_SEND且MIME类型为text/plain,安卓就会运行它;如果不止一个应用程序满足条件,系统就会展示一个消除歧义的对话框来供用户选择一个应用程序。

然而,如果将目的对象传递给Intent.createChooser()并调用它,此方法将会展示选择对话框的目的的版本。这将有一些好处:

- 即使用户之前为目的选择了默认的动作,选择框依然会被展示。

- 如果无应用程序匹配,安卓将会给出一个系统提示。

- 可以为选择对话框配置一个标题。

以下是更新后的代码:

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent.setType("text/plain");

startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));


此代码对应的对话框如图1.



图1.在手机上ACTION_SEND目的选择对话框的截图

随意地,您也可以设置其它标准的目的:EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC,EXTRA_SUBJECT。如果接收应用程序没有使用它们,这些目的会被忽略。

注:一些e-mail应用程序,如Gmail,对 EXTRA_EMAIL和EXTRA_CC动作还期望有String[],用 putExtra(String, String[])添加这个内容到目的中。

2015.11.03

(2) 发送二进制内容

用ACTION_SEND并设置合适的MIME类型再用额外的EXTRA_STREAM将URI放置到数据中即可发送二进制数据。这种方式可以用来分享任何诸如图片这样的二进制内容:

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uriToImage);
shareIntent.setType("image/jpeg");
startActivity(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));


注意以下点:

- 可以使用”/“MIME类型,但这只会匹配能够处理通用数据流的活动。

- 接收应用程序需要访问Uri所指数据的权限。推荐按照以下方法来做:

将数据保存在ContentProvider,并保证其它的应用程序有权限访问它。用能够暂时为接收应用程序授权的 per-URI permissions来为其它应用程序提供访问权限。用 FileProvider帮助类可很简单的创建ContentProvider。

使用系统的MediaStore,MediaStore原本是用来为视屏、音频以及图片MIME类型服务的。但从安卓3.0(API level 1)开始,它也能够存储非媒体类型(见MediaStore.Files获取更多信息)。将用来分享的content://风格的Uri传递给回调方法onScanCompleted()并调用,再调用scanFile()就可以将文件插入到MediaStore。注意,一旦将内容添加到MediaStore后任何应用程序都可以访问它。

(3) 发送多块内容

使用ACTION_SEND_MULTIPLE动作并用一系列指向数据内容的URIs就可以分享多块内容的数据。根据所分享内容,MIME类型随之变化。例如,如果分享3张JPEG图片,对应的类型为”image/jpeg”。当混合图片类型时,类型应该为”image/“,这样就可以去匹配能够处理任何类型图片的活动。如果分享内容多种多样,应该使用”/*”。如前面的陈述,这些都取决于接收应用程序怎么解析和处理分享的数据。以下有一个例子:

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris.add(imageUri1); // Add your image URIs here
imageUris.add(imageUri2);

Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent.setType("image/*");
startActivity(Intent.createChooser(shareIntent, "Share images to.."));


这段代码的前提是,接收应用程序要能够访问所提供的URIs所指向的数据。

1.2 从其它应用程序接收简单的数据

同应用程序可以发送数据给其它应用程序一样,应用程序同样可以接收其它应用程序发送的数据。考虑用户和应用程序交互的方式,您的应用程序想要接收什么数据。例如,一个社交网络应用程序会想接收其它应用程序的文本内容(如网页的URL)。 Google+ Android application接收文本以及单个或多个的图片。用这个应用程序,用户能够根据从Android Gallery应用程序中得到的图片很简单的开启一个新的Google+ post。

(1) 更新清单文件

目的过滤器(intent filters)告知系统应用程序组件所能接收的目的。跟 Sending Simple Data to Other Apps笔记中构建含 ACTION_SEND动作的目的一样,构建这样的一个目的也是为了活动能够接收此动作。在清单文件中是用元素来定义目的过滤器。例如,如果应用程序要接收文本内容、一种类型的图片或者任何类型的图片,清单文件中的内容应该如下:

<activity android:name=".ui.MyActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>


注:更多关于目的过滤器和目的解决请读 Intents and Intent Filters。

当其它应用程序通过构建目的并将目的传递给startActivity()来分享数据时,您的应用程序有可能被系统列在选择对话框中。如果用户选择了您的应用程序,相关的活动(如例子中的.ui.MyActivity)将会被启动。数据会得到怎样的处理取决于此活动的用户界面及其内部代码。

(2) 操控传进来的内容

要处理目的传递过来的内容,需要调用 getIntent()来获取目的对象。一旦拥有了此对象,就可以根据其中的内容来决定下一步做什么。记住,如果活动能够被系统的其它部分启动,如发射器,那么在分析目的时需要将这个因素考虑在内。

void onCreate (Bundle savedInstanceState) {
...
// Get intent, action and MIME type
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();

if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // Handle text being sent
} else if (type.startsWith("image/")) {
handleSendImage(intent); // Handle single image being sent
}
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {
if (type.startsWith("image/")) {
handleSendMultipleImages(intent); // Handle multiple images being sent
}
} else {
// Handle other intents, such as being started from the home screen
}
...
}

void handleSendText(Intent intent) {
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
if (sharedText != null) {
// Update UI to reflect text being shared
}
}

void handleSendImage(Intent intent) {
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (imageUri != null) {
// Update UI to reflect image being shared
}
}

void handleSendMultipleImages(Intent intent) {
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (imageUris != null) {
// Update UI to reflect multiple images being shared
}
}


警告:因为您不知道什么样的应用程序会发送数据给您的应用程序,编写一段额外的代码来检查接收的数据。例如,MIME类型被设置错误,被发送的图片过大。同时,还要记住处理二进制数据时需要在一个独立的线程中。

所更新的用户界面很简单:填充EditText一样简单,或者稍微复杂一点:设置图片过滤器。此代码指定了应用程序下一步会执行什么样的操作。

1.3 添加一个简单的共享操作

根据安卓4.0(API level 14)中对 ActionProvider的介绍,在ActionBar中实现一个有效且友好的用户分享变得很简单。一旦将 ActionProvider附在动作条上的菜单条目中,它将操作条目的显示及行为。对于ShareActionProvider,您只需要提供分享的目的,其余由它本身完成。

注:在API level 14及以上支持ShareActionProvider。

(1) 更新菜单声明

通过在菜单资源(menu resource)文件的元素下定义android:actionProviderClass属性来使用ShareActionProvider:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_item_share"
android:showAsAction="ifRoom"
android:title="Share"
android:actionProviderClass=
"android.widget.ShareActionProvider" />
...
</menu>


ShareActionProvider将代表条目的外表和功能。然而,您需要告知提供者(provider)您所想要分析的东西。

(2) 设置分享目的

为了ShareActionProvider的功能,必须给其提供一个分享的目的。此分享目的需要跟Sending Simple Data to Other Apps笔记中描述的一样,有ACTION_SEND动作和通过额外的诸如EXTRA_TEXT and EXTRA_STREAM设置的数据。为了分配目的,首先需要找到当活动或碎片(片段)在运行时时对应的MenuItem。然后调用MenuItem.getActionProvider()来检索的ShareActionProvider实例。然后调用setShareIntent()来更新跟动作条目对应的共享目的。以下是一个例子:

private ShareActionProvider mShareActionProvider;
...

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate menu resource file.
getMenuInflater().inflate(R.menu.share_menu, menu);

// Locate MenuItem with ShareActionProvider
MenuItem item = menu.findItem(R.id.menu_item_share);

// Fetch and store ShareActionProvider
mShareActionProvider = (ShareActionProvider) item.getActionProvider();

// Return true to display menu
return true;
}

// Call to update the share intent
private void setShareIntent(Intent shareIntent) {
if (mShareActionProvider != null) {
mShareActionProvider.setShareIntent(shareIntent);
}
}


在创建菜单时可能需要设置一次共享目的,或者在更新用户界面之前设置共享目的。例如,当使用Gallery应用程序在全屏下查看照片时,当翻转照片时共享的目的将会改变。

更多关于ShareActionProvider的讨论,见 Action Views and Action Providers。

2015.11.04

2. 共享文件

怎么使用内容URI和临时的访问权限来提供与应用程序所关联的文件的安全访问。

应用程序时常都需要提供一个或者多个的文件给另一个应用程序。例如,图片浏览应用会给图片编辑器提供图片,文件管理应用程序能让用户在外部存储器的各个区域内复制粘贴文件。发送文件应用程序能分享文件的实现一种方式是跟请求文件的应用程序关联起来。

在所有的情形下,从一个应用程序发送文件给另外一个应用程序的安全做法是给接收应用程序发送文件内容的URI,且暂时给此URI授权让接收应用程序可访问。被授予访问权限的内容URIs是安全的,因为只有接收此内容URI的应用程序才能够访问,URI的权限也会自动过期。安卓的FileProvider组件提供getUriForFile()方法来产生文件内容URI。

如果在应用程序间分享小量的文本或数字数据,应用用包含数据的目的来分享它们。学习怎么用目的来发送简单的数据,见类Sharing Simple Data。

此笔记解释怎么用安卓的FileProvider组件产生的内容URIs来在应用程序之间分享文件以及怎么给接收文件的应用程序授权内容URI。

2.1 设置文件共享

想要安全地将应用程序的文件分享给另外一个应用程序,需要在应用程序中以内容URI的格式为文件配置一个安全的句柄。安卓的FileProvider组件会根据在XML的指定为文件产生相应的内容URI。此部分笔记记录怎么在应用程序中添加一个默认实现的FileProvider,以及怎么指定将要分享给其它应用程序的文件。

注:FileProvider类是v4 Support Library的一部分。将此库包含到应用程序的信息见 Support Library Setup。

(1) 指定FileProvider

在清单文件中为应用程序定义FileProvider需要一个入口。此入口指定了产生内容URI的权威(规则),同时也包含了指定应用程序所分享目录的XML文件的名字。

以下的一小片段代码展示了如何往清单文件中添加用来指定FileProvider类、权威以及XML文件名字的元素:

<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:authorities属性指定了您想根据FileProvider来生成内容URI的权威即com.example.myapp.fileprovider。对于自己的应用程序,权威由应用程序的android:package值再加上”fileprovider”字符串组成。欲学习更多关于权威值,见

Content URIs 话题以及 android:authorities属性的文档。

的子元素指定了指定所要分享目录的XML文件。android:resource属性的值表示文件路径及文件名,文件名不含.xml扩展名。文件的内容将会在下一节中描述。

(2) 指定可分享的目录

一旦在应用程序的清单文件中添加FileProvider,就需要指定包含所要分享文件的目录。首先,在工程的子目录res/xml/下创建filepaths.xml文件来。然后,在filepaths.xml文件中,通过为每个目录添加XML元素的方式指定目录。以下的片段代码为res/xml/filepaths.xml中的内容。此代码演示了如何在内部存储器中分享子目录file/:

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


在此例中,标签分享应用程序内部存储器中的files/目录下的目录。path属性指定分享files/目录下的images/目录。name属性将告知FileProvider去为files/images/子目录添加路径片段myimages到内容URIs。

元素能够拥有多个子元素,每个元素可以指定不同的分享目录。除元素外,可以使用元素来指定所分享的外部存储器中的目录,元素可指定在内部缓存中的目录。欲学习更多关于指定分享目录的子元素,见 FileProvider相关文档。

注:XML文件是能够指定分享目录的唯一方式。不能以编程的方式增加目录。

完成之前的步骤后,此时咱拥有了一个为应用程序内部存储器的files/目录(或其子目录)产生内容URIs的FileProvider。当应用程序为文件产生了内容URI,它包含了在元素(com.example.myapp.fileprovider)中指定的权威、路径myimages/以及文件名。

举例,如果根据此笔记已经定义了一个FileProvider,若有应用程序请求文件default_image.jpg的内容URI,FileProvider将会返回以下的URI:

content://com.example.myapp.fileprovider/myimages/default_image.jpg


【URI:统一资源标识符,用于标识某的资源名称的字符串】

2.2 共享文件

一旦在应用程序中用内容URI设置了共享文件后,应用程序就能够响应其它应用程序请求文件的请求。一种响应这些请求的方法是从服务器应用程序那里提供一个文件选择接口给其它应用程序调用。这个方法允许客户端应用程序让用户从服务器应用程序选择文件并接受文件内容URI。

此部分笔记展示如何在响应请求文件的应用程序中创建一个文件选择的活动。

(1) 接收文件请求

在应用程序中提供一个文件选择活动来接收客户端应用程序的文件请求并用内容URI响应。客户端应用程序通过包含ACTION_PICK动作的目的来调用startActivityForResult()从而启动这个活动。当客户端应用程序调用startActivityForResult()时,您的应用程序将以内容URI的格式列举给客户端应用程序供用户选择。

学习如何在客户端程序实现文件请求,见 Requesting a Shared File。

(2) 创建文件选择活动

在清单文件中用含ACTION_PICK动作以及CATEGORY_DEFAULT和CATEGORY_OPENABLE类别的目的过滤器来设定文件选择活动,当然此过滤器也要包含具体的MIME类型。以下的代码片段用来指定活动及其目的过滤器:

<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>


在代码中定义文件选择活动

接下来,定义展示应用程序内部存储器files/images/目录下能够允许用户选择的文件的活动子类。以下代码片段演示如何定义活动及响应用户选择:

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


(3) 响应文件选择

一旦用户选择了共享文件,您的应用程序必须需要知道用户到底选择了哪一个文件且需为此文件产生一个内容URI。由于活动使用ListView列举这些文件的,所以当用户选择选择文件时系统会调用onItemClick()方法,此方法可以得知用户所选择的文件。

onItemClick()能够根据用户所选择的文件得到其文件对象并会将此对象作为参数传递给getUriForFile(),伴随在元素中为FilePRovider指定的权威。得到的内容URI包含权威、文件所在目录的路径(跟在XML meta-data中指定的一致)以及包含扩展的文件名。根据XML的meta-data,FileProvider是如何将目录映射成路径片段的见 Specify Sharable Directories。以下代码片段描述了如何检测共享文件以及如何获取此文件的内容URI:

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


记住,只能为在应用程序中的包含元素的meta-data文件中所指定目录下的文件产生内容URI,此过程在Specify Sharable Directories中。如果对一个没有被指定的文件调用getRriForFile(),就会收到IllegalArgumentException。

(4) 为文件授权权限

现在应用程序中已经有了共享文件的内容URI,还需允许客户端应用程序来访问文件。为达访问权限,在客户端应用程序中增添一个内容URI到目的上并为此目的设置权限标签的方式来给客户端程序访问文件授权。这种授权时暂时的,当接收应用程序任务栈结束时此权限也会自动过期。

以下代码片段演示如何设置文件的读权限:

protected void onCreate(Bundle savedInstanceState) {
...
// Define a listener that responds to clicks in the ListView
mFileListView.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView,
View view,
int position,
long rowId) {
...
if (fileUri != null) {
// Grant temporary read permission to the content URI
mResultIntent.addFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
...
}
...
});
...
}


警告:为文件设置安全的临时访问权限唯一方式是调用setFlag()。避免调用Context.grantUriPermission()方法来为内容URI服务,因为经此方法授权访问的文件只能调用Context.revokeUriPermission()来获取访问权限。

(5) 与请求文件的应用程序共享文件

传递包含内容URI和权限的目的给setResult()来与请求文件共享的应用程序共享文件。当定义的活动结束时,系统会发送包含内容URI的目的给客户端应用程序。以下代码片段体现了这个过程:

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


一旦用户选择了文件就会立即将结果返回到客户端应用程序。实现这个功能的一种方式是提供一个检查标签(checkmark)或完成按钮。用按钮的android:onClick属性为按钮指定一个方法,在方法中调用finish()方法。如下例:

public void onDoneClick(View v) {
// Associate a method with the Done button
finish();
}


2015.11.05

2.3 请求共享文件

当应用程序要访问其它应用程序共享的文件时,请求应用程序(客户端)常会向共享文件的应用程序(服务端)发送请求。在大多数情况下,请求都在服务端展示共享文件的活动中被开启。由用户来选择一个文件,然后服务端应用程序将给客户端应用程序返回共享文件的内容URI。

此笔记展示客户端应用程序想服务端应用程序请求文件,从服务端应用程序接收内容URI,根据内容URI打开文件。

(1) 发送共享文件的请求

客户端将包含诸如ACTION_PICK动作和客户端应用程序能够处理的MIME类型的目的传递给 startActivityForResult方法来向服务器段应用程序请求文件。

以下代码片段演示了如何向服务器端应用程序发送目的,以开启描述在 Sharing a 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);
...
}
...
}


(2) 访问所请求的文件

服务器端应用程序将文件的内容URI包含在目的中并将目的返回给客户端应用程序。此目的被传递到客户端应用程序的重写方法onActivityResult()中。客户端应用程序一旦拥有文件的内容URI,它就可以通过获取文件的FileDescriptor来访问文件。

在这个过程中不会涉及到文件的安全性,因为文件的内容URI只会被客户端应用程序收到。由于URI没有包含目录路径,客户端不会发现或服务器端应用程序的其它任何文件。只有客户端应用程序能够访问到此文件,也只有服务器端应用程序为此授权。文件的权限也是临时的,只要客户端程序访问文件的任务一经结束,此文件除了服务器端应用程序外,其它程序都不可再访问。

以下代码片段演示客户端应用程序操控服务器端应用程序发送的目的,并用内容URI来获取FileDescriptor:

/*
* 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()方法为此文件返回ParcelFileDescriptor。根据此对象,客户端应用程序能够获取FileDescriptor对象,能够用此对象读取此文件。

2015.11.06

2.4 检索文件信息

在客户端应用程序处理拥有内容URI的文件之前,客户端应用程序能够从服务器端应用程序那里获取文件的信息,包括文件的数据类型及文件尺寸。文件的数据类型将帮助客户端应用程序决定它是否能够处理此文件,文件的尺寸将帮助客户端应用程序为此文件开辟适当的内存缓冲区。

此笔记演示如何从服务器应用程序的FileProvider那里检索到文件的MIME类型及大小。

(1) 检索文件的MIME类型

根据文件的数据类型,客户端应用程序就能够知道如何处理文件内容。根据共享文件的内容URI,客户端应用程序调用ContentResolver.getType()来获取文件的数据类型,getType()返回文件的MIME类型。默认情况下,FileProvider根据文件的扩展名来判断文件的数据类型。

以下代码片段演示服务器应用程序一旦将文件的内容URI返回给客户端应用程序,客户端如何检索此文件的MIME类型:

//……
/*
* 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);
//……


(2) 检索文件名及大小

FileProvider类中的query() 方法的默认功能是返回内容URI在 Cursor中的文件的名字和尺寸。默认实现的query()将会返回两个值:

DISPLAY_NAME

以字符串的方式返回文件名。其值跟File.getName()方法返回的一样。

SIZE

以长整型类型且以字节方式放回文件的尺寸。这个值跟File.length()方法返回的一样。

在客户端应用程序中,通过设置query()方法除内容URI之外参数都为null的方式可同时获得文件DISPLAY_NAME的和SIZE。以下代码片段检索了文件的DISPLAY_NAME和SIZE,并将它们分别显示在TextView中。

//……
/*
* 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)));
//……


3. 用NFC共享文件

怎么使用NFC Android Beam特性在设备间传输文件。

使用Android Beam文件传输特性可以在设备之间传输大文件。此特性有一个简单的API并允许用户通过简单的触碰设备就可以开启传输。开启此特性后,Android Beam文件传输特性自动的从一个设备复制文件到另一个设备,当传输完毕后会自动通知用户。

Android Beam文件传输API处理较大的数据传输,安卓4.0(API level 14)Android Beam NDEF传输API处理诸如URIs或其它小信息的小数据传输。Android Beam只是Android NFC框架其中的一个特性,它能够从NFC标签中读取NDEF信息。欲知更多关于Android Beam,见Beaming NDEF Messages to Other Devices.。欲知更多关于NFC框架,见Near Field Communication API手册。

3.1 发送文件给其它设备

学习怎么设置应用程序来发送文件到其它设备。

此部分笔记将演示如何用Android Beam文件传输来设置应用程序以发送大文件到其它设备。欲发送文件,需要请求NFC和外部存储器的权限,测试确保设备支持NFC,再给Android Beam文件传输提供URIs。

Android Beam文件传输特性需要以下条件:

[1] 传输大文件的Android Beam文件传输只能在Android 4.1(API level 16)以及以上使用。

[2] 欲被发送的文件必须在外部存储器上。欲知更多关于外部存储器见Using the External Storage。

[3] 欲发送的文件必须是可读的。可以通过调用File.setReadable(true,false)方法来设置此权限。

[4] 必须为欲传输的文件另提供URI。Android Beam文件传输不能处理FileProvider.getUriForFile产生的内容URIs。

(1) 在清单文件中声明特性

首先,需要在应用程序的清单文件中声明应用程序所需的权限以及特征。

[1] 请求权限

欲在应用程序中用Android Beam文件传输的NFC来发送外部存储器中的文件,必须在应用程序的清单文件中请求一下权限:

NFC

允许应用程序通过NFC发送数据。通过在元素下添加以下元素作为其子元素的方式指定这个权限:

<uses-permission android:name="android.permission.NFC" />


READ_EXTERNAL_STORAGE

允许应用程序读取外部存储器。通过在元素下添加以下元素作为其子元素的方式指定这个权限:

<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />


注:在安卓4.2.2(API level 17)中,此权限不是必需的。后续版本在访问外部存储器时或许会需要权限。为确保兼容,现在就使用请求权限吧。

[2] 指定NFC特征

通过将元素作为子元素的方式指定应用程序使用NFC。将android:required属性设置为ture来表明应用程序不会使用传文件除非NFC存在。

以下代码片段演示如何指定元素:

<uses-feature
android:name="android.hardware.nfc"
android:required="true" />


注意,如果应用程序只是将NFC作为一个选择,在NFC不存在时依旧可以传文件,就应该将android:required设置为false,并测试此部分代码。

[3] 指定Android Beam文件传输

因为Android Beam文件传输只在安卓4.1(API level 16)及以上使用,如果应用程序只是将NFC作为一个选择来使用,就必须将的属性这样设置:android:minSdkVersion=”16”。否则,就要将android:minSdkVersion设置为其它值,并测试此部分。接下来描述。

(2) 测试是否支持Android Beam 文件传输

用以下元素来指定应用程序的NFC是可选的:

<uses-feature android:name="android.hardware.nfc" android:required="false" />


如果将android:required属性设置为false,必须在代码中测试是否支持NFC和Android Beam文件传输。

在代码中测试是否支持Android Beam文件传输,首先用FEATURE_NFC参数传递给PackageManager.hasSystemFeature()方法来测试设备是否支持NFC。接着,通过测试SDK_INT值来检查安卓的版本是否支持Android Beam文件传输。如果支持Android Beam文件传输,那么就获取NFC控制器的实例,NFC控制器能够和NFC硬件通信。举例:

public class MainActivity extends Activity {
...
NfcAdapter mNfcAdapter;
// Flag to indicate that Android Beam is available
boolean mAndroidBeamAvailable  = false;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// NFC isn't available on the device
if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
/*
* Disable NFC features here.
* For example, disable menu items or buttons that activate
* NFC-related features
*/
...
// Android Beam file transfer isn't supported
} else if (Build.VERSION.SDK_INT <
Build.VERSION_CODES.JELLY_BEAN_MR1) {
// If Android Beam isn't available, don't continue.
mAndroidBeamAvailable = false;
/*
* Disable Android Beam file transfer features here.
*/
...
// Android Beam file transfer is available, continue
} else {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
...
}
}
...
}


(3) 创建提供文件的回调方法

一旦确定设备支持Android Beam文件传输,需要增加一个当Android Beam文件传输检测到用户想要发送文件到另外一个NFC使能设备时系统会自动调用的回调方法。这个方法会返回 Uri对象序列。Android Beam文件传输会复制URIs所对应的文件到接收设备之上。

欲增加此回调方法,需实现NfcAdapter.CreateBeamUrisCallback接口以及其createBeamUris()方法。以下代码片段演示如何完成此过程:

public class MainActivity extends Activity {
...
// List of URIs to provide to Android Beam
private Uri[] mFileUris = new Uri[10];
...
/**
* Callback that Android Beam file transfer calls to get
* files to share
*/
private class FileUriCallback implements
NfcAdapter.CreateBeamUrisCallback {
public FileUriCallback() {
}
/**
* Create content URIs as needed to share with another device
*/
@Override
public Uri[] createBeamUris(NfcEvent event) {
return mFileUris;
}
}
...
}


完成此接口后,调用setBeamPushUrisCallback()来将回调方法提供给Android Beam文件传输。以下代码片段演示如何实现此过程:

public class MainActivity extends Activity {
...
// Instance that returns available files from this app
private FileUriCallback mFileUriCallback;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Android Beam file transfer is available, continue
...
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
/*
* Instantiate a new FileUriCallback to handle requests for
* URIs
*/
mFileUriCallback = new FileUriCallback();
// Set the dynamic callback for URI requests.
mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);
...
}
...
}


注:通过应用程序的NfcAdapter实例也可以直接将Uri对象序列提供给NFC框架。如果在NFC触摸事件发生之前就为传输定义了URIs就可以使用此方法。与知更多关于此方法的知识,见NfcAdapter.setBeamPushUris()。

(4) 指定欲发送的文件

欲发送一个或更多的文件到其它的NFC使能的设备上,需获取每个文件的URI并需将UIR添加到Uri对象序列中。与传输文件,必须要有对文件的永久读取权限。举例,以下代码片段演示怎么根据文件名获取文件URI以及怎么将URI添加到Uri对象序列中:

/*
* Create a list of URIs, get a File,
* and set its permissions
*/
private Uri[] mFileUris = new Uri[10];
String transferFile = "transferimage.jpg";
File extDir = getExternalFilesDir(null);
File requestFile = new File(extDir, transferFile);
requestFile.setReadable(true, false);
// Get a URI for the File and add it to the list of URIs
fileUri = Uri.fromFile(requestFile);
if (fileUri != null) {
mFileUris[0] = fileUri;
} else {
Log.e("My Activity", "No File URI available for file.");
}


2015.11.07

3.2 从其它设备接收文件

学习怎么设置应用程序去接收其它设备发送的文件。

Android Beam文件传输将文件复制到接收设备的一个特殊目录下。接收设备同时会用安卓媒体扫描器扫描复制过来的文件并将媒体文件的条目增加给MediaStore提供器。此部分笔记将演示当文件复制完毕后接收设备如何响应,以及如何定位接收到的文件。

(1) 响应展示数据的请求

当Android Beam文件传输将文件复制到接收设备后,它将给用户包含的ACTION_VIEW动作和指向所传输的第一个文件的MIME类型及内容URI的目的的通知。当用户点击这个通知,此目的就会被发送给系统。欲让应用程序能够响应此目的,在相应活动的xml布局文件中的元素下添加元素。在元素下,增加以下子元素:

匹配由通知发送来的ACTION_VIEW目的。

匹配无明确类别的目的。

匹配MIME类型。指定应用程序能够处理的MIME类型。

以下代码片段演示如何增加目的过滤器来触发com.example.android.nfctransfer.ViewActivity活动:

<activity
android:name="com.example.android.nfctransfer.ViewActivity"
android:label="Android Beam Viewer" >
...
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
...
</intent-filter>
</activity>


注:Android Beam文件传输并不是 ACTION_VIEW 目的的唯一来源。在接收设备上的其它应用程序也可以发送包含 ACTION_VIEW动作的目的。关于这种情况的处理见Get the directory from a content URI。

(2) 请求文件权限

欲读Android Beam文件传输拷贝到接收设备上的文件,请求READ_EXTERNAL_STORAGE权限。举例:

如果想将复制过来的文件拷贝到应用程序自己的存储区域,请求WRITE_EXTERNAL_STORAGE权限。WRITE_EXTERNAL_STORAGE包含了READ_EXTERNAL_STORAGE权限。

注:若用户要做以上操作,WRITE_EXTERNAL_STORAGE权限在安卓4.2.2(API level 17)中是必须的。将来的版本或许会在所有的情况都要求此权限。为保证程序的兼容性,在程序中请求此权限。

因为应用程序对内部存储器拥有绝对的控制权限,所以当将文件拷贝到内部存储器中时不必请求写权限。

(3) 获取复制过来的文件的目录

Android Beam文件传输用单个传输复制所有的文件到接收设备上的一个目录下。包含文件的内容URI的目的由Android Beam文件传输指向第一个文件的通知发送。然而,应用程序也可能从本设备的其它应用程序中接收到ACTION_VIEW动作的目的。为处理这种情况,需要检查目的的权威(authority)和方案(scheme)。

调用Uri.getScheme()可获取URI的方案。以下代码片段演示如何根据URI而获取相应的方案:

public class MainActivity extends Activity {
...
// A File object containing the path to the transferred files
private File mParentPath;
// Incoming Intent
private Intent mIntent;
...
/*
* Called from onNewIntent() for a SINGLE_TOP Activity
* or onCreate() for a new Activity. For onNewIntent(),
* remember to call setIntent() to store the most
* current Intent
*
*/
private void handleViewIntent() {
...
// Get the Intent action
mIntent = getIntent();
String action = mIntent.getAction();
/*
* For ACTION_VIEW, the Activity is being asked to display data.
* Get the URI.
*/
if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
// Get the URI from the Intent
Uri beamUri = mIntent.getData();
/*
* Test for the type of URI, by getting its scheme value
*/
if (TextUtils.equals(beamUri.getScheme(), "file")) {
mParentPath = handleFileUri(beamUri);
} else if (TextUtils.equals(
beamUri.getScheme(), "content")) {
mParentPath = handleContentUri(beamUri);
}
}
...
}
...
}


[1] 由文件URI获取文件目录

如果传入的目的包含文件URI,URI会包含文件的全名,包括文件的路径及文件名。对Android Beam文件传输,此目录路径同时代表其它复制过来的文件的目录。欲获取此目录路径,需要获取URI的路径部分,此部分包含了URI中除前缀的全部。从路径部分创建一个文件,然后获取此文件的父目录:

//……
public String handleFileUri(Uri beamUri) {
// Get the path part of the URI
String fileName = beamUri.getPath();
// Create a File object for this filename
File copiedFile = new File(fileName);
// Get a string containing the file's parent directory
return copiedFile.getParent();
}
//……


[2] 由内容URI获取文件目录

如果传入的目的包含内容URI,URI可能指向存储在 MediaStore内容提供器中目录和文件名。通过测试URI的权威值可检测 MediaStore的内容URI。 MediaStore的内容URI可能来自Android Beam文件传输或者其它应用程序,不管哪一种情况都可以根据内容URI检索到目录和文件名。

也可以接收为内容提供器包含内容URI的ACTION_VIEW目的。在这种情况下,内容URI不包含 MediaStore权威值,且内容URI也通常不会指向目录。

注:对Android Beam文件传输来说,在收到的ACTION_VIEW目的中的内容URI所代表的传入的第一份文件如果包含”audio/“、”image/“或”video/*”的MIME类型,那么就说明这些文件时媒体相关的。Android Beam文件传输通过在存储传输文件目录下运行媒体扫描器来索引传输过来的媒体文件。媒体扫描器将结果写进 MediaStore内容提供器中,然后它第一个文件的内容URI传递给Android Beam文件传输。这个内容URI是所接收的通知目的中的其中一个内容URI。欲获取第一个文件的目录,用相应的内容URI从 MediaStore中检索。

[3] 判断内容提供器

欲检查是否可以根据内容URI检索文件的目录,调用Uri.getAuthority() 来检查跟URI关联的内容提供器。可能会有两种情况的结果:

MediaStore.AUTHORITY

文件的URI由记录 MediaStore。从 MediaStore检索文件的全名,并获取文件所在目录。

其它的权威值

从其它的内容提供器中得来的内容URI。展示跟内容URI相关的数据,但不能获取文件的目录。

欲获取MediaStore内容URI的目录,运行一个为Uri指定传入内容URI去查询参数和列MediaColumns.DATA达到预测的目的。返回的光标包含URI代表的文件的路径和命名。此路径也是其他通过Android Beam文件传输复制过来的文件的路径。

以下代码片段展示如何测试被传输的文件的内容URI的权威以及怎么检索路径和文件名:

//……
public String handleContentUri(Uri beamUri) {
// Position of the filename in the query Cursor
int filenameIndex;
// File object for the filename
File copiedFile;
// The filename stored in MediaStore
String fileName;
// Test the authority of the URI
if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
/*
* Handle content URIs for other content providers
*/
// For a MediaStore content URI
} else {
// Get the column that contains the file name
String[] projection = { MediaStore.MediaColumns.DATA };
Cursor pathCursor =
getContentResolver().query(beamUri, projection,
null, null, null);
// Check for a valid cursor
if (pathCursor != null &&
pathCursor.moveToFirst()) {
// Get the column index in the Cursor
filenameIndex = pathCursor.getColumnIndex(
MediaStore.MediaColumns.DATA);
// Get the full file name including path
fileName = pathCursor.getString(filenameIndex);
// Create a File object for the filename
copiedFile = new File(fileName);
// Return the parent directory of the file
return new File(copiedFile.getParent());
} else {
// The query didn't work; return null
return null;
}
}
}
//……


欲学习更多关于根据内容提供器而检索数据的,见Retrieving Data from the Provider.。

[2015.11.11-10:27]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: