12/08/2018, 14:17

Chia sẻ Files với NFC trong Android

NFC là gì? NFC(Near-Field Communications) là công nghệ kết nối không dây phạm vi tầm ngắn trong khoảng cách 4 cm, sử dụng cảm ứng từ trường để thực hiện kết nối giữa các thiết bị khi có sự tiếp xúc trực tiếp hay để gần nhau. NFC được phát triển dựa trên nguyên lý nhận dạng bằng tín hiệu tần số ...

NFC là gì?

NFC(Near-Field Communications) là công nghệ kết nối không dây phạm vi tầm ngắn trong khoảng cách 4 cm, sử dụng cảm ứng từ trường để thực hiện kết nối giữa các thiết bị khi có sự tiếp xúc trực tiếp hay để gần nhau. NFC được phát triển dựa trên nguyên lý nhận dạng bằng tín hiệu tần số vô tuyến (Radio-frequency identification - RFID), hoạt động ở dải băng tần 13.56 MHz và tốc độ truyền tải dữ liệu tối đa 424 Kbps.

Do khoảng cách truyền dữ liệu khá ngắn nên giao dịch qua công nghệ NFC được xem là an toàn.Thiết bị được trang bị NFC thường là điện thoại di động, có thể giao tiếp với các thẻ thông minh, đầu đọc thẻ hoặc thiết bị NFC tương thích khác. Ngoài ra, NFC còn được kết hợp nhiều công nghệ sử dụng trong các hệ thống công cộng như bán vé, thanh toán hóa đơn…

Android cho phép bạn chuyển các tập tin lớn giữa các thiết bị di động sử dụng tính năng Android Beam file transfer. Tính năng này có một API đơn giản, dễ sử dụng cho phép người dùng thực hiện chuyển file chỉ bằng cách chạm giữa các thiết bị. Trong response, Android Beam file transfer tự động copy các file từ thiết bị khác và hiện thông báo khi hoàn tất.

Trong khi Android Beam file transfer API xử lý một lượng lớn dữ liệu, thì Android Beam NDEF transfer API (hỗ trợ từ Android 4.0 trở lên) sẽ xử lý một lượng nhỏ dữ liệu như là các URI hoặc các message nhỏ. Ngoài ra Android Beam chỉ là một trong những tính năng có sẵn của Android NFC framework, cho phép bạn đọc message NDEF từ NFC tags. Để tìm hiểu thêm về Android Beam, bạn có thể xem topic Beaming NDEF Messages to Other Devices. Để tìm hiểu thêm về NFC framework: Near Field Communication API guide.

Gửi Files cho các thiết bị khác

Ta sẽ sử dụng Android Beam file transfer để thực hiện gửi một file có dung lượng lớn tới các thiết bị khác. Để có thể thực hiện gửi files, bạn cần có quyền sử dụng NFC và external storage, thiết bị thì phải hỗ trợ NFC, và cung cấp URIs để thực hiện Android Beam file transfer.

Tính năng Android Beam file transfer yêu cầu:

  1. Android 4.1 trở lên (API 16 trở lên).
  2. Files phải được lưu trữ trên bộ nhớ ngoài external storage. Và điều này tức là bạn phải cần có quyền đọc android.permission.WRITE_EXTERNAL_STORAGE.
  3. Files muốn gửi phải đọc được. Bạn có thể thiết lập quyền này bằng cách sử dụng phương thức File.setReadable(true,false).
  4. Bạn phải cung cấp 1 URI cho files muốn gửi. Android Beam file transfer không thể xử lý một URIs được tạo bởi FileProvider.getUriForFile.

Khai báo Features trong Manifest

Đầu tiên, bạn cần khai báo các permissions và features cần thiết trong Manifest của app.

Yêu cầu quyền

NFC: để app có thể gửi files thông qua NFC ta cần phải có quyền

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

READ_EXTERNAL_STORAGE: và đồng thời tất nhiên phải có quyền đọc từ external storage để lấy files

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

Chỉ định tính năng NFC

Để chỉ định rằng app sử dụng tính năng NFC, ta thêm một <uses-feature> vào trong Manifest của app. Bạn thêm android:required="true" để chỉ rõ rằng app sẽ không hoạt động nếu tính năng NFC không có mặt.

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

Chú ý: app chỉ sử dụng NFC như một tùy chọn, vẫn có thể có chức năng dù NFC không có mặt, bạn nên set android:required là false và test NFC trong code.

Chỉ định Android Beam file transfer

Bởi vì Android Beam file transfer chỉ hỗ trợ từ API 16 trở lên, vì vậy bạn cần khai báo rõ trong <uses-sdk> là android:minSdkVersion="16", hoặc một API nào đó cao hơn.

defaultConfig {
    ...
    minSdkVersion 16
    ...
}

Test Android Beam File Transfer Support

Để chỉ định rằng NFC là một tùy chọn, thiết lập trong Manifest như sau:

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

Nếu bạn set android:required="false", thì bạn phải test NFC support và Android Beam file transfer support trong code.

Để thực hiện test Android Beam file transfer support, bắt đầu bằng việc test xem thiết bị có hỗ trợ NFC hay không bạn sử dụng phương thức PackageManager.hasSystemFeature() với tham số truyền là FEATURE_NFC. Tiếp theo, kiểm tra Android version bằng tham số SDK_INT. Nếu Android Beam file transfer được hỗ trợ, ta thực hiện khởi tạo NFC controller - thực hiện giao tiếp với NFC hardware.

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

