创建可以分享内容的应用

来源:互联网 发布:淘宝联盟无法登陆 编辑:程序博客网 时间:2024/05/17 07:44

原文链接:http://android.xsoftlab.net/training/building-content-sharing.html

第一篇文章,虽然是翻译,也会学习到一些内容的,以后会不定时更新的

这篇文章主要讲如何构建在不同应用和设备间共享数据的应用。该部分总共包含三部分,分别是:

  1. 共享简单数据
  2. 共享文件
  3. 通过NFC共享文件

共享简单数据

1. 发送简单数据到其他应用

1.1 发送文本内容

在应用间使用Intent发送、接收数据是最简单的分享内容的做法。Intent可以让用户使用他们喜欢的应用快速、简单的分享内容。

注意:在ActionBar上添加分享动作的最好做法就是使用ShareActionProvider,它从API14开始可用。

ACTION_SEND action最直接、常见的用法是在activity间发送文本内容。例如内置的浏览器应用可以把当前显示页面的URL作为文本内容分享给任何应用。这对于朋友间通过email或社交网络分享文章或网址来说很重要。下面是一个例子:

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);
如果安装的应用有匹配的,就运行。如果匹配的应用超过一个,就显示一个应用选择列表,来让用户选择要启动的应用。然而,如果调用Intent.createChooser(),传入构建好的Intent 对象,将返回一个永远展示选择器的Intent。 以下是改动后的代码:

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.2 发送二进制内容

二进制数据通过ACTION_SEND action并设置合适的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类型也可以使用“*/*”,但是这样只有处理一般数据流的activity才能匹配
  • 目标应用需要有相应的权限才能访问uri指向的数据。推荐的做法是:(1)在自己的ContentProvider中保存数据,确保其他应用有访问的权限。更好的提供访问权限的机制是使用per-URI权限,这是临时的并且只赋给目标应用。一个创建这样的ContentProvider的简单做法是使用FileProvider帮助类。(2)使用系统的MediaStore。MediaStore只有匹配video,audio和image的MIME类型,然而从Android 3.0 (APIlevel 11)开始,它也可以存储非media类型。content://类型的Uri传递给onScanCompleted()回调方法后,使用scanFile()就可以把文件插入MediaStore。要注意的是一旦添加到系统的MediaStore,这些内容就可以被设备的其他应用访问。

1.3 发送多条内容

为了发送多条内容,使用ACTION_SEND_MULTIPLE action,以及指向了目标内容的URI集合。MIME类型由要分享的内容而定。比如,要分享3张JPEG格式的图片,MIME类型还是“image/jpeg”。如果图片的格式不一致,MIME类型就是“image/*”,以此来匹配可以处理任何类型图片的Activity。如果分享的内容的类型有很多种,MIME类型是“*/*”。如前所述,由接收应用来解析、处理数据。示例如下:

ArrayList<Uri> imageUris = new ArrayList<Uri>();imageUris.add(imageUri1); // Add your image URIs hereimageUris.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.."));
和以前一样,要确保接收应用可以访问URI列表指向的数据。

2. 接收其他应用发送的简单数据

2.1 更新manifest文件

Intent 过滤器告诉系统一个应用的组件可以接收什么intent。和上一节创建带有ACTION_SEND动作的Intent相似,创建Intent过滤器是为了可以接收带有这种动作的Intent。在manifest文件中使用<intent-filter>节点添加intent过滤器。例如,如果你的应用可以接收文本内容,任何类型的一张图片,或任何类型的多张图片,manifest文件是这样:

<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>
当另一个应用想要分享这些东西时,你的应用就会作为带选项显示在列表里。如果用户选择了你的应用,相应的activity(.ui.MyActivity)就会启动。然后就可以自行处理这些内容。

2.2 处理收到的内容

为了处理Intent传递的内容,调用getIntent()得到Intent对象,然后就可以决定下一步要做什么。始终要记住的一点是如果这个Activity可以被系统的其他部分启动,如Launcher,那么当你检查intent时要考虑到这一点。

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类型不对,或者发送的图片太大。而且,记得在另一个线程中处理二进制数据而不是在主线程("UI"线程)。UI的更新可以和定位一个EditText一样简单,也可以和在图片上使用一个图片过滤器一样复杂。这都取决于你的应用接下来会做什么。

3. 添加简单的分享动作

Android4.0(API14)引进的ActionProvider让我们在ActionBar上添加高效、易用的分享动作变得非常简单,一旦在ActionBar上添加一个菜单项,它就会处理菜单的外观和动作。使用ShareActionProvider时,我们提供一个Intent,然后Intent处理其他的部分。

注意:ShareActionProvider也是从API14开始引进的。

3.1 更新菜单说明

为了使用ShareActionProvider,在menu.xml文件的对应项定义android:actionProviderClass 属性:

<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>
这表明了item的外观和ShareActionProvider的功能。然而你应该告诉它你想要分享什么。.

