内容分享之用NFC分享文件

作者: keith666 | 来源:发表于2016-06-16 16:57 被阅读729次

    Android允许你通过Android Beam功能来传输大文件,不过该功能只能在带有NFC并且Android版本在(4.1及以上 API 16)的设备上运行.更多关于Android Beam的内容可以看Beaming NDEF Messages to Other Devices,NFC的可以看Near Field Communication.

    1. 发送文件到其他设备

    要通过NFC发送文件,需要做到以下三点:

    • 请求权限(NFC和external storage的使用权限).
    • 测试设备是否支持NFC.
    • 给Android Beam提供URI.

    对于Android Beam文件传输功能,有四点要求:

    • 只支持Android 4.1(API 16)及以上.
    • 传输的文件必须是在external storage中.
    • 所有你要发送的文件的必须是world-readable的,你也可以通过File.setReadable(true,false)来设置该权限.
    • 你必须要给要传输的文件提供相应的URI(不能用 FileProvider.getUriForFile生成的Content URI).

    1.1 在Manifest中声明

    1.1.1 请求权限

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

    注意READ_EXTERNAL_STORAGE这个权限,在API 19以前不需要,但是从API 19开始就需要了,所以这里还是要加上,为了兼容.

    1.1.2 设置NFC feature

    因为NFC是属于hardware feature,需要添加<uses-feature>标签,如下:

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

    注意:

    • android:required为true表示如果你的设备没有该feature,则你的app就不能运行. 如果该功能只是你的一个可选择的功能,你应该将android:required的值设为false.
    • 这个属性只是告知性的,Google Play会用这个属性来过滤你的设备不支持的应用.
    • 设置了<uses-feature>还要记得添加相应的权限.

    1.1.3 设置SDK版本

    因为Android Beam只支持Android 4.1(API 16)及以上,你过你的app必须要该功能,那么需要设置android:minSdkVersion="16".

    1.2 测试设备是否支持Android Beam

    要测试设备是否支持Android Beam,需要三步:

    i. 将NFC这个featrue设置成optional,如下:

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

    ii. 测试设备是否支持NFC.调用PackageManager.hasSystemFeature()方法并将FEATURE_NFC作为参数传入.

    // NFC isn't available on the device
    if (!getPackageManager().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
    }
    

    iii. 测试设备是否支持Android Beam.通过判断Android版本来测试.

    // Android Beam file transfer isn't supported
    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
    }
    

    在验证设备已经支持NFC和Android Beam后,使用时还有判断设备是否开启了NFC和Android Beam功能,可以分别用下面两个方法:
    a. NfcAdapter的isEnabled()来验证NFC功能是否开启.
    b. NfcAdapter的isNdefPushEnabled ()来验证是否开启了Android Beam功能.

    1.3 创建提供文件的回调方法

    一旦你验证了你的设备支持Android Beam来进行文件传输之后,需要添加一个回调方法来返回一组Uri对象,这些对象是你想要传输的文件的复制品的URI,但Android Beam检测到你想要分享文件给其他设备时,系统就会调用该回调方法. 要添加该回调方法,要实现NfcAdapter.CreateBeamUrisCallback这个接口然后override里面的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;
            }
        }
        ...
    }
    

    实现了接口后,就可以将该接口实现类的实例提供给Android Beam,通过setBeamPushUrisCallback()方法:

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

    注意: 除了通过setBeamPushUrisCallback()来提供URI之外,还可以使用setBeamPushUris()(方法,只不过前者是动态的,后者是固定的.

    1.4 设置要发送的文件

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

    2. 从其他设备接收文件

    2.1 响应展示数据的请求

    当Android Beam传输问文件之后,它会弹出一个notification,里面包含有intent,这个intent由一个action为ACTION_VIEW,MIME类型为第一个文件的文件类型以及第一个文件的URI组成.当用户点击这个notification之后,这个intent就会被发出. 要让你的app来响应该intent,你需要在manifest中相应的Activity下添加<intent-filter>标签.如下示例:

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

    2.2 请求文件读取权限

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

    2.3 获取复制过来的文件的路径

    Android Beam将所有复制过来的文件放在一个路径下,上述提到的intent中包含了第一个文件的URI,但是你的app也有可能被其他的app启动不一定是Android Beam,而其他启动你的app的intent携带的URI可能是不一样的格式,所以你要判断Uri的scheme和authority来决定如何处理:

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

    2.3.1 处理file URI

    File 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.3.2 处理Content URI

    如果intent包含的是content URI,那么该URI指向的文件的路径和文件名可能存在MediaStore中.你可以通过测试URI的authority的值来判断其是否来自MediaStore. MediaStore的URI可能是来自Android Beam传输过来的也有可能是其他app传过来的,但是两者都能拿到路径和文件名.
    根据URI的authority来判断content provider的类型,然后进行相应的处理:

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

    Reference

    1. Sharing Files with NFC
    2. Sending Files to Another Device
    3. Receiving Files from Another Device

    相关文章

      网友评论

        本文标题:内容分享之用NFC分享文件

        本文链接:https://www.haomeiwen.com/subject/dyimdttx.html