Android使用NFC傳檔案
傳送檔案給其他裝置
本節將向您介紹如何設計應用程式,以使用Android Beam檔案傳輸將大檔案傳送到另一臺裝置。要傳送檔案,您請求使用NFC和外部儲存的許可權,測試以確保您的裝置支援NFC,並提供URI到Android Beam檔案傳輸。
Android Beam檔案傳輸功能有以下要求:
- 適用於大檔案的Android Beam檔案傳輸僅適用於Android 4.1(API級別16)及更高版本。
- 要傳輸的檔案必須位於外部儲存器中。
- 您要傳輸的每個檔案必須是全域性可讀的。您可以通過呼叫File.setReadable(true,false)方法來設定此許可權。
- 您必須為要傳輸的檔案提供檔案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;
}
}
}