Tạo phương thức Callback để cung cấp Files

Một khi bạn đã xác nhận răng thiết bị có hỗ trợ Android Beam file transfer, ta sẽ add một Callback để gọi hệ thống gọi đến khi Android Beam file transfer phát hiện ra một thiết bị NFC enable. Trong phương thức Callback này sẽ trả về một array của đối tượng Uri. Android Beam file transfer copy các files được thể hiện bởi các URIs từ thiết bị nhận.

Để add phương thức Callback, bạn cần implement NfcAdapter.CreateBeamUrisCallback và phương thức 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;
        }
    }
    ...
}

Khi bạn đã implement interface, để cung cấp Callback cho Android Beam file transfer ta gọi phương thức 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);
        ...
    }
    ...
}

Chú ý: Bạn cũng có thể cung cấp một array của đối tượng URI vào NFC framework thông qua NfcAdapter . Sử dụng phương pháp này nếu bạn có thể xác định các URIs trước khi có sự kiện chạm NFC xảy ra, để tìm hiểu thêm về vấn đề này tham khảo thêm tại NfcAdapter.setBeamPushUris().

Xác định Files để gửi

Để gửi một hoặc nhiều files đến thiết bị đã bật NFC, bạn cần có một file URI cho mỗi một file và add các URI đó vào một đối tượng array URI. Để chuyển một file, bạn cần quyền truy cập vào file đó.

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

Nhận File từ thiết bị khác

Android Beam file transfer copy files vào một thư mục đặc biệt trên các thiết bị nhận.

Trả lời một yêu cầu để hiển thị dữ liệu

Khi Android Beam file transfer hoàn thành việc copy files vào thiết bị nhận, nó sẽ thực hiện gửi một notification với action là ACTION_VIEW, loạiMIME của file được truyền đầu tên, và URI trỏ đến file đầu tiên. Khi người dùng click vào notification đó, một intent sẽ được gửi lên hệ thống. Để ứng dụng của bạn có thể trả lời lại yêu cầu này, bạn cần add một <intent-filter> cho <activity>.

<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"/>
            <data android:mimeType="mime-type" />
        </intent-filter>
</activity>

Chú ý: Không phải chỉ duy nhất Android Beam file transfer nhận và xử lý ACTION_VIEW, rất nhiều ứng dụng khác cũng có thể nhận và xử lý ACTION_VIEW.

Yêu cầu File Permissions

File nhận được ghi tại external storage nên tất nhiên bạn phải cần quyền READ_EXTERNAL_STORAGE rồi

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

Nếu bạn muốn copy file vào một vùng nào đó của storage, bạn cần yêu cầu quyền WRITE_EXTERNAL_STORAGE (quyền này đã bao gồm quyền READ_EXTERNAL_STORAGE)

Lấy thư mục Files được copy

Android Beam file transfer sẽ sao chép toàn bộ files trong một lần gửi vào một directory trên thiết bị nhận. URI trong Android Beam file transfer notification sẽ trỏ tới file đầu tiên được gửi. Tuy nhiên, ứng dụng của bạn cũng có thể nhận được intent ACTION_VIEW từ một nguồn khác, để xác định ứng dụng nên xử lý thế nào khi nhận được intent đến, bạn cần kiểm tra scheme và authority.

Để lấy scheme của URI, gọi phương thức 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);
            }
        }
        ...
    }
    ...
}

Lấy thư mục từ một file URI

Nếu Intent đến có chứa một file URI, URI này sẽ chứa tên file tuyệt đối - bao gồm đường dẫn thư mục đầy đủ và tên. Đối với Android Beam file transfer, đường dẫn trỏ đến location của files được gửi nếu có. Để lấy đường dẫn thư mục, lấy đường dẫn con của URI, trong đó có tất cả các URI trừ file: prefix, ta sẽ tạo một file từ đường dẫn con sau đó lấy đường dẫn cha của file.

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

Lấy thư mục từ nội dung của URI

Nếu một Intent đến có chưa một nội dung URI, URI này có thể trỏ đến một thư mục và file được lưu trữ trong MediaStore. Bạn có thể xác định một URI cho MediaStore bằng các kiểm tra giá trị URI's authority. Một nội dung URI for MediaStore có thể đến từ Android Beam file transfer hoặc đến từ một ứng dụng khác, nhưng trong cả 2 trường hợp, bạn đều có thể truy xuất một thư mục và tên file cho nội dung URI.

Xác định nhà cung cấp nội dung

Để xác định xem bạn có thể truy xuất một thư mục file từ URI, hãy xác định nhà cung cấp nội dung có liên quan tới các URI bằng cách gọi phương thức Uri.getAuthority() để lấy URI's authorit. Kết quả trả về gồm 2 giá trị:

  • MediaStore.AUTHORITY
  • Bất kỳ giá trị authority nào

Để lấy thư mục từ MediaStore URI, bạn cần thự hiện một query nội dung URI tới cho đối số Uri và cột MediaColumns.DATA. Ta sẽ thu được một Cursor bao gồm đường dẫn đầy đủ và file name được cung cấp bởi 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;
             }
        }
}

Để tìm hiểu thêm về truy xuất dữ liệu từ một nhà cung cấp, bạn có thể xem thêm Retrieving Data from the Provider.

0