集册 Android&Java 技术笔记 Building Apps with Content Sharing

Building Apps with Content Sharing

欢马劈雪     最近更新时间:2020-08-04 05:37:59

88
  • 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;
                  }
              }
          }
          ...
展开阅读全文