Android知识要点整理(5)----文件分享

APP经常需要发送一个或多个文件给其他APP,例如,相册APP发送图片给图片编辑APP用来编辑图片,文件管理APP的复制和粘贴文件都涉及到文件分享功能。分享文件的唯一安全的方法是分享文件的content uri并临时授予访问权限给其他APP。这个拥有临时访问权限的content uri 是安全的,因为它只针对接收文件的APP授权,并且在访问完后权限自动失效。Android 系统提供了FileProvider类帮助我们实现文件分享的功能,下面我们就具体讲述一下如何使用它实现文件分享功能。

1.文件分享配置

为了实现安全的文件分享方式,我们需要通过Content uri 来分享文件。FileProvider类可以帮助我们生成这些要分享的文件的URI。

注册FileProvider

首先需要在AndroidManifest.xml文件中注册FileProvider类,并指定用于生成分享URI的Authority,同时还要指定生成规则的xml文件。

<provider  android:authorities="${package}.fileprovider" android:name="android.support.v4.content.FileProvider" android:grantUriPermissions="true" android:exported="false">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
</provider>

上述代码指定authority为APP包名加上“.fileprovider”后缀,使用meta-data标签指定生成规则的xml文件。

设置分享目录

生成规则的xml文件其实就是用来配置哪些目录下的文件是可以分享的,并为该目录指定一个字符串用于在content uri中唯一标识该目录。

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!-- APP internal storage 下的 images目录-->
    <files-path path="images/" name="myimages"/>
    <!-- APP external storage 下的 images目录-->
    <external-path path="images/" name="externalimages"/>
    <!-- APP 缓存目录 下的 images目录-->
    <cache-path path="images" name = "cacheimages"/>
</paths>

上述配置文件将APP internal 存储和外部存储以及缓存目录下的image目录设置为共享目录。name属性用于告诉FileProvider为对应目录下的文件生成content uri时需要加上该字段。例如,文件”file/images/default_image.jpg”对应的生成的content uri为:

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

其中com.example.myapp.fileproviderauthority指定的值,myimagesname属性指定的值。

2.开始分享文件

设置好分享目录之后,我们就可以响应其他APP的分享请求了。分享请求的Intent的action通常都是设置为ACTION_PICK,接受度这样的请求后,通常的响应方式是提供一个文件选择界面让用户选择需要的文件,然后发送响应文件的content uri给他。

创建FileSelectActivity

AndroidManifest.xml中注册用于处理分享请求的Activity。我们需要配置它的intent-filter,action 设置为ACTION_PICK,category设置为android.intent.category.OPENABLE,并配置可处理的数据类型为文本和图片类型。具体如下:

<activity  android:name=".FileSelectActivity" android:label="选择文件">
    <intent-filter>
         <action android:name="android.intent.action.PICK"/>
         <category android:name="android.intent.category.OPENABLE"/>
         <category android:name="android.intent.category.DEFAULT"/>
         <data android:mimeType="text/plain"/>
         <data android:mimeType="image/*"/>
    </intent-filter>
</activity>

这样我们就能接受文件分享的请求了,亦即我们的APP将会出现在选择列表里供用户选择。

响应分享请求

接下来就是处理分享逻辑了。关键代码如下所示:

......
mFilesLv = (ListView)findViewById(R.id.lst_files);
        String type = getIntent().getType();//获取请求的数据类型

        File rootDir = getFilesDir();
        if(TextUtils.equals("image/*",type)) {
            //预处理要展示的图片
            mImageDir = new File(rootDir, "images");
            if (!mImageDir.exists()) {
                mImageDir.mkdirs();
            }
            Log.d(TAG, "image dir=>" + mImageDir.getPath());
            mFiles = mImageDir.listFiles();
            if (mFiles != null) {
                convertFilesToMap();
            }
        }else if(TextUtils.equals("text/plain",type)){
            //TODO:处理文本文件的展示
        }else{
            //TODO:提示不支持的类型
        }
        //设置适配器
        mFilesLv.setAdapter(new SimpleAdapter(this,mMapList,android.R.layout.simple_list_item_2,
                new String[]{"name","size"},new int[]{android.R.id.text1,android.R.id.text2}));
        mFilesLv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                File selectFile = mFiles[position];
                //获取文件的URI
                Uri fileUri = FileProvider.getUriForFile(FileSelectActivity.this,
                        "com.github.znacloud.contentsharedemo.fileprovider",selectFile);
                Intent intent = new Intent();
                intent.setDataAndType(fileUri,getContentResolver().getType(fileUri));
                //授予临时权限
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                setResult(RESULT_OK,intent);
                finish();
            }
        });
......

需要注意的是,如果用户选择的文件不在xml配置文件中配置的目录中,FileProvider#getUriForFile()将会抛出异常IllegalArgumentException异常。所以一定要确保文件列表中展示的文件都是在配置目录里的。

3.请求分享文件

上面主要是讲述如何响应其他APP的分享请求。下面讲一讲如何主动发起请求去获取想要的文件,比如请求获取一张照片并上传到网络。

发起请求

发起请求通常是构造一个Intent,intent的action 为ACTION_PICK,并指定要请求的数据类型(MIME)。代码如下:

Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent,REQUEST_PICK);

处理返回结果

