内容分享之用NFC分享文件

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,646评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,979评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,391评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,356评论 0 215
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,740评论 3 293
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,836评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,022评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,764评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,487评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,728评论 2 252
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,200评论 1 263
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,548评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,217评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,134评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,921评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,919评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,766评论 2 274

推荐阅读更多精彩内容