3.2 设置分享intent
为了让ShareActionProvider起作用,你必须提供给它一个分享Intent。这个分享Intent应该和在“发送简单数据到其他应用”一节中描述的一样,有ACTION_SEND动作和通过特定标签,如EXTRA_TEXT和EXTRA_STREAM,附加的数据。为了分配一个分享Intent,首先在Activity或Fragment中找到对应的菜单项,然后调用MenuItem.getActionProvider()得到ShareActionProvider的一个实例对象。使用setShareIntent()设置Intent。下面是一个例子:

private ShareActionProvider mShareActionProvider;...@Overridepublic 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 intentprivate void setShareIntent(Intent shareIntent) {    if (mShareActionProvider != null) {        mShareActionProvider.setShareIntent(shareIntent);    }}
你也许只需要在创建菜单栏的设置分享Intent,或者在UI变化时更新。例如,当使用图库应用全屏查看图片时,随着图片的滑动,分享Intent也会变。

共享文件

1. 设置文件分享

1.1 指定FileProvider

为应用定义FileProvider需要在manifest.xml文件中添加入口。该入口明确了生成URI时使用的authority,以及一个指明应用可以分享的目录的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的URI authority。例如"com.example.myapp.fileprovider"。对于自己的应用定义authority时可以使用包名+fileprovider。<meta-data>子节点指向了定义应用可以分享的目录的XML文件。android:resource 定义了文件的路径和名字。

1.2 指定可分享的目录
在manifest.xml文件添加FileProvider后,在/res/xml/目录下定义filepaths.xml文件指明应用可以分享的目录和名字:

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

<files-path> 标签分享应用内存的files/目录的子目录。images/目录实际代表的是files/images/子目录。

 <paths> 元素可以包含许多子元素,每一个都指明了要分享的不同目录。如使用<external-path> 分享内存中的目录;使用<cache-path>分享内存缓存目录。

Note: 在XML明确要分享的文件目录是唯一的方式。不能再编写代码时添加。

当这一切都完成后,当你的应用为一个文件产生URI时,它会包含在manifest.xml文件中定义的authority,路径和文件名。如下所示:

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

2. 分享一个文件

2.1 接收文件请求

应用应该包含一个文件选择Activity,其他应用使用带有ACTION_PICK的intent,并调用startActivityForResult()。在manifest.xml文件中注册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>
action:PICK;category:DEFAULT和OPENABLE。

2.2 定义文件选择Activity

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

2.3 响应文件选择

应用以ListView的形式列出所有的文件,当回调onItemClick()时,就代表选择了要分享的文件。根据得到的文件名字,生成一个File对象,并传递给getUriForFile(),生成一个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);                }                ...            }        });        ...    }
记住,只可以为在文件中指明了的目录下的文件生成URI,否则会抛出IllegalArgumentException。

2.4 为文件授权

为指定文件生成URI后,还需要为其指定读的权限。此时可以在Intent中添加权限标志。此时授予的是临时权限,应用的回退栈结束后,权限就无效了:

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);                }                ...             }             ...        });    ...    }
警告:使用setFlags()是唯一安全的授予临时权限访问文件的方式。避免使用Context.grantUriPermission(),因为这样授予的权限只能使用Context.removeUriPermission()移除。

2.5 在应用间分享文件

为了和请求应用分享文件,使用setResult()把包含URI值得Intent传递进去。系统会把Intent发送给请求应用:

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);                    }                }        });
给用户提供一种方式:当他们选择文件后,立即返回。此时可以在完成按钮上使用android:onCkick属性绑定一个监听来立即结束Activity:

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

3. 请求分享文件

3.1 发送文件请求

定义好Intent后,即设置了ACTION_PICK动作和数据的MIME类型,调用startActivityForResult()来启动目标Activity:

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

3.2 访问请求到的文件

客户端在onActivityResult()中处理服务器端发送过来的Intent数据。得到文件的URI后,就可以使用FileDescriptor来访问文件。

在此过程中,文件安全是受保护的,因为URI是客户端收到的唯一数据。由于URI不包含目录路径,客户端就不能访问服务器端的任何文件。只有客户端可以使用服务器端授予的权限访问文件。由于权限是临时的,一旦客户端结束,在服务器外,文件就不可访问:

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对象。

4. 检索文件信息

4.1 检索文件的MIME类型

客户端根据文件的数据类型决定如何处理。调用ContentResolver.getType()得到文件的类型,返回文件的MIME类型。默认FileProvider根据文件扩展名决定文件的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);    ...

4.2 检索文件名和大小

FileProvider默认实现的query()方法可以根据文件URI得到的Cursor对象返回文件的名字和大小。默认返回两列:

  • DISPLAY_NAME:文件名,String类型。和通过File.getName()得到的一样。
  • SIZE:文件大小,long类型。和通过File.lengh()得到的一样。
通过设置除URI之外的其他查询参数为null,客户端可以同时得到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)));    ...

通过NFC共享文件

1. 发送文件到其他设备

2. 从其他设备接收文件

最后的NFC还没有学习过,所以暂时没有翻译,等以后学习会回来更新的。

0 0
原创粉丝点击