结果会在onActivityResult回调方法中返回。当我们得到要获取的文件的URI后,通过URI可以获得文件描述符FileDescriptor,有了文件描述符我们就可以访问文件内容了。前面讲过URI的授权问题,URI的授权是临时的,这个授权仅在当前APP进程的生命周期内有效。如果APP所在进程结束,则此次的授权失效,以后都不能再通过文件描述符直接访问文件,除非重新发起请求。获取文件描述符的代码如下:

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(resultCode == RESULT_OK){
            switch (requestCode){
                case REQUEST_PICK:
                    Uri fileUri = data.getData();
                    String type = data.getType();
                    mPathEt.setText(fileUri.toString());
                    try {
                        ParcelFileDescriptor pfd = 
                                getContentResolver().openFileDescriptor(fileUri, "r");
                        FileDescriptor fp = pfd.getFileDescriptor();
                        Log.d(TAG,"file desciptor=>"+fp.toString());
                        //如果为文本类型的文件
                         BufferedReader reader = new BufferedReader(new FileReader(fp));
                        String line;
                        while((line = reader.readLine()) != null){
                            Log.d(TAG,line);
                        }
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                    break;
                default:break;
            }
        }
    }

获取文件信息

通常我们在准备读取文件时,先要了解文件的大概信息,比如文件名称、大小、文件类型等等。
文件类型可以直接通过URI来获取

Uri fileUri = returnIntent.getData();
String mimeType = getContentResolver().getType(fileUri);

文件的名称和大小信息则可以通过FileProvider类获取到。FileProvider类默认实现了query()方法,该方法可以通过URI查询到文件的名字和大小信息,它返回两个字段信息:DISPLAY_NAMESIZE,
DISAPLAY_NAME对应的值等同于File#getName()的返回值,SIZE对应的值等同于File#length()的返回值。代码如下:

Uri returnUri = returnIntent.getData();
    Cursor returnCursor =
            getContentResolver().query(fileUri, null, null, null, null);
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
    returnCursor.moveToFirst();
    String fileName = returnCursor.getString(nameIndex));
    long fileSize = returnCursor.getLong(sizeIndex)));

4.通过NFC分享文件

使用NFC技术可以实现设备间传输文件。NFC全称Nearby Field Communication(近场通讯)。Android系统从4.0(API Level 14)开始支持NFC,它通过Android Beam NDEF transfer API提供接口来支持NFC。

权限声明

从4.1开始,Android Beam file transfer支持在设备间传输大文件。为了发送大文件到其他设备,我们首先要声明使用NFC和外部存储的权限,还需要手机硬件支持NFC功能,同时必须提供要传输文件的URI。
Android Beam file transfer具有如下特性:

  • 从4.1开始才支持大文件传输;
  • 要传输的文件必须存储在外部存储中(External-storage);
  • 要传输的每个文件必须是全局可读的(World-readable);
  • 必须提供要传输的文件URI,它不能处理由FileProvider生成的URI。

权限声明如下:

 <uses-permission android:name="android.permission.NFC" /><!-- NFC 权限-->
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 读外存权限-->

声明NFC特性:通过生命nfc feature,并设置required为true,表明没有NFC功能,APP将不能正常工作。

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

此外,为了能够使用Android Beam file transfer,需要设置androidMinSdkVerion=16,若低于16,则应该在代码中检测API版本,区别处理:

 ...
        // 不支持NFC
        if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
            //TODO:禁用NFC功能
            ...
        // Android Beam file transfer 不可用
        } else if (Build.VERSION.SDK_INT <
                Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 如果不可用,则不再继续
            mAndroidBeamAvailable = false;
            //TODO:禁用transfer

            ...
       } else {
           //如果都可用,那么可以做后续的工作了
           mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        ...
      }

设置回调方法

当系统检测到需要传输文件到另外一台支持NFC的设备时(这个通常是通过设备接触来触发),NfcAdapter.CreateBeamUrisCallback接口方法createBeamUris()会被调用,方法返回一个URI的数组,表示要传输的文件的URI,Android Beam file transfer会将这些URI代表的文件拷贝传输到另外一台设备。

@Override
public Uri[] createBeamUris(NfcEvent event) {
    return mFileUris;
}

我们需要实现一个该接口的类,然后设置给Android Beam file transfer

mFileUriCallback = new FileUriCallback();
// 根据传输请求类型动态设置该回调
mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);

指定要传输的文件

要传输的文件URI通常是scheme 为“file://”,并且确保文件的全局可读性。

private Uri[] mFileUris = new Uri[10];
        String transferFile = "transferimage.jpg";
        File extDir = getExternalFilesDir(null);
        File requestFile = new File(extDir, transferFile);
        requestFile.setReadable(true, false);

        fileUri = Uri.fromFile(requestFile);
        if (fileUri != null) {
            mFileUris[0] = fileUri;
        } else {
            Log.e("My Activity", "No File URI available for file.");
        }

接收文件

另外一台设备接收完文件后会收到Android Beam file transfer发出的广播,广播的Intent 的Action 为ACTION_VIEW,data为最后收到的文件的URI。我们可以定义一个处理这样的通知的Activity,将接收到的文件展现出来。因为通常接收到的文件都是存储在同一目录下,所以通过最后一个文件的uri我们可以得到存储目录,展示该目录下的文件,具体方法就不赘述了。

好了,以上就是Android 文件分享功能的重点知识。

你可能感兴趣的:(android,文件分享)