1. 程式人生 > >Android使用NFC傳檔案

Android使用NFC傳檔案

傳送檔案給其他裝置


本節將向您介紹如何設計應用程式,以使用Android Beam檔案傳輸將大檔案傳送到另一臺裝置。要傳送檔案,您請求使用NFC和外部儲存的許可權,測試以確保您的裝置支援NFC,並提供URI到Android Beam檔案傳輸。

Android Beam檔案傳輸功能有以下要求:

  1. 適用於大檔案的Android Beam檔案傳輸僅適用於Android 4.1(API級別16)及更高版本。
  2. 要傳輸的檔案必須位於外部儲存器中。
  3. 您要傳輸的每個檔案必須是全域性可讀的。您可以通過呼叫File.setReadable(true,false)方法來設定此許可權。
  4. 您必須為要傳輸的檔案提供檔案URI。 Android Beam檔案傳輸無法處理由FileProvider.getUriForFile生成的內容URI。

宣告Manifest中的功能

首先,修改應用Manifest以宣告應用需要的許可權和功能。

請求許可權
要允許您的應用使用Android Beam檔案傳輸功能使用NFC從外部儲存裝置傳送檔案,您必須在應用Manifest中請求以下許可權:

NFC
允許應用程式透過NFC傳送資料。要指定此許可權,請將以下元素作為<manifest>元素的子元素新增:

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

READ_EXTERNAL_STORAGE
允許應用程式從外部儲存裝置讀取。要指定此許可權,請將以下元素作為<manifest>元素的子元素新增:

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

注意:從Android 4.2.2(API級別17)開始,不會強制實施此許可權。未來版本的平臺可能需要它從想要從外部儲存讀取的應用程式。為了確保向前相容性,請在需要之前立即請求許可。

指定NFC功能

通過新增<uses-feature>元素作為<manifest>元素的子元素,指定您的應用使用NFC。將android:required屬性設定為true表示您的應用程式將不會執行,除非NFC存在。

以下程式碼段顯示如何指定<uses-feature>元素:

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

注意,如果你的應用程式只使用NFC作為侯選項,但假如NFC不存在你仍想它工作,你應該設定android:required為false,並在程式碼中測試NFC。

指定Android Beam檔案傳輸

由於Android Beam檔案傳輸僅在Android 4.1(API級別16)及更高版本中可用,如果您的應用程式依賴於Android Beam檔案傳輸的一部分功能,您必須使用android:minSdkVersion指定<uses-sdk>元素=“16”屬性。否則,您可以根據需要將android:minSdkVersion設定為另一個值,並在程式碼中測試平臺版本。

測試Android Beam檔案傳輸是否支援

要在您的應用清單中指定NFC是可選的,請使用以下元素:

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

如果你設定屬性android:required =“false”,你必須在程式碼中測試NFC支援和Android Beam檔案傳輸是否支援。

要測試程式碼中的Android Beam檔案傳輸支援,首先通過呼叫帶有引數FEATURE_NFC的PackageManager.hasSystemFeature()來測試裝置是否支援NFC。接下來,通過測試SDK_INT的值來檢查Android版本是否支援Android Beam檔案傳輸。如果支援Android Beam檔案傳輸,請獲取NFC控制器的例項,允許您與NFC硬體通訊。例如:

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

建立提供檔案的回撥方法

一旦您驗證裝置支援Android Beam檔案傳輸,請添加回調方法,系統在Android Beam檔案傳輸檢測到使用者想要將檔案傳送到另一個啟用NFC的裝置時呼叫。在這個回撥方法中,返回一個Uri物件陣列。 Android Beam檔案傳輸將由這些URI表示的檔案複製到接收裝置。

要添加回調方法,請實現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()提供回撥到Android Beam檔案傳輸。以下程式碼段顯示瞭如何執行此操作:

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

注意:您還可以通過應用程式的NfcAdapter例項將Uri物件陣列直接提供給NFC框架。如果可以在NFC觸控事件發生之前定義要傳輸的URI,請選擇此方法。要了解有關此方法的更多資訊,請搜尋NfcAdapter.setBeamPushUris()。

指定要傳送的檔案

要將一個或多個檔案傳輸到另一個啟用NFC的裝置,請為每個檔案獲取檔案URI(具有檔案方案的URI),然後將該URI新增到Uri物件陣列。 要傳輸檔案,您還必須具有該檔案的永久性讀取訪問許可權。 例如,以下程式碼段顯示如何從檔名獲取檔案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.");
        }

從其他裝置接收檔案

Android Beam檔案傳輸將檔案複製到接收裝置上的特殊目錄。它還使用Android Media Scanner掃描複製的檔案,並將媒體檔案的條目新增到MediaStore提供程式。下面將向您介紹如何在檔案複製完成後進行響應,以及如何在接收裝置上查詢已複製的檔案。

響應顯示資料的請求

當Android Beam檔案傳輸完成將檔案複製到接收裝置時,它將釋出一個通知,其中包含具有操作ACTION_VIEW的Intent,傳輸的第一個檔案的MIME型別和指向第一個檔案的URI。當用戶單擊通知時,此Intent將傳送到系統。要使您的應用程式響應此Intent,請為應該響應的Activity的<activity>元素新增一個<intent-filter>元素。在<intent-filter>元素中,新增以下子元素:

