Được tạo bởi Blogger.

Labels

Thứ Sáu, 17 tháng 6, 2016

AndroidBasic - Hello World Example


Chúng ta hãy cùng bắt đầu lập trình thực với Android Framework. Trước khi bạn bắt đầu viết chương trình đầu tiên sử dụng Android SDK, bạn phải chắc chắn rằng bạn đã cài đặt môi trường phát triển Android chính xác như giải thích trong hướng dẫn Android - Environment Set-up. Tôi cũng giả sử rằng bạn có biết một chút kiến thức làm việc với Eclipse IDE.

Nên chúng ta hãy cùng tiếp tục viết một ứng dụng Android đơn giản sẽ in ra "Hello World!".

Tạo một ứng dụng Android
Bước đầu tiên là đi tạo một ứng dụng Android đơn giản sử dụng Eclipse IDE. Theo các bước sau: File -> New -> Project và cuối cùng chọn Android New Application. Bây giờ, tên ứng dụng của bạn là HelloWorld như hình:


Tiếp theo, theo các hướng dẫn sau và giữ nguyên tất cả các mục khác như mặc định đến bước cuối cùng. Một khi project của bạn được tạo thành công, bạn sẽ có giao diện làm việc dưới đây:


Cấu trúc của ứng dụng Android
Trước khi bạn chạy ứng dụng, bạn nên nhận biết một vài thư mục và file trong Android project - 


1. src: Thư mục này chứa các file nguồn .java cho project. Mặc định, nó bao gồm một file nguồn MainActivity.java có một activity class để chạy khi ứng dụng của bạn được bật lên bởi icon ứng dụng.

2. gen: Chứa file .R, một tập tin trình biên dịch tham khảo tất cả các nguồn được tìm thấy trong project. Bạn không nên chỉnh sửa file này.

3. bin: Thư mục này chứa các gói Android .apk được tạo ra bởi ADT trong suốt quá trình build và mọi thứ khác cần để chạy một ứng dụng Android.

4. res/drawable-hdpi: Chứa toàn bộ các đối tượng drawable được thiết kế cho các màn hình độ phân giải cao.

5. res/layout: Chứa toàn bộ các file định nghĩa giao diện người dùng của ứng dụng.

6. res/values: Chứa các file XML khác nhau, lưu trữ toàn bộ nguồn dữ liệu như định nghĩa string, colour,..

7. AndroidMainfest.xml: Đây là file mainfest miêu tả các đặc trưng cơ bản của ứng dụng, định nghĩa mỗi thành phần của nó.

Phần dưới đây sẽ cung cấp một cái nhìn tổng quan ngắn gọn một vài trong số các file quan trọng của ứng dụng. 

File Main Activity 
Main activity code là một file java MainActivity.java. Đây là file ứng dụng thực sự, sẽ nhận sự chuyển đổi tới một Dalvik executable và chạy ứng dụng của bạn. Dưới đây là code mặc định được tạo bởi wizard cho ứng dụng Hello World!

package com.example.helloworld;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MenuItem;
import android.support.v4.app.NavUtils;

public class MainActivity extends Activity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }
   
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.activity_main, menu);
      return true;
   }
}

Ở đây, R.layout.activity_main tìm đến file cục bộ activity_main trong thư mục res/layout. Phương thức onCreate() là một trong những phương thức được gọi đến khi một activity được load.

File Manifest
Bất cứ thành phần nào bạn phát triển như một phần của ứng dụng, bạn phải khai báo tất cả thành phần của nó trong file manifest.xml, được lưu trong địa chỉ gốc của ứng dụng. File này làm việc như một giao diện giữa Android OS và ứng dụng của bạn, nên nếu bạn không khai báo thành phần trong file này, nó sẽ không được xem xét bởi OS. Ví dụ, một file manifest mặc định sẽ trông như sau:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.helloworld"
   android:versionCode="1"
   android:versionName="1.0" >
   
   <uses-sdk
      android:minSdkVersion="8"
      android:targetSdkVersion="22" />
   
   <application
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
       
       <activity
          android:name=".MainActivity"
          android:label="@string/title_activity_main" >
       
          <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER"/>
          </intent-filter>
       
       </activity>
       
   </application>
</manifest>

Ở đây, các thẻ <application>...</application> là các thẻ có liên quan tới ứng dụng. Thuộc tính android:icon sẽ nhắm đến icon ứng dụng có sẵn trong đường dẫn res/drawable-hdpi. Ứng dụng sử dụng ảnh được đặt tên ic_launcher.png được đặt trong thư mục drawable.

Thẻ <activity> được sử dụng để định rõ một activity và android:name là thuộc tính chỉ rõ chất lượng đầy đủ tên class của subclass Activity và android:lable là thuộc tính định rõ một chuỗi để sử dụng như nhãn cho activity. Bạn có thể định rõ nhiều activity sử dụng các thẻ <activity>.

