跟Google 学代码 :Building Apps with Content Sharing(跟女神去表白)

本篇博客都讲了些什么?

1. Sharing Simple Data 共享简单的数据,如文本,图片,URI
2. Sharing Files 共享文件
3. Sharing Files with NFC无线传输

在写博客之前,假设 “我”是服务端App,我的“女神”是客户端App

情书是“我”想传递的数据

那么我该如何做呢?

向客户端App发送数据(暗送秋波篇)

对女神爱在心中口难开怎么办?如何示爱这是穷学生最纠结的心病

在Android 开发中,当我们的App想对其他App “暗送秋波”传达点数据的时候,急需一个玩意来推动一把,专业点说,就是触发,那么究竟什么能触发数据的传送呢?

答案是:包含ACTION_SEND的Intent

你可以理解为,情书写好了,需要同桌帮忙传递(SEND)
示爱短信编辑ok,需要按下发送(SEND)按钮
数据只是载体,SEND(传递)是一种行为方式

发送文本内容(写情书)

给女神写一封情书,是年少时最懵懂的念想

那么在App之间,向心仪的那个App分享文本内容,自然是最基本的功能!

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "You are my girl");
sendIntent.setType("text/plain");
startActivity(sendIntent);

会发生什么?
写一个button自己观察结果吧!

Intent好比一个信封:

setAction()中定义了它的行为->发送出去
putExtra() 中定义信中的内容:少年们最热诚的心->你是我的女孩!(跟我走)
setType() 中定义了它所携带的数据类型,好比邮戳,告诉邮递员这是一封信(text文本内容),请送到能读懂它的人手中,亦或者是一部热烈纷呈的成人动作片(视频内容)->它包含多少宅男年少的梦想

当然,有时候,女神不止一个

Intent提供了createChooser()可以满足你向众多女神群发信息的功能,

Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "This is my the best love.");
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));

通过createChooser()可以弹出一个列表内容的dialog来展示一些可以接收该数据的App,

当然Intent不仅仅只有SEND功能,它还包裹 EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC, EXTRA_SUBJECT 等系统定义好的行为

发送字节数据(跟女神打电话)

这年头,做什么都要速度,发送信件这种文本信息似乎已经过时,情书未到,女神已经被隔壁班拿着Iphone 7的小帅哥抢走了-——人家可是天天跟女神打电话的!

痛定思痛,我们也决定要使用高科技传递信息->Binary 字节信息

 Uri uriImage=Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
                + getResources().getResourcePackageName(R.drawable.ic_launcher) + "/"
                + getResources().getResourceTypeName(R.drawable.ic_launcher) + "/"
                + getResources().getResourceEntryName(R.drawable.ic_launcher));

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

这里行为仍然是setAction中定义的Intent.ACTION_SEND

数据类型是image/jpeg

数据的内容换成了Intent.EXTRA_STREAM

发送Intent的方式也变了:createChooser来之定义可以接收到我们发出数据的 App

发送多个内容

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

跟之前两例代码的区别,仅仅在于setAction中定义新的行为:”Intent.ACTION_SEND_MULTIPLE“