//匹配通知傳送的ACTION_VIEW Intent。
<action android:name="android.intent.action.VIEW" />

//匹配沒有顯式類別的Intent。
<category android:name="android.intent.category.CATEGORY_DEFAULT" />

//匹配MIME型別。僅指定您的應用程式可以處理的MIME型別。
<data android:mimeType="mime-type" />

例如,以下程式碼段顯示如何新增觸發Activitycom.example.android.nfctransfer.ViewActivity的Intent過濾器:

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

注意:Android Beam檔案傳輸不是ACTION_VIEW Intent的唯一來源。接收裝置上的其他應用程式也可以傳送具有此操作的Intent。處理這種情況將在後面討論。

請求檔案許可權

要讀取Android Beam檔案傳輸複製到裝置的檔案,請請求READ_EXTERNAL_STORAGE許可權。例如:

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

如果要將傳輸的檔案複製到應用程式自己的儲存區域,請改為請求WRITE_EXTERNAL_STORAGE許可權。 WRITE_EXTERNAL_STORAGE包括READ_EXTERNAL_STORAGE。

注意:從Android 4.2.2(API級別17),許可權READ_EXTERNAL_STORAGE僅強制執行,如果使用者選擇這樣做。未來版本的平臺可能在所有情況下都需要此許可權。為了確保向前相容性,請在需要之前立即請求許可。

由於您的應用程式可以控制其內部儲存區域,因此您無需請求寫入許可權即可將傳輸的檔案複製到內部儲存區域。

獲取已複製檔案的目錄

Android Beam檔案傳輸將單個傳輸中的所有檔案複製到接收裝置上的一個目錄。 Android Beam檔案傳輸通知傳送的內容Intent中的URI指向第一個傳輸的檔案。但是,您的應用程式也可能從Android Beam檔案傳輸之外的來源接收ACTION_VIEWIntent。要確定如何處理傳入的Intent,您需要檢查其方案和許可權。

要獲取URI的方案,請呼叫Uri.getScheme()。以下程式碼段顯示瞭如何確定方案並相應地處理URI:

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

從檔案URI獲取目錄

如果傳入的Intent包含檔案URI,則URI包含檔案的絕對檔名,包括完整的目錄路徑和檔名。對於Android Beam檔案傳輸,目錄路徑指向其他傳輸檔案的位置(如果有)。要獲取目錄路徑,請獲取URI的路徑部分,其中包含除file:字首之外的所有URI。從路徑部分建立一個檔案,然後獲取檔案的父路徑:

... ...
public String handleFileUri(Uri beamUri){
    //獲取URI的路徑部分
    String fileName = beamUri.getPath();
    //為此檔名建立一個File物件
    File copiedFile = new File(fileName);
    //獲取包含檔案的父目錄的字串
    return copiedFile.getParent();
}}
... ...

從內容URI獲取目錄

如果輸入Intent包含內容URI,則URI可以指向儲存在MediaStore內容提供者中的目錄和檔名。您可以通過測試URI的許可權值來檢測MediaStore的內容URI。 MediaStore的內容URI可能來自Android Beam檔案傳輸或來自其他應用程式,但在這兩種情況下,您都可以檢索內容URI的目錄和檔名。

您還可以接收傳入的ACTION_VIEWIntent,其中包含除MediaStore之外的內容提供者的內容URI。在這種情況下,內容URI不包含MediaStore許可權值,並且內容URI通常不指向目錄。

注意:對於Android Beam檔案傳輸,如果第一個傳入檔案的MIME型別為“audio / ”,“image / ”或“video / *”,則會在ACTION_VIEWIntent中接收到一個內容URI,是媒體相關的。 Android Beam檔案傳輸通過在儲存傳輸檔案的目錄上執行Media Scanner來對其傳輸的媒體檔案進行索引。 Media Scanner將其結果寫入MediaStore內容提供程式,然後將第一個檔案的內容URI傳回Android Beam檔案傳輸。此內容URI是您在通知Intent中收到的URI。要獲取第一個檔案的目錄,您可以使用內容URI從MediaStore中檢索它。

確定content provider

要確定是否可以從內容URI檢索檔案目錄,請通過呼叫Uri.getAuthority()來確定與URI關聯的內容提供者以獲取URI的許可權。結果有兩個可能的值:

MediaStore.AUTHORITY

  • URI用於由MediaStore跟蹤的一個或多個檔案。從MediaStore檢索完整的檔名,並從檔名獲取目錄。

任何其他許可權值

  • 來自另一內容提供商的內容URI。顯示與內容URI相關聯的資料,但不獲取檔案目錄。

要獲取MediaStore內容URI的目錄,請執行一個查詢,指定Uri引數的傳入內容URI和投影的列MediaColumns.DATA。返回的Cursor包含由URI表示的檔案的完整路徑和名稱。此路徑還包含Android Beam檔案傳輸剛剛複製到裝置的所有其他檔案。

以下程式碼段顯示如何測試內容URI的許可權,並檢索傳輸檔案的路徑和檔名:

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