Action cho intent filter được đặt tên android.intent.action.MAIN để cho biết rằng activity này phục vụ như một đầu vào cho ứng dụng. Category cho intent-filter được đặt tên android.intent.category. LAUNCHER để cho biết rằng ứng dụng có thể được bật lên từ icon của thiết bị.

@string chuyển đến file string.xml được giải thích bên dưới. @string/app_name chuyển đến chuỗi app_name được định nghĩa trong file string.xml, và có tên "Hello World". Cách tương tự, các chuỗi khác nhận được tên thay thế trong ứng dụng.

Dưới đây là danh sách các thẻ, bạn sẽ sử dụng trong file manifest để định rõ sự khác nhau giữa các thành phần trong ứng dụng Android.

     + <activity> các phần tử cho các activity
     + <service> các phần tử cho các service
     + <receiver> các phần tử cho broadcast receiver
     + <provider> các phần tử cung cấp nội dung.

File Strings
File strings.xml được đặt trong thư mục res/values và nó chứa tất cả các text mà ứng dụng của bạn sử dụng. Ví dụ, tên của các button, label,..File này chịu trách nhiệm cho nội dụng của text. Ví dụ, một file strings mặc định sẽ nhìn giống như file dưới đây:

<resources>
   <string name="app_name">HelloWorld</string>
   <string name="hello_world">Hello world!</string>
   <string name="menu_settings">Settings</string>
   <string name="title_activity_main">MainActivity</string>
</resources>

File R
File gen/com.example.helloworld/R.java là sự liên kết giữa các file activity Java giống như MainActivity.java và các nguồn như strings.xml. Nó là một file tự động được tạo ra và bạn không nên điều chỉnh nội dung của file R.java. Dưới đây là mẫu của file R.java:

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.example.helloworld;

public final class R {
   public static final class attr {
   }
   
   public static final class dimen {
      public static final int padding_large=0x7f040002;
      public static final int padding_medium=0x7f040001;
      public static final int padding_small=0x7f040000;
   }
   
   public static final class drawable {
      public static final int ic_action_search=0x7f020000;
      public static final int ic_launcher=0x7f020001;
   }
   
   public static final class id {
      public static final int menu_settings=0x7f080000;
   }
   
   public static final class layout {
      public static final int activity_main=0x7f030000;
   }
   
   public static final class menu {
      public static final int activity_main=0x7f070000;
   }
   
   public static final class string {
      public static final int app_name=0x7f050000;
      public static final int hello_world=0x7f050001;
      public static final int menu_settings=0x7f050002;
      public static final int title_activity_main=0x7f050003;
   }
   
   public static final class style {
      public static final int AppTheme=0x7f060000;
   }
}

File Layout
activity_main.xml là một file layout có sẵn trong địa chỉ res/layout, là file tham khảo của ứng dụng khi xây dựng giao diện. Bạn sẽ điều chỉnh file này rất thường xuyên để thay đổi layout ứng dụng của bạn. Cho ứng dụng "Hello World!", file này sẽ có nội dung liên quan tới layout mặc định:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent" >
   
   <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerHorizontal="true"
      android:layout_centerVertical="true"
      android:padding="@dimen/padding_medium"
      android:text="@string/hello_world"
      tools:context=".MainActivity" />
      
</RelativeLayout>

Đây là một ví dụ đơn giản của RelaytiveLayout, cái bạn sẽ học trong một chương riêng biệt. TextView là một Android control được dùng để xây dựng giao diện và nó có các thuộc tính như android:layout_width, android:layout_height, ect.

Running the Application
Hãy thử chạy ứng dụng Hello World vừa mới tạo. Tôi giả sử bạn đã tạo AVD trong môi trường cài đặt. Để chạy ứng dụng từ Eclipse, mở một file activity của project và click Run từ thanh công cụ. Eclipse cài đặt ứng dụng trên AVD và khởi động nó, nếu mọi thứ đúng với cài đặt và ứng dụng, nó sẽ hiển thị như dưới đây:


Chúc mừng! Bạn đã phát hiển ứng dụng Android đầu tiên của mình và bây giờ chỉ cần bám theo các hướng dẫn phía dưới từng bước một trở thành một nhà phát triển Android. Mọi thứ đều rất tuyêt

AndroidAdvanced - Bluetooth Tutorial


Nền tảng Android hỗ trợ cho các mạng cục bộ Bluetooth, cho phép một thiết bị trao đổi dữ liệu không dây với các thiết bị Bluetooth khác. Framework ứng dụng cung cấp truy cập tới các chức năng Bluetooth thông qua các Android Bluetooth API. Các API này giúp các ứng dụng kết nối không dây tới các thiết bị Bluetooth khác, cho phép các tính năng không dây điểm điểm và đa điểm.

Sử dụng các Bluetooth API, một ứng dụng Android có thể thực hiện:
     + Quét các thiết bị Bluetooth khác
     + Truy vấn tới Bluetooth adapter cục bộ để ghép nối các thiết bị Bluetooth.
     + Thiết lập các kênh RFCOMM.
     + Kết nối tới các thiết bị khác thông qua service discovery 
     + Truyền dữ liệu tới và nhận dữ liệu từ các thiết bị khác.
     + Quản lý đa kết nối