setType中定义更大的范围”image/*“

总结:在这一节中,作为懵懂少年,我们通过Intent:

1. 用setType()定义了数据类型”image/*”, “text/plain”

2. 用setAction()定义了Intent的行为Intent.ACTION_SEND,
Intent.ACTION_SEND_MULTIPLE

3. 用putExtra()封装了数据的格式Intent.EXTRA_TEXT,EXTRA_EMAIL, EXTRA_CC, EXTRA_BCC, EXTRA_SUBJECT.

4. 最后通过startActivity将Intent“扔了出去”

关于URI是如何定义的,可以看这里URI的使用

接收其他App发来的数据(妹子如何接受表白)

作为校园的女神,公司的最闪亮的星星,自然会有不少男生追求,那么如何过滤掉不合适的追求呢?

更新清单文件(贴上人格标签,过滤信息)

通过上一节的内容,我们知道,情书的发出,需要定义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>

在Manifest文件里,对应的我们所期望的接收数据的Activity 下,我们通过来过滤那些“示爱信息”

<action > 要求 信息必须是SEND类型的,即发送方Intent的行为必须是ACTION_SEND,这样我们才会接受这个Intent

<category>一般为DEFAULT

<data> 定义了能接收数据类型image/* , text/plain

处理发来的信息,学会分类讨论

首先我们知道数据是通过Intent传输的,自然少不了对Intent的研究,在Activity中回调getIntent获得Intent对象,就可以操作发送过来的数据啦!

通常从其他地方发送过来的Intent不止一个怎么办?

不用怕,通过Intent的属性来区分:
比如下面的例子,就通过Intent.getAction和Intent.getType来 分类讨论

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

通过Google的代码,我们至少学会了两点:

  1. 分类讨论的思想,通过if-else if语句描述不同的情况处理办法,而具体情况的划分,是通过Intent的“ACTION”和“TYPE”类型来区分的

  2. 函数分装,当有几行代码描述一种行为的时候,就提取出来,作为一个函数,描述一种功能,比如:handleSendText,handleSendImage这样有意义的命名

值得警惕的地方:一定要额外小心 来自外部的信息,对于陌生的信息非常危险,因为陌生的东西往往是不可控的,比如有可能传递过来的是一张非常大的图片。

对于图片,或者其他URI数据,你必须使用子线程去处理他们的细节,而不是简单的接收,展示,保存等

切记不要再主线程处理某些数据类型,比如大图,或者数据流

Adding an Easy Share Action

在使用ActionBar的时候,如果加上ActionProvider效果会更棒!

更新Menu中item的声明

android:actionProviderClass 属性代表了被声明的item具有的功能

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

别高兴的太早,你还需要告诉provider 你希望去分享,把数据发送出去

设置分享功能的Intent

为了真正实现 ShareActionProvider功能,你需要提供一个intent,关于intent如何定义,在本文第一节已经有写过:ACTION_SEND和额外的信息 EXTRA_TEXT,EXTRA_STREAM

那么如何把这个Intent发送出去?

  1. 通过Menu获得MenuItem对象
  2. 通过MenuItem.getActionProvider()获得ShareActionProvider
  3. 通过ShareActionProvider的setShareIntent(inten)来发送inten
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);
    }
}

总结:

这个示例告诉我们,不仅仅只有startActivity才能发送Intent嘛!

ShareActionProvider的setShareIntent()也可以!

共享文件

什么情况需要共享?

比如图片工具美图秀秀,我之前独立开发的图简(PhotoStars),都需要获取本地相册中的图片,这就要求相册的图片“共享”

接下来,我们将通过使用FileProvider提供的getUriForFile()方法来生成文件的URI索引

设置文件共享

很遗憾,Android系统不想让开发者这么轻易的去使用它;

对于FileProvider 使用的前提是在清单文件中去配置

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

对于meta-data标签下的android:resource属性,很多人看起来费解,接下来将去解释它

重点内容特别的共享路径

在res/xml目录下创建filepaths.xml文件,它就是我们打算共享出去的文件,接下来的小例子展示了如何共享位于我们App内部存储的文件filepaths.xml

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

接着如何用代码获得这个URI呢?

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

关于标签的细节,可以查询 FileProvide的文档去复习。

共享一个文件

在上一步中我们为即将共享的文件,设置好了URI作为外部访问该文件的路径。

1. 那么我们的App该如何接收来自其他App的请求呢?

<font size=3>答案是在我们的App中提供一个选择列表,当其他客户端App通过startActivityForResult() 启动 一个ACTION设置为ACTION_PICK的Intent,我们在App中响应该Intent即可

关于如何请求服务端App共享文件,在下一节会分析。

2. 在Activity中创建文件选择列表

在写Activity代码之前,首先得再manifest中声明Activity的intent-filter属性

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

老生常谈了:

intent-filter定义的是当前activity可以接收的intent类型

intent-filte指定:

 必须是action定义为PICK

 共享的数据格式MIME定义为文本 text/plain 或图像 iamge/

3. 在activity中加入代码

首先写一个类继承Activity,用于显示我们App内部存储区域中files/image/目录下能够分享的文件,这里的示例通过

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

注释已经写的很明白了,这里总结两点Google代码的规范:
1. 变量命名都用mXX的格式,小写m开头,后面都是有意义的名称比如Image,ImagesDir,ImageFiles等
2. 写一段代码之前,思路想清楚, 从注释就可以看出,写出那段示例的人 思路很清晰,一步一步写出来的

4. 响应文件的选择

一旦用户选中了listview中的某一个item,就会触发onItemClick(),通过getUriForFile(),我们就可以把File以URI的形式告诉给其他App,以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);
                }
                ...
            }
        });
        ...
    }

在这段Google的代码里,又有很多地方值得我们学习

  1. 在这里进行了异常判断,在获取URI的时候,有可能该文件不存在,所以要进行catch捕获—>在写代码的时候,尽可能的考虑出错的情况,进行合理的处理,比如加上判断,或者异常捕获,来提高代码质量,养成代码检查的好习惯,而不是等待bug出现了采取埋头苦恼着错误原因!

  2. 学会了FileProvider.getUriForFile()的用法,可以将“某些特殊的文件”转换成URI

5. 获得权限
现在我们已经生成URI了,下一步要做的是允许其他App可以接触这个文件

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

总结:

  1. 设置标记的方式 可以i安全的获得我们App中的文件,并不需要Context.grantUriPermission() 去获得权限,通过它获得的权限有可能被Context.revokeUriPermission()撤销掉

  2. 优秀的代码习惯:,在使用fileUri之前,先判断是否为空

  3. 学会了Intent.addFlags的用法,

6.共享文件至客户端App

在这一步我们要做的就是 setResult(),它将把Intent中的数据返回给之前通过startAcitivtyForResult启动的Activity

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

在这一节

  1. 复习了Activity.RESULT_OK和RESULT_CANCELED两个参数

请求服务端App共享文件给客户端App

1.发送请求

通过startActivityFctivityorResult传递Intent,来启动其他App请求共享文件

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.如何使用文件

当服务端App返回文件的带有Uri的Intent时,这个Intent会传送至onActivityResult(),我们可以通过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();
            ...
        }
    }

恢复文件

请求文件并对该文件操作的时候,我们想获取该文件的信息,比如大小和类型,怎么办?

1.恢复文件的MIME Type

我们只需要通过 ContentResolver.getType()来获取

  ...
    /*
     * 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()返回Cursor对象,其中包含文件名和大小,问题很清楚了,那么我们只需要了解Cursor对象的api即可!

官方文档的解释:

DISPLAY_NAME

The file’s name, as a String. This value is the same as the value returned by File.getName().

SIZE

The size of the file in bytes, as a long This value is the same as the value returned by File.length()

那么使用方法就显而易见了:

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

总结:
1. 使用了FileProvider的query()和URI 来获取Cursor对象
2. 复习了Cursor对象,以及通过Cursor获取信息

Sharing Files with NFC(向外星人表白)

我们已经不满足于跟地球上的女神示爱了,接下来我们打算去跟月球上的嫦娥通信

那么在移动设备之间,该如何共享文件呢?

答案是通过NFC无线通信技术。

Android无线传输技术有几大特征

  1. 只有在Android4.1 API 16 及以上才可以传输大文件

  2. 想传输存储卡中的文件,你得了解如何使用存储卡(External Storage)

  3. 传输的文件必须是可读的,通过File.setReadable(true,false).来设置权限

  4. 必须将文件转换成URI的形式,FileProvider.getUriForFile可以为文件生成URI,接着我们就可以传输了

向另一台设备发送文件

1.Manifest声明

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

2. 指定NFC特性

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

3.检查设备是否支持 无线传输

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

如果android:required 设置false,我们就必须检查设备是否支持NFC

那么代码如何写呢?

通过PackageManager.hasSystemFeature( FEATURE_NFC)返回true或false来进行后续的操作

下面是Google的代码示例

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

这一节

  1. Build类来判断系统版本,比如Build.VERSION.SDK_INT, Build.VERSION_CODES.JELLY_BEAN_MR1

  2. PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)来判断系统特性

4.创建提供文件的回调方法

一旦我们确定手机设备支持无线传输,我们需要在activity中加入回调函数,实现 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().可以将回调提供给无线传输的文件。

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

6.指定传输的文件

最后通过Uri.fromFile(File)将文件转换出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.");
        }

接收通过NFC传输的文件

作为客户端,我们需要做三件事

1.声明权限

这里需要注意的是 ,在intent-filter中定义action为VIEW

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

定义请求文件的权限

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

如果你还想往自己的存储区存储点东西,那么你需要加上 WRITE_EXTERNAL_STORAGE 权限

WRITE_EXTERNAL_STORAGE 包含了READ_EXTERNAL_STORAGE的声明

此外,在高于Android 4.2.2 API17的版本, 文件可读权限 READ_EXTERNAL_STORAGE 是默认存在的,意思就是 不需要加入该权限,我们的App也可以读取一些本地存储的数据

2. 获得文件的目录

在拿到该文件之前,我们不确定该文件是通过带有ACTION_VIEW的Intent传输过来的,还是通过NFC无线传输过来的,这将决定我们如何处理该Intent,所以我们需要检查Intent的约束和来源

通过Uri.getScheme()可以完成上述需求

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

3. Get the directory from a 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(); } ... 

4. Get the directory from a content URI

对于content URI,我们需要使用MediaStore 来处理,MediaStore用起来有些复杂,请自行查阅API

5. 决定内容 提供者

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

参考

  1. Google 官方 Using the External Storage
  2. Google 官方内容提供者基础知识
  3. URI的使用
  4. App间交互

你可能感兴趣的:(android,谷歌,NFC,文件共享)