Building Apps with Content Sharing

  • Sharing Simple Data
    • Intent && ActionProvider
    • 发送数据(发起intent调起其他app处理)
      • Send Text Content
      • Send Binary Content
      • Send Multiple Pieces of Content
    • 接收数据
      • AndroidManifest.xml中为Activity定义<intent-filter>
      • 在Activity的onCreate中调用getIntent()获取action、数据,并进行处理
  • Sharing Files
    • 唯一“安全”的方式就是:将文件对应的URI通过Intent发送出去,并为该URI提供临时的访问权限。而这些步骤都可以通过FileProvider完成
    • 在AndroidManifest.xml中声明provider
      <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>指定描述要分享的目录的xml文件
    • 指定要分享的目录
      <paths>
        <files-path path="images/" name="myimages" />
      </paths>
      
    • <paths>标签可以有多个子标签,<files-path>指定app的files目录下的分享目录名,<external-path>指定外部存储(Environment.getExternalStorageDirectory())的分享目录名,<cache-path>指定app的cache目录下的分享目录名;分享路径只能在xml中描述;
    • 如上配置后,需要访问files/images/default_image.jpg时,对应uri为:content://com.example.myapp.fileprovider/myimages/default_image.jpg
    • Receive File Requests
      • 定义一个Selection Activity,响应Intent action,例如:ACTION_PICK
        <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>
        
      • 其他app发起该intent,通过startActivityForResult()onActivityResult()中发起请求、处理结果
      • 在Selection Activity的onCreate函数中,解析其他app的请求
        File requestFile = new File(mImageFilename[position]);
        // 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);
        }
        
      • 赋予临时访问权限
        mResultIntent = new Intent("com.example.myapp.ACTION_RETURN_FILE");
        if (fileUri != null) {
           // Grant temporary read permission to the content URI
           mResultIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        
      • 使用setFlags()赋予临时访问权限更安全,Context.grantUriPermission()赋予的权限只有手动调用Context.revokeUriPermission()才会被移除
      • 返回结果
        mResultIntent.setDataAndType(
               fileUri,
               getContentResolver().getType(fileUri));
        // Set the result
        MainActivity.this.setResult(Activity.RESULT_OK,
               mResultIntent);
        finish();
        
    • Requesting a Shared File
      • 通常流程是:app发起一个带有请求的intent,分享文件的app的相应Activity被启动,该Activity显示文件列表,用户选择文件后返回被选中的文件的Uri
      • 发起请求
        mRequestFileIntent = new Intent(Intent.ACTION_PICK);
        mRequestFileIntent.setType("image/jpg");
        startActivityForResult(mRequestFileIntent, 0);
        
      • 访问返回的文件
        @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();
               ...
           }
        }
        
    • Retrieving File Information
      • MIME Type
        Uri returnUri = returnIntent.getData();
        String mimeType = getContentResolver().getType(returnUri);
        
      • File's Name and Size
        Uri returnUri = returnIntent.getData();
        Cursor returnCursor =
           getContentResolver().query(returnUri, null, null, null, null);
        int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
        nameView.setText(returnCursor.getString(nameIndex));
        sizeView.setText(Long.toString(returnCursor.getLong(sizeIndex)));
        
  • Sharing Files with NFC

    • Android Beam,大文件传输,from 4.1 API 16
    • Android Beam NDEF,小数据传输,from 4.0 API 14
    • 发送文件

      • 权限:<uses-permission android:name="android.permission.NFC" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      • NFC feature:<uses-feature android:name="android.hardware.nfc" android:required="true" />
      • minSdkVersion >= 16
      • 检查是否支持
        if (PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC) &&
               Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
           mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        }
        
      • 创建提供文件的回调

        // 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 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);
        ...
        
      • 设置需要发送的文件
        /*
        * 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.");
        }
        
    • 接收文件
      • 设置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>
        
      • 权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      • 获取接收文件的路径
        // 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);
           }
        }
        
      • 读取
        String fileName = beamUri.getPath();
        File copiedFile = new File(fileName);
        
      • 根据不同的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;
               }
           }
        }
        ...
        

results matching ""

    No results matching ""