Tài liệu này miêu tả các sử dung Classic Bluetooth. Classic Bluetooth là một lựa chọn chính xác cho các hệ điều hành tiết kiệm năng lượng như luồng và giao tiếp giữa các thiết bị Android. Để các thiết bị Bluetooth với yêu cầu năng lượng thấp, Android 4.3 hỗ trợ Bluetooth năng lượng thấp. Để học nhiều hơn, truy cập Bluetooth Low Energy.

The Basics

Tài liệu này miêu tả các sử dụng các Android Bluetooth API để thực hiện 4 nhiệm vụ chính cần thiết cho giao tiếp sử dụng Bluetooth: cài đặt bluetooth, tìm kiếm các thiết bị cho phép ghép nối hoặc có sẵn trong khu vực cục bộ, kết nối các thiết bị và truyền nhận dữ liệu giữa các thiết bị.

Tất cả các Bluetooth API đều có sẵn gói android.bluetooth. Ở đây tóm tắt các class và interface bạn sẽ cần để tạo các kết nối Bluetooth.

BluetoothAdapter: Đại diện cho các Bluetooth adapter cục bộ. Bluetoothadapter là một điểm vào cho tất cả các tương tác Bluetooth. Sử dụng nó, bạn có thể phát hiện các thiết bị bluetooth khác, truy bấn một danh sách các thiết bị đã ghép nối, khởi tạo một BluetoothDevice sử dụng một địa chỉ MAC đã biết, và tạo một BluetoothServerSocket để lắng nghe các giao tiếp từ các thiết bị khác.

BluetoothDevice: Đại diện cho một thiết bị điều khiển Bluetooth. Sử dụng nó để yêu cầu một kết nối với một thiết bị điều khiển thông qua một BluetoothSocket hoặc truy vấn thông tin về thiết bị như tên, địa chỉ, class và trạng thái kết nối.

BluetoothSocket: Đại diện giao diện cho một Bluetooth socket. Nó là một điểm kết nối, cho phép một ứng dụng trao đổi dữ liệu với một thiết bị bluetooth khác thông qua InputStream và OutputStream.

BluetoothServerSocket: Đại diện một server socket mở để lắng nghe các yêu cầu đến. Để kết nối hai thiết bị Android, một thiết bị phải mở một server socket với class này. Khi một thiết bị điều khiển bluetooth tạo một yêu cầu kết nối tới thiết bị này, BluetoothServerSocket sẽ trả về một BluetoothSocket đã kết nối khi kết nối được chấp nhận.

BluetoothClass: Miêu tả các đặc tính và khả năng chung của một thiết bị Bluetooth. Đây là một tập các thuộc tính chỉ đọc, định nghĩa các thông tin quan trọng của thiết bị. Tuy nhiên, nó không miêu tả một cách đáng tin cậy tất cả thông tin của Bluetooth và dịch vụ được hỗ trợ bởi thiết bị. Nhưng lại hữu ích như một loại thiết bị ẩn.

BluetoothProfile: Một interface đại điện cho hồ sơ Bluetooth. Một Bluetooth profile là một đặc tính giao diện không dây cho việc giao tiếp giữa các thiết bị Bluetooth. Một ví dụ là hồ sơ xử lý.

BluetoothHeadSet: Cung cấp hỗ trợ cho Bluetooth headset để sử dụng với các điện thoại di động. Class này bao gồm cả Bluetooth Headset và Hands-Free.

BluetoothA2dp: Định nghĩa chất lương audio có thể được truyền từ một thiết bị tới một thiết bị kết nối khác.

BluetoothHealth: Đại diện cho Health Device Profile, điều khiển các dịch vụ Bluetooth.

BluetoothHealthCallBack: Một abstract class mà bạn sử dụng để kế thừa BluetoothHealth. Bạn phải kế thừa class này và các phương thức của nó.

BluetoothHealthAppConfiguration
BluetoothProfile.SẻviceListener

Bluetooth Permissions

Để sử dụng các tính năng Bluetooth trong ứng dụng của bạn, bạn phải khai báo giấy phép Bluetooth. Bạn cần giấy phép này để thực hiện bất cử giao tiếp Bluetooth nào, như yêu cầu một kết nối, chấp nhận một kết nối và truyền dữ liệu.

Nếu bạn muốn ứng dụng của mình khởi tạo phát hiện thiết bị hoặc thao tác với các cài đặt Bluetooth, bạn cũng phải khai báo giấy phép Bluetooh_admin. Phần lớn các ứng dụng cần giấy phép này để có thể phát hiện các thiết bị Bluetooth cục bộ. Các tính năng khác được cấp bởi giấy phép này không thể được sử dụng. ít nhất ứng dụng là một quản lý năng lượng sẽ điều chỉnh cài đặt Bluetooth trên yêu cầu của người dùng.

Chú ý: Nếu bạn sử dụng giấy phép BLUETOOTH_ADMIN, bạn cũng phải khai báo giấy phép BLUETOOTH.

Khai báo giấy phép Bluetooth trong file Manifest của ứng dụng. Ví dụ:

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

Setting Up Bluetooth


Trước khi ứng dụng của bạn có thể giao tiếp qua Bluetooth, bạn cần kiểm tra rằng Bluetooth được hỗ trợ trên thiết bị và nếu vậy, đảm bảo rằng nó đã được kích hoạt.

Nếu Bluetooth không được hỗ trợ, bạn nên disable các tính năng Bluetooth. Nếu Bluetooth được hỗ trợ nhưng disable, bạn có thể yêu cầu người dùng kích hoạt Bluetooth mà không cần rời khỏi ứng dụng. Điều này cài đặt thiết lập trong 2 bước, sử dụng BluetoothAdapter.

1. Nhận BluetoothAdapter: BluetoothAdapter được yêu cầu cho bất kỳ và tất cả hoạt động Bluetooth. Để nhận BluetoothAdater, gọi phương thức tĩnh getDefaultAdapter(). Nó sẽ trả về một Bluetooth Adapter đại diện cho Bluetooth Adapter riêng của thiết bị. Có một bluetooth adapter cho toàn bộ hệ thống và ứng dụng của bạn có thể tác động tới nó sử dụng đối tượng này. Nếu getDefaultAdapter() trả về null, nghĩa là thiết bị không hỗ trợ Bluetooth và your story ends here. Ví dụ:

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    // Device does not support Bluetooth
}

2. Kích hoạt Bluetooth: tiếp theo, bạn cần đảm bảm rằng Bluetooth được cho phép. Gọi isEnable() để kiểm tra liệu Bluetooth có đang được enable. Nếu phương thức này trả về false, nghĩa là Bluetooth đã disable. Để yêu cầu Bluetooth thành enable, gọi startActivityForResult() với ACTION_REQUEST_ ENABLE action intent. Điều này sẽ cấp phát một yêu cầu để kích hoạt Bluetooth thông qua hệ thống settings. Ví dụ:

if (!mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

Một dialog sẽ xuất hiện yêu cầu người dùng cấp phép để kích hoạt Bluetooth, như chỉ trong hình trên. Nếu người dùng phản hồi "Yes" hệ thống sẽ bắt đầu kích hoạt Bluetooth và focus sẽ quay lại ứng dụng khi tiến trình hoàn tất (hoặc thất bại).

Hằng số REQUEST_ENABLE_BT truyền vào startActivityForResult() là một hằng số toàn cục dạng số nguyên (phải lớn hơn 0), mà hệ thống sẽ truyền lại cho bạn trong phương thức onActivityResult() như một biến requestCode.

Nếu kích hoạt bluetooth thành công, activity của bạn nhận result code là RESULT_OK trong onActivityResult(). Nếu Bluetooth không được kích hoạt do lỗi hoặc người dùng phản hồi "No" thì result code là RESULT_CANCELED.

Theo tùy chọn, ứng dụng của bạn cũng có thể lắng nghe broadcast Intent ACTION_STATE_ CHANGED, hệ thống sẽ broadcast mỗi khi trạng thái Bluetooth thay đổi. Broadcast này chứa các trường mở rộng EXTRA_STATE và EXTRA_PREVIOUS_STATE, chứa trạng thái mới và cũ của Bluetooth. Giá trị có thể có cho các trường mở rộng là STATE_TURNING_ON, STATE_ON, STATE _TURNING_OFF và STATE_OFF. Lắng nghe broadcast này có thể hữu ích để phát hiện thay đổi trạng thái của Bluetooth trong khi ứng dụng của bạn đang chạy.

Finding Devices

Sử dụng BluetoothAdapter, bạn có thể tìm thấy các thiết bị điều khiển Bluetooth thông qua thiết bị phát hiện hoặc bằng cách truy vấn danh sách các thiết bị ghép cặp.

Thiết bị phát hiện là một thủ tục quét, tìm kiếm các Bluetooth có trong khu vực được kích hoạt các thiết bị và sau đó yêu cầu một số thông tin về mỗi thiết bị. Tuy nhiên, một thiết bị Bluetooth trong phạm vi quét sẽ trả về một yêu cầu phát hiện chỉ khi nó đang được để ở có thể phát hiện. Nếu một thiết bị có thể phát hiện, nó sẽ phản hồi yêu cầu phát hiện bằng cách chia sẻ một số thông tin như: tên, class, địa chỉ MAC. Sử dụng thông tin này, thiết bị thực hiện tìm kiếm có thể chọn để khởi tạo một kết nối với thiết bị được phát hiện.

Một khi một kết nối được tạo với một thiết bị điều khiển lần đầu tiên, một yêu cầu ghép cặp được tự động gửi tới người dùng. Khi một thiết bị được ghép cặp, thông tin cơ bản về thiết bị đó được lưu và có thể được đọc lại sử dụng Bluetooth API. Sử dụng địa chỉ MAC đã biết, một kết nối có thể được khởi tạo với nó ở bất cứ thời gian nào mà không cần thực hiện tìm kiếm (giả sử thiết bị trong phạm vi kết nối)

Nhớ rằng có một sự khác nhau giữa được ghép cặp và được kết nối. Ghép cặp nghĩa là 2 thiết bị được nhận biết sự tồn tại của nhau, có một chia sẻ từ khóa có thể được sử dụng để xác nhận và được thiết lập một mật mã kết nối với thiết bị khác. Kết nối nghĩa là các thiết bị đang chia sẻ một kênh RFCOMM và có thể truyền dữ liệu với thiết bị khác. Android Bluetooth API yêu cầu thiết bị được ghép cặp trước khi một kết nối RFCOMM có thể được thiết lập.

Các mục dưới đây miêu rả cách tìm kiếm các thiết bị đã được ghép cặp hoặc phát hiện các thiết bị mới sử dụng device discovery.

Querying paired devices

Trước khi thực hiện phát hiện thiết bị, có thể truy vấn tới tập các thiết bị đã ghép cặp để thấy neus thiết bị mong muốn đã biết. Để làm điều này, gọi getCondedDevices(). Hàm này sẽ trả về một tập các BluetoothDevices đã từng được ghép cặp. Ví dụ, bạn có thể truy vấn tất cả các thiết bị ghép cặp và hiển thị tên của mỗi thiết bị cho người dùng, sử dụng ArrayAdapter.

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
    // Loop through paired devices
    for (BluetoothDevice device : pairedDevices) {
        // Add the name and address to an array adapter to show in a ListView
        mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    }
}

Tất cả những gì cần từ đối tượng BlutoothDevice trong yêu cầu tạo một kết nối là địa chỉ MAC. Trong ví dụ này, nó được lưu như một phần của ArrayAdapter được hiển thị cho người dùng. Địa chỉ MAC có thể lấy khi muốn tạo một kết nối.

Discovering Devices

Để bắt đầu tìm kiếm các thiết bị, đơn giản gọi startDiscovery(). Tiến trình bất đồng bộ và phương thức sẽ ngay lập tức trả về một giá trị logic mỗi khi phát hiện thành công. Tiến trình phát hiện thường tốn 12s quét. Theo sau bởi một trang quét mỗi thiết bị tìm thấy để nhận tên của Bluetooth đó.

Ứng dụng của bạn phải đăng ký một BroadcastReceiver từ ACTION_FOUND Intent để nhận thông tin từ mỗi thiết bị được tìm thấy. Với mỗi thiết bị, hệ thống sẽ broadcast ACTION_FOUND intent. Intent này mang các trường mở rộng EXTRA_DEVICE và EXTRA_CLASS, chưa một Bluetooth Device và một BluetoothClass. Ví dụ, đây là cách bạn đăng ký để xử lý broadcast khi thiết bị được phát hiện.

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        // When discovery finds a device
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            // Get the BluetoothDevice object from the Intent
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            // Add the name and address to an array adapter to show in a ListView
            mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
        }
    }
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy

Tất cả những gì cần từ đối tượng BluetoothDevice trong yêu cầu khởi tạo một kết nối là địa chỉ MAC. Trong ví dụ này nó được lưu như một phần của ArrayAdapter để hiện thị cho người dùng. Địa chỉ MAC có thể lấy khi muốn khởi tạo kết nối.

Caution: Thực hiện phát hiện thiết bị là một thủ tục nặng cho Bluetooth adapter và sẽ tiêu thụ rất nhiều nguồn của nó. Một khi bạn đã tìm thấy một thiết bị để kết nối, hãy chắc chắn rằng bạn luôn dừng phát hiện với cancelDiscovery() trước khi tập trung vào kết nối. Cũng vậy, nếu bạn đã giữ một kết nối với một thiết bị, sau đó thực hiện phát hiện có thể làm giảm đáng kể băng thông có sẵn cho kết nối, bạn không nên thực hiện phát hiện trong khi đang kết nối.

Enabling Discoverability

Nếu bạn thích tạo một thiết bị cục bộ có thể được phát hiện bởi các thiết bị khác, gọi startActivityFor Result(Intent, int) với ACTION_REQUEST_DISCOVERABLE action Intent. Điều này sẽ phát ra một yêu cầu để kích hoạt chế độ khả năng được phát hiện thông qua hệ thông cài đặt (mà không cần dừng ứng dụng của bạn). Mặc định, thiết bị sẽ trở thành có thể phát hiện trong 120s. Bạn có thể định nghĩa một giá trị khác bằng cách thêm Intent EXTRA_DISCOVERABLE_DURATION. Giá trị max một ứng dụng có thể cài đặt là 3600s, và giá trị min là 0 nghĩa là thiết bị luôn luôn có thể được phát hiện. Bất kỳ giá trị nào dưới 0 hoặc trên 3600 thì đều tự động được cài đặt về 120s. Ví dụ, đoạn trích dưới đây cài đặt thời hạn là 300s.

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);


Một dialog sẽ được hiển thị, yêu cầu người dùng cho phép thiết bị có thể được phát hiện. Nếu người dùng phản hồi "Yes" thì thiết bị sẽ trở thành có thể được phát hiện trong một khoảng thời gian nhất định. Hoạt động của bạn sau đó nhận một cuộc gọi lại tới onActivityResult(), với mã kết quả bằng với thời hạn thiết bị có thể được phát hiện. Nếu người dùng phản hồi là "No" hoặc nếu một lỗi xảy ra, mã kết quả là RESULT_CANCELED.

Chú ý: Nếu Bluetooth vẫn chưa được kích hoạt trên thiết bị thì việc kích hoạt phát hiện sẽ tự động kích hoạt Bluetooth.

Thiết bị sẽ lặng lẽ trở lại chế độ có thể phát hiện trong thời gian quy định. Nếu bạn muốn được thông báo khi chế độ có thể phát hiện thay đổi trạng thái, bạn có thể đăng ký một BroadcastReceiver với Intent ACTION_SCAN_MODE_CHANGED. Điều này sẽ chứa các trường mở rộng EXTRA_SCAN _MODE và EXTRA_PREVIOUS_SCAN_MODE, nó sẽ nói bạn biết chế độ quét cũ và mới. Các giá trị có thể có cho mỗi cái là SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE hoặc SCAN_MODE_NONE, nó sẽ cho biết rằng thiết bị trong chế độ có thể phát hiện, không trong chế độ có thể phát hiện nhưng vẫn có khả năng nhận kết nối hoặc không trong chế độ có thể phát hiện và không thể nhận kết nối.

Bạn không cần kích hoạt phát hiện thiết bị nếu bạn sẽ khởi tạo kết nối tới một thiết bị điều khiển. Kích hoạt khả năng phát hiện chỉ cần thiết khi bạn muốn ứng dụng của bạn làm chủ một server socket, sẽ chấp nhận một kết nối đang đến, bởi vì các thiết bị điều khiển phải có khả năng phát hiện thiết bị trước khi nó có thể khởi tạo kết nối.

Connecting Devices

Để tạo một kết nối giữa ứng dụng của bạn trên hai thiết bị, bạn phải thực hiện cả 2 cơ chế server và client Bởi vì một thiết bị phải mở một server socket và thiết bị kia phải khởi tạo kết nối (sử dụng địa chỉ MAC của server để khởi tạo kết nối). Server và client được xem xét kết nối tới nhau khi chúng có một kết nối BluetoothSocket trên cùng kênh RFCOMM. Ở điểm này, mỗi thiết bị có thể có được đầu vào và luồng đầu ra và dữ liệu truyền có thể bắt đầu, như dã thảo luận trong mục về quản lý một kết nối. Mục này sẽ miêu tả cách khởi tạo một kết nối giữa hai thiết bị.

Thiết bị server và client có thể đạt được yêu cầu BluetoothSocket trong các cách khác nhau. Server sẽ nhận nó khi một kết nối đến là được chấp nhận. Client sẽ nhện nó khi nó mở một kênh RFCOMM tới server.


Một kỹ thuật thực hiện là để tự động chuẩn bị mỗi thiết bị như một server, nên thiết bị kia sẽ có một server socket mở và lắng nghe kết nối. Sau đó thiết bị kia sẽ khởi tạo một kết nối với thiết bị khác và trở thành client. Ngay lập tức, một thiết bị có thể làm chủ một cách rõ ràng kết nối và mở một server socket theo yêu cầu và thiết bị khác có thể khởi tạo kết nối đơn giản.

Chú ý: Nếu hai thiết bị chưa từng ghép cặp, Android framework sẽ tự động hiển thị một thông báo yêu cầu ghép cặp hoặc một dialog tới người dùng trong khi vẫn đang kết nối. Khi tập trung tới kết nối các thiết bị, ứng dụng của bạn không cần quan tâm về liệu hoặc không thiết bị nào ghép cặp. Kênh RFCOMM của bạn sẽ đóng cho đến khi người dùng đã ghép cặp thành công hoặc sẽ lỗi nếu người dùng bỏ qua ghép cặp hoặc nếu ghép cặp thất bại hoặc hết thời gian.

Connecting as a server

Khi bạn muốn kết nối hai thiết bị, một thiết bị phải trở thành server bằng cách giữ một BluetoothServerSocket mở. Mục đích của server socket là để lắng nghe yêu cầu kết nối đến và khi một thiết bị chấp nhận, cung cấp một kết nối BluetoothSocket. Khi BluetoothSocket có được từ BluetoothServerSocket, BluetoothServerSocket có thể được loại bỏ, trừ khi bạn muốn chấp nhận nhiều kết nối hơn.

Dưới đây là các bước cơ bản để cài đặt như một server socket và chấp nhận một kết nối:

1. Nhận một BluetoothServerSocket bằng cách gọi listenUsingRfcommWithServiceRecord(String, UUID).
String là một nhận diện tên của dịch vụ, hệ thống sẽ tự động viết vào một cơ sở dữ liệu Service Discovery Protocol (SDP) mới vào trong thiết bị. UUID cũng được lưu trong SDP và sẽ trở thành thành phần cơ bản cho chấp nhận kết nối với thiết bị client. Điều đó là khi client cố gắng kết nối với thiết bị này, nó sẽ mang theo một UUID là nhận diện riêng của dịch vụ với cái mà nó muốn kết nối. Các UUID này phải được trùng khớp để cho kết nối được chấp nhận.

2. Bắt đầu lắng nghe yêu cầu kết nối bằng cách gọi accept().
Đây là một khối gọi. Nó sẽ trả về cả khi một kết nối được chấp nhận hoặc một ngoại lệ xảy ra. Một kết nối được chấp nhận chỉ khi một thiết bị điều khiển gửi một yêu cầu kết nối với một UUID trùng khớp một đăng ký với cái đang lắng nghe server socket. Khi thành công, accept() sẽ trả về một kết nối BluetoothSocket.

3. Trừ khi bạn muốn chấp nhận thêm kết nối, nếu không gọi close().
Hàm này sẽ giải phóng server socket và tất cả nguồn tài nguyên, nhưng không đóng kết nối BluetoothSocket mà đã được trả về bởi accept(). Không giống TCP/IP, RFCOMM chỉ cho phép một kết nối client trên một kênh ở một thời điểm, nên trong phần lớn các trường hợp nó có ý nghĩa để gọi close() trên BluetoothServerSocket ngay sau khi chấp nhận một kết nối socket.

Hàm accept() không nên được gọi trong main activity bởi vì nó là một khối gọi và sẽ phòng ngừa bất kỳ tương tác với ứng dụng. Nó thường để làm tất cả mọi việc với một BluetoothServerSocket hoặc BluetoothSocket trong một luồng mới quản lý bởi ứng dụng của bạn. Để bỏ qua một khối gọi như accept(), gọi close() trong BluetoothServerSocket (hoặc BluetoothSocket) từ một luồng khác và khối gọi sẽ lập tức trả về. Chú ý rằng tất cả phương thức trong một BluetoothServerSocket hoặc Bluetooth Socket được thread-safe.

Ví dụ: Đây là một luồng đơn giản hóa cho thành phần server để chấp nhận các kết nối đến:

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;
 
    public AcceptThread() {
        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also used by the client code
            tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }
 
    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned
        while (true) {
            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // If a connection was accepted
            if (socket != null) {
                // Do work to manage the connection (in a separate thread)
                manageConnectedSocket(socket);
                mmServerSocket.close();
                break;
            }
        }
    }
 
    /** Will cancel the listening socket, and cause the thread to finish */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) { }
    }
}

Trong ví dụ này, chỉ một kết nối đến được chờ, nên ngay khi một kết nối được chấp nhận và BluetoothSocket có được, ứng dụng gửi BluetoothSocket đó đến một luồng tách, đóng Bluetooth ServerSocket và thoát vòng lặp.

Chú ý rằng khi accept() trả về BluetoothSocket, socket đã được kết nối, nên bạn không nên gọi connect() (như sẽ làm ở client).

manageConnectedSocket() là một phương thức giả trong ứng dụng, sẽ khởi tạo luồng cho truyền dữ liệu, được thảo luận trong mục về Managing a Connection.

Bạn nên thường xuyên đóng BluetoothServerSocket ngay khi hoàn tất lắng nghe yêu cầu kết nối đến. Trong ví dụ này, close() được gọi ngay khi có được BluetoothSocket. Bạn cũng có thể muốn cung cấp một phương thức public trong luồng của bạn, có thể đóng BluetoothSocket private trong sự kiện mà bạn cần dừng lắng nghe trong server socket.

Connecting as a Client

Để khởi tạo một kết nối với một thiết bị điều khiển (một thiết bị đang mở server socket), bạn phải có được một đối tượng BluetoothDevice đầu tiên, đại diện cho thiết bị điều khiển (Nhận một BluetoothDevice được thực hiện từ mục Finding Devices). Bạn phải sử dụng BluetoothDevice này để có được một BluetoothSocket và khởi tạo kết nối.

Dưới đây là các bước cơ bản:

1. Sử dụng BluetoothDevice, nhận một BluetoothSocket bằng cách gọi createRfcommSockettoServic eRecord(UUID).
Việc này khởi tạo UUID tương ứng được sử dụng bởi thiết bị server khi nó mở BluetoothServerSoc ket (với listenUsingRfcommWithServiceRecord(String, UUID)). Sử dụng cùng tên UUID làm đơn giản vấn đề mã hóa cứng chuỗi UUID vào ứng dụng của bạn và sau đó sử dụng nó từ cả server và client.

2. Khởi tạo kết nối bằng cách gọi connect().
Sau khi phương thức này gọi, hệ thống sẽ thực hiện SDP dò tìm trên thiết bị điều khiển để trùng khớp UUID. Nếu dò tìm thành công và thiết bị điều khiển chấp nhận kết nối, nó sẽ chia sẻ kênh RFCOMM để sử dụng trong suốt quá trình kết nối và connect() sẽ trả về. Phương thức này là một khối gọi, nếu vì bất kỳ lý do nào, kết nối thất bại hoặc hết thời gian phương thức connect() (sau khoảng 12s), nó sẽ đưa ra một ngoại lệ.

Bởi vì connect() là một khối gọi, thủ tục kết nối này nên luôn luôn được thực hiện trong một luồng riêng biệt với luồng main activity.

Chú ý: Bạn nên luôn đảm bảo rằng thiết bị không thực hiện phát hiện thiết bị khi gọi connect(). Nếu phát hiện trong tiến trình, nỗ lực kết nối sẽ bị làm chậm đáng kể và nhiều thứ có thể thất bại.

Ví dụ: Đây là một ví dụ cơ bản của một luồn khởi tạo một kết nối Bluetooth.

private class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
 
    public ConnectThread(BluetoothDevice device) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
 
        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
        } catch (IOException e) { }
        mmSocket = tmp;
    }
 
    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();
 
        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }
 
        // Do work to manage the connection (in a separate thread)
        manageConnectedSocket(mmSocket);
    }
 
    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}
Chú ý rằng cancelDiscovery() được gọi trước khi kết nối tạo thành. Bạn nên luôn luôn làm điều này trước khi kết nối và nó sẽ an toàn để gọi mà không cần thực sự kiểm tra lại mỗi khi nó chạy hoặc không (nhưng kết bạn muốn kiểm tra, gọi isDiscovering()).

manageConnectedSocket() là một phương thức giả trong ứng dụng sẽ khởi tạo luồng cho truyền dữ liệu, nó sẽ được thảo luận trong mục về Manage a Connction.

Khi bạn kết thúc với BluetoothSocket, luôn gọi close() để ngay lập tức đóng mọi kết nối. Làm vậy sẽ đóng ngay lập tức kết nối socketed và xóa toàn bộ nguồn tài nguyên.

Managing a Connection

Khi bạn đã kết nối thành công hai hay nhiều thiết bị, mỗi cái sẽ có một BluetoothSocket đã kết nối. Cái này là nơi niềm vui bắt đầu bởi vì bạn có thể chia sẻ dữ liệu giữa các thiết bị. Sử dụng BluetoothSocket, thủ tục chung cho truyền dữ liệu khá đơn giản:

1. Nhận InputStream và OutputStream để xử lý truyền qua socket, thông qua getInputStream() và getOutputStream() tương ứng.

2. Đọc và viết dữ liệu tới các stream với read(byte[]) và write(byte[]).

Đầu tiên và trước nhất, bạn nên sử dụng một luồng chuyên biệt cho tất cả stream đọc và viết. Đây là điều quan trọng vì cả read(byte[]) sẽ block cho đến khi có thứ gì đó để đọc từ stream. write(byte[]) không thường block, nhưng có thể block để điều khiển luồng nếu thiết bị điều khiển không gọi read(byte[]) đủ nhưng và bộ nhớ buffer bị đầy. Nên vòng lặp chính của bạn trong luồng nên được dành riêng để đọc từ InputStream. Một phương thức riêng rẽ trong luồng có thể được sử dụng để khởi tạo viết vào OutputStream.

Ví dụ: Đây là một ví dụ cách làm trên:

private class ConnectedThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
 
    public ConnectedThread(BluetoothSocket socket) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
 
        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }
 
        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }
 
    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()
 
        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                        .sendToTarget();
            } catch (IOException e) {
                break;
            }
        }
    }
 
    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }
 
    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

Constructor có được các stream cần thiết và được thực hiện một lần, luồng sẽ đợi dữ liệu đến qua InputStream. Khi read(byte[]) trả về với các byte từ stream, dữ liệu được gửi tới main activity sử dụng một member xử lý từ class cha. Sau đó nó quay trở lại và chờ các byte tiếp theo từ stream.

Gửi dữ liệu ra ngoài đơn giản bằng cách gọi phương thức write() từ main activity và các byte sẽ được gửi đi. Phương thức này sau đó đơn giản gọi write(byte[]) để gửi dữ liệu tới thiết bị điều khiển.

Luồng của phương thức calcel() là quan trọng nên kết nối có thể được kết thúc ở bất cứ thời điểm nào bằng cách đóng BluetoothSocket. Phương thức này nên luôn được gọi khi bạn kết thúc sử dụng  kết nối Bluetooth.

END. THANKS FOR READ!!!