Author: phongpn3

  • Android Bluetooth Classic

    Android Bluetooth Classic

    Tiếp tục chuỗi bài liên quan đến việc tìm hiểu kết nói giữa ứng dụng và thiết bị thông qua Bluetooth. Các bạn có thể tìm hiểu về BLE mình đã chia sẻ tại đây, giống như BLE, hôm nay mình cũng xin phép overview qua và chia sẻ kiến thức mình biết được liên quan đến Bluetooth Classic.

    Chuỗi bài chia sẻ này mình xin chia sẻ một số phần chính

    • Setup Bluetooth Classic trong Android
    • Lấy danh sách các thiết bị ghép nối
    • Tìm kiếm (Scanning) thiết bị
    • Connect và Transfer giữa Ứng dụng và Thiết bị thông qua Bluetooth Classic
    1. Setup Bluetooth Classic trong Android

    Để sử dụng các tính năng Bluetooth trong ứng dụng của mình, bạn phải khai báo vs xin một số quyền để được sử dụng. Tại file AndroidManifest chúng ta yêu cầu các quyền.

    <manifest>
        <!-- Request legacy Bluetooth permissions on older devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH"
                         android:maxSdkVersion="30" />
        <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                         android:maxSdkVersion="30" />
    
        <!-- Needed only if your app looks for Bluetooth devices.
             If your app doesn't use Bluetooth scan results to derive physical
             location information, you can strongly assert that your app
             doesn't derive physical location. -->
        <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    
        <!-- Needed only if your app makes the device discoverable to Bluetooth
             devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    
        <!-- Needed only if your app communicates with already-paired Bluetooth
             devices. -->
        <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    
        <!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        ...
    </manifest>
    

    Như các bạn thấy ở trên:

    • BLUETOOTH permission cho phép ứng dụng kết nối, ngắt kết nối, và truyền dữ liệu với các thiết bị Bluetooth khác.
    • BLUETOOTH_ADMIN permission cho phép ứng dụng phát hiện ra các thiết bị Bluetooth mới và thay đổi cài đặt Bluetooth của thiết bị.
    • BLUETOOTH_SCAN permission phục vụ cho việc Scan thiết bị cho cả Bluetooth Classic và BLE
    • BLUETOOTH_CONNECT cho phép ứng dụng communicates với thiết bị đã được Pair sẵn trong System trước đó
    • BLUETOOTH_ADVERTISE cho phép các thiết bị hiện tại có thể discoverable với các thiết bị khác

    Đối với Android 12 or higher, các bạn chú ý them:

    • Đối với các khai báo quyền liên quan đến Bluetooth cũ, hãy đặt android:maxSdkVersion thành 30. Bước compatibility ứng dụng này giúp hệ thống chỉ cấp cho ứng dụng của bạn các quyền Bluetooth mà ứng dụng đó cần khi cài đặt trên các thiết bị chạy Android 12 trở lên.
    • ACCESS_FINE_LOCATION quyền này chỉ cần thiết nếu ứng dụng của bạn sử dụng kết quả quét Bluetooth để xác định vị trí thực tế. Về quyền này, đặc biệt là các quyền liên quan đến Location, mình sẽ trao đổi rõ hơn ở bài viết tiếp theo liên quan đến Companion Device Manager.

    Note: Trong các quyền trên thì BLUETOOTH_SCAN, BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE là các runtime permissions. Các bạn chú ý việc xử lý code về việc nhận approve từ người dùng.

    1. Lấy danh sách các thiết bị ghép nối

    Cụ thể mình lấy các thiết bị Bluetooth đã được ghép nối và hiển thị chúng trong một danh sách. Tại các trạng thái mà thiết bị Bluetooth hiển thị trong ứng dụng của mình sẽ là:

    • unknown
    • paired (đã ghép nối).
    • connected (đang kết nối).

    Có một lưu ý là chúng ta cần phải phân biệt giữa paired và connected của một thiết bị Bluetooth. Thiết bị đã paired là chỉ biết về sự tồn tại của nhau và sẵn sàng kết nối thông qua một mã code. Mã code được sử dụng để xác thực và dẫn đến một kết nối. Đầu tiên chúng ta gọi lớp BluetoothAdapter để giao tiếp với Bluetooth. Tạo một object của cuộc gọi bằng cách gọi statics method getDefaultAdapter().

    fun getBluetoothAdapter(context: Context?): BluetoothAdapter? {
        val manager =
            if (context == null) null else context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        return if (manager == null) {
            BluetoothAdapter.getDefaultAdapter()
        } else {
            manager.adapter
        }
    }
    

    Sau khi chúng ta get danh sách Devices đã từng Paired.

    Set<BluetoothDevice> pairedDevices = getBluetoothAdapter(context).getBondedDevices();
    

    Nếu trong trường hợp ứng dụng chưa bật Bluetooth, để cho phép bật Bluetooth của thiết bị, chúng ta gọi intent với hằng số Bluetooth ACTION_REQUEST_ENABLE.

    Intent turnOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(turnOn, 0);
    

    Ngoài ra Android còn cung cấp các hằng số khác, các bạn có thể xem phía dưới đây:

    • ACTION_REQUEST_DISCOVERABLE: Hằng số này sử dụng cho việc bật discovering của bluetooth
    • ACTION_STATE_CHANGED: Hằng số này sẽ thông báo rằng trạng thái Bluetooth sẽ được thay đổi
    • ACTION_FOUND: Hằng số này dùng để nhận thông tin về mỗi device mà được discover

    Bài tiếp theo mình sẽ trình bày về việc tìm kiếm Bluetooth Devices, cũng như Connect và Transfer giữa ứng dụng và thiết bị.

  • Annotation in Android Hilt

    Annotation in Android Hilt

    Hôm nay mình xin chia sẻ một chút kiến thức của mình liên quan đến Dependency Injection.

    Đầu tiên mình xin phép nói sơ qua về khái niệm của Dependency Injection. Vậy Dependency Injection là gì ? Cụ thể dựa vào DI trong Principle, Dependency Injection cơ bản là cung cấp đối tượng phụ thuộc như 1 tham số. Ứng dụng phụ thuộc này khi các class đã được xây dựng hoặc chuyển chúng (các đối tượng phụ thuộc) vào các function cần mỗi phụ thuộc. Cách triển khai chúng theo lý thuyết là lấy đấy tượng phụ thuộc và cung cấp chúng vào class thay vì tạo ra thể hiện của chúng trực tiếp trong class bị phụ thuộc.

    Ưu điểm:

    • Khả năng tái sử dụng lại các class và tách rời các phụ thuộc (dependencies): Dễ dàng hơn trong việc thay đổi một dependency. Việc tái sử dụng code được cải thiện do Inversion of Control, và các class không còn kiểm soát việc tạo ra các dependencies như thế nào, thay vào đó nó có thể làm việc với bất kỳ cấu hình nào.
    • Dễ tái cấu trúc: Các dependencies trở thành những phần có thể kiểm tra như API. Hoàn toàn có thể kiểm tra lúc tạo đối tượng, lúc biên dịch chứ không bị ẩn đi.
    • Dễ dàng cho việc Testing: Các class không quản lý các dependencies của nó. Vì thế khi testing chúng ta có thể truyền các dependency khác nhau và xử lý được nhiều test case

    Tiếp đến mình sẽ nói phần chính của bài này liên quan đến Annotation in Android Hilt (Một framework trong Android support việc triển khai Dependency Injection trong Project).

    Chúng ta có 3 annotation thường dùng để inject các object trong Hilt:

    • @Inject: annotation dùng ở constructor của class
    • @Provides: annotation dùng ở Module
    • @Binds: một annotation khác cũng dùng ở Module

    Vậy khi nào dùng chúng, đặc biệt là giữa @Provide và @Binds?

    1. Inject

    Chúng ta dùng @Inject annotation ở tất cả các constructor mà mình cần inject đối tượng (Object). Ví dụ như ViewModel, Repository, UseCase, DataSource, thậm trí là Android classes (ex. Activity, Fragment, …)

    class ConnectRepositoryImpl @Inject constructor(
        private val connectManager: ConnectManager
    ) : ConnectRepository {
    
       fun doSomething() {}
    }
    
        class ConnectUseCaseImpl @Inject constructor(
            private val connectRepository: ConnectRepository,
        ) : ConnectUseCase {
    
        fun doSomething() {
            // Using connectRepository 
        }
     }
    

    Qua ví dụ trên sau khi khởi tạo class ConnectRepositoryImpl với @Inject constructor. Chúng ta có thể dễ dàng sử dụng ConnectRepository ở các class khác (như ViewModel, Android class, UseCase) bằng cách inject chúng vào nơi cần chúng. Tuy nhiên thì chúng ta lại chỉ có thể sử dụng annotation này để annotate constructor của những class mà mình tự define.

    1. Provide

    Vậy thì để khắc phục hạn chế chỉ có thể sử dụng annotation này để annotate constructor của những class mà mình tự define của @Inject, inject object của những class mà mình không define (Ví dụ như Retrofit, OkHttpClient hoặc Room database), chúng ta cùng đến với @Provides. Trước tiên, chúng ta cần tạo một @Module để chứa các dependency với annotation @Provides.

        @InstallIn(SingletonComponent::class)
        @Module
        class DatabaseModule {
    
            @Provides
            @Singleton
            fun provideGSDatabase(@ApplicationContext context: Context): GSDatabase {
                return Room.databaseBuilder(context, GSDatabase::class.java, GS_DATABASE_NAME)
                    .fallbackToDestructiveMigration()
                    .build()
            }
    
            @Provides
            fun provideSpeakerDao(gsDatabase: GSDatabase): SpeakerDao {
                return gsDatabase.speakerDao()
            }
        }
    

    Như đoạn code ở trên, mình khởi tạo đối tượng RomDatabase và một đối tượng Dao của chúng,  đó là khởi tạo object không phải code của chúng ta define, hơn nữa trong trường hợp này còn khởi tạo theo kiểu Builder pattern, nên chúng ta không thể dùng @Inject annotation mà bắt buộc phải dùng @Provides. Bây giờ, chúng ta đã có thể inject object của interface GSDatabase và SpeakerDao ở bất cứ đâu.

    1. Bind

    Đối với interface, chúng ta không thể dùng annotation @Inject, vì nó không có constructor function. Tuy nhiên, nếu bạn có một interface mà chỉ có duy nhất một implementation (một class implement interface đó), thì bạn có thể dùng @Binds để inject interface đó. Việc inject interface thay vì class là trong những cách triển khai được Recommend sử dụng bởi vì nó tuân theo nguyên tắc Dependence Inversion của SOLID, từ đó dễ dàng cho việc testing và maintain mai này.

    interface ConnectRepository {
        fun doSomething()
    }
    
    class ConnectRepositoryImpl @Inject constructor(
        private val connectManager: ConnectManager
    ) : ConnectRepository {
    
       fun doSomething() {}
    }
    
    @Module
    @InstallIn(SingletonComponent::class)
    abstract class ConnectDeviceModule {
        @Binds
        @Singleton
        abstract fun provideConnectRepository(
            connectRepository: ConnectRepositoryImpl
        ): ConnectRepository
    }
    
    class ConnectUseCaseImpl @Inject constructor(
        private val connectRepository: ConnectRepository,
    ) : ConnectUseCase {
    
      fun doSomething() {
          // Using connectRepository
      }
    }
    

    Cụ thể trong ví dụ trên, mình khai báo 1 interface ConnectRepository thể hiện cho đối tượng ConnectRepositoryImpl. Từ đó khi sử dụng ConnectRepositoryImpl chúng ta sẽ sử dụng và gọi nó thông qua ConnectRepository.

    Ưu điểm của việc dùng @Binds thay cho @Provides là nó giúp giảm lượng code được generate, như là Module Factory class. Từ đó tăng thời gian build và tăng kích thước file .apk và .aab của Project.

    Trên đây là chia sẻ của mình về 1 số Annotation của Hilt Android. Mong sẽ giúp ích được ít nhiều mọi người. Hẹn mọi người ở bài viết sắp tới

  • Annotation in Android Hilt

    Annotation in Android Hilt

    Hôm nay mình xin chia sẻ một chút kiến thức của mình liên quan đến Dependency Injection.

    Đầu tiên mình xin phép nói sơ qua về khái niệm của Dependency Injection. Vậy Dependency Injection là gì ? Cụ thể dựa vào DI trong Principle, Dependency Injection cơ bản là cung cấp đối tượng phụ thuộc như 1 tham số. Ứng dụng phụ thuộc này khi các class đã được xây dựng hoặc chuyển chúng (các đối tượng phụ thuộc) vào các function cần mỗi phụ thuộc. Cách triển khai chúng theo lý thuyết là lấy đấy tượng phụ thuộc và cung cấp chúng vào class thay vì tạo ra thể hiện của chúng trực tiếp trong class bị phụ thuộc.

    Ưu điểm:

    • Khả năng tái sử dụng lại các class và tách rời các phụ thuộc (dependencies): Dễ dàng hơn trong việc thay đổi một dependency. Việc tái sử dụng code được cải thiện do Inversion of Control, và các class không còn kiểm soát việc tạo ra các dependencies như thế nào, thay vào đó nó có thể làm việc với bất kỳ cấu hình nào.
    • Dễ tái cấu trúc: Các dependencies trở thành những phần có thể kiểm tra như API. Hoàn toàn có thể kiểm tra lúc tạo đối tượng, lúc biên dịch chứ không bị ẩn đi.
    • Dễ dàng cho việc Testing: Các class không quản lý các dependencies của nó. Vì thế khi testing chúng ta có thể truyền các dependency khác nhau và xử lý được nhiều test case

    Tiếp đến mình sẽ nói phần chính của bài này liên quan đến Annotation in Android Hilt (Một framework trong Android support việc triển khai Dependency Injection trong Project).

    Chúng ta có 3 annotation thường dùng để inject các object trong Hilt:

    • @Inject: annotation dùng ở constructor của class
    • @Provides: annotation dùng ở Module
    • @Binds: một annotation khác cũng dùng ở Module

    Vậy khi nào dùng chúng, đặc biệt là giữa @Provide và @Binds?

    1. Inject

    Chúng ta dùng @Inject annotation ở tất cả các constructor mà mình cần inject đối tượng (Object). Ví dụ như ViewModel, Repository, UseCase, DataSource, thậm trí là Android classes (ex. Activity, Fragment, …)

    <pre> class ConnectRepositoryImpl @Inject constructor( private val connectManager: ConnectManager ) : ConnectRepository {

    fun doSomething() {} } </pre>

    <pre> class ConnectUseCaseImpl @Inject constructor( private val connectRepository: ConnectRepository, ) : ConnectUseCase {

    fun doSomething() { // Using connectRepository } } </pre>

    Qua ví dụ trên sau khi khởi tạo class ConnectRepositoryImpl với @Inject constructor. Chúng ta có thể dễ dàng sử dụng ConnectRepository ở các class khác (như ViewModel, Android class, UseCase) bằng cách inject chúng vào nơi cần chúng. Tuy nhiên thì chúng ta lại chỉ có thể sử dụng annotation này để annotate constructor của những class mà mình tự define.

    1. Provide

    Vậy thì để khắc phục hạn chế chỉ có thể sử dụng annotation này để annotate constructor của những class mà mình tự define của @Inject, inject object của những class mà mình không define (Ví dụ như Retrofit, OkHttpClient hoặc Room database), chúng ta cùng đến với @Provides. Trước tiên, chúng ta cần tạo một @Module để chứa các dependency với annotation @Provides.

    <pre> @InstallIn(SingletonComponent::class) @Module class DatabaseModule {

    @Provides
    @Singleton
    fun provideGSDatabase(@ApplicationContext context: Context): GSDatabase {
        return Room.databaseBuilder(context, GSDatabase::class.java, GS_DATABASE_NAME)
            .fallbackToDestructiveMigration()
            .build()
    }
    
    @Provides
    fun provideSpeakerDao(gsDatabase: GSDatabase): SpeakerDao {
        return gsDatabase.speakerDao()
    }
    

    } </pre>

    Như đoạn code ở trên, mình khởi tạo đối tượng RomDatabase và một đối tượng Dao của chúng,  đó là khởi tạo object không phải code của chúng ta define, hơn nữa trong trường hợp này còn khởi tạo theo kiểu Builder pattern, nên chúng ta không thể dùng @Inject annotation mà bắt buộc phải dùng @Provides. Bây giờ, chúng ta đã có thể inject object của interface GSDatabase và SpeakerDao ở bất cứ đâu.

    1. Bind

    Đối với interface, chúng ta không thể dùng annotation @Inject, vì nó không có constructor function. Tuy nhiên, nếu bạn có một interface mà chỉ có duy nhất một implementation (một class implement interface đó), thì bạn có thể dùng @Binds để inject interface đó. Việc inject interface thay vì class là trong những cách triển khai được Recommend sử dụng bởi vì nó tuân theo nguyên tắc Dependence Inversion của SOLID, từ đó dễ dàng cho việc testing và maintain mai này.

    <pre> interface ConnectRepository { fun doSomething() } </pre>

    <pre> class ConnectRepositoryImpl @Inject constructor( private val connectManager: ConnectManager ) : ConnectRepository {

    fun doSomething() {} } </pre>

    <pre> @Module @InstallIn(SingletonComponent::class) abstract class ConnectDeviceModule { @Binds @Singleton abstract fun provideConnectRepository( connectRepository: ConnectRepositoryImpl ): ConnectRepository } </pre>

    <pre> class ConnectUseCaseImpl @Inject constructor( private val connectRepository: ConnectRepository, ) : ConnectUseCase {

    fun doSomething() { // Using connectRepository } } </pre>

    Cụ thể trong ví dụ trên, mình khai báo 1 interface ConnectRepository thể hiện cho đối tượng ConnectRepositoryImpl. Từ đó khi sử dụng ConnectRepositoryImpl chúng ta sẽ sử dụng và gọi nó thông qua ConnectRepository.

    Ưu điểm của việc dùng @Binds thay cho @Provides là nó giúp giảm lượng code được generate, như là Module Factory class. Từ đó tăng thời gian build và tăng kích thước file .apk và .aab của Project.

    Trên đây là chia sẻ của mình về 1 số Annotation của Hilt Android. Mong sẽ giúp ích được ít nhiều mọi người. Hẹn mọi người ở bài viết sắp tới

  • Android Bluetooth Low Energy (BLE) – Part 3

    Android Bluetooth Low Energy (BLE) – Part 3

    Chào các bạn, tiếp tục loạt bài vè chủ đề BLE, hôm nay mình tiếp tục trình bày về Discovery Service và Transfer BLE Data

    1. Discovery Service Sau khi kết nối thành công, để phục vụ việc Transfer dữ liệu, điều đầu tiên cần làm khi bạn kết nối với GATT Server trên thiết bị BLE là thực hiện Discovery Service. Điều này cung cấp thông tin về các Available Service trên remote device cũng như các service characteristics và thông tin của chúng. Cụ thể là

      image

      image

      image

      3 thông số trên thì device sẽ có một mã code riêng phục vụ việc Discovery và mở cổng transfer dữ liệu giữa App và thiết bị.

    2. Turning notifications on and off

      Để thực hiện việc Read và Write dữ liệu giữa ứng dụng và Device BLE chúng ta cần Turning notifications sau khi Discovery Service thành công. Cụ thể là việc Call setCharacteristicNotification(). Điều này các bạn hiểu đơn giản là sẽ báo cho ngăn xếp Bluetooth về cái mong đợi nhận thông báo cho characteristic cụ thể của Device BLE đó. Nếu không có điều này hoàn bạn bạn không thể nhận được dữ liệu response từ Device BLE cho dù bạn có thể gọi thành công writeCharacteristic.

      Cụ thể về việc gọi Turning notifications. Chú ý về việc thực hiện sau khi Discovery Service thành công. Bạn có thể làm điểu này bằng Callback. Nhưng trong ví dụ của mình thì mình sử dụng 1 SingleSubject của Rx để thực hiện nó. 

      image

      image

      image  

    3. Read and Write dữ liệu

      Đầu tiên các bạn clear rằng, kiểu dữ liệu giao tiếp giữa ứng dụng và Device BLE là ByteArray. Và để nhận biết việc nhận gửi đó giữa ứng dụng và Device BLE nào thì nó sẽ thông qua outputCharacteristicinputCharacteristic tương ứng với việc Write và Read. 2 thông số outputCharacteristicinputCharacteristic lấy được từ đối tượng BluetoothGattService khi sau khi chúng ta Discovery Service.

      Data ByteArray gửi lên được define cụ thể cho từng Device BLE cụ thể. Đa phần sẽ thuộc về team Hardware và Firmware cung cấp file command request và response tương ứng của thiết bị đó.

      Có 1 điểm lưu ý là trong quá trình trao đổi dữ liệu giữa ứng dụng và Device BLE, chúng ta sẽ gửi nhận liên tục. Hoàn toàn bài toán của việc gửi dữ liệu, Device BLE chưa trả về và mình lại đã gửi tiếp dữ liệu 1 lần nữa. Để giải quyết bài toán này, các bạn nên quản lý việc gửi nhận dữ liệu (hay ở đây mình hay gọi là command request ) trong 1 Queue tương ứng. Điều đó sẽ giúp các bạn quản lý dễ dàng cho dù bạn muốn việc thực hiện tuần tự, hay loại nào khác cũng sẽ dễ dàng hơn.

      image

    • Đây là quá trình gửi dữ liệu từ ứng dụng sang Device BLE. Thông qua việc gọi writeCharacteristic qua đối tượng bluetoothGatt.

      image

    • Sau khi gọi thành công. System sẽ trả về hàm callback tương ứng, thông về status đã gửi thành công hay thất bại đến Device BLE.

      image

    • Tiếp đến là quá trình nhận dữ liệu sau khi request gửi đến Device BLE thành công. Thiết bị sẽ trả về response tương ứng tại 1 trong 2 functions.

      image  Sau khi nhận được dự liệu kiểu ByteArray. Chúng ta sẽ thực hiện việc xử lý dữ liệu tương ứng.

    Lưu ý:

    Vì BLE là asynchronous nên là có nhiều Thread được tạo ra và thực thi. Cụ thể ở đây là  khi nhận callbacks on BluetoothGattCallback, thì các dòng code này sẽ thực thực hiện **Binder** threads. Các hàm mình muốn nói đến ở đây ví dụ như các hàm **onCharacteristicWrite()**, **onCharacteristicRead()**, **onCharacteristicChanged()**, … đây là 1 số functions quan trọng trong việc xử lý và thực thi gửi nhận dữ liệu. 
    
    Vậy tại sao nên tránh xử lý trên Binder threads? Khi bạn nhận được nội dung nào đó trên **Binder** Thread, Android sẽ không gửi bất kỳ lệnh gọi lại mới nào cho đến khi code của bạn hoàn thành trên **Binder** Thread. Vì vậy, nói chung, bạn nên tránh thực hiện nhiều thao tác trên các luồng **Binder** vì bạn đang chặn các cuộc gọi lại mới khi bạn đang sử dụng nó.
    

    Đây là những chia sẻ mang tính overview của mình về BLE của Android. Mong ít nhiều có thể chia sẻ cho mọi người.

  • Android Bluetooth Low Energy (BLE) – Part 2

    Android Bluetooth Low Energy (BLE) – Part 2

    Chào các bạn, tiếp tục loạt bài vè chủ đề BLE, hôm nay mình tiếp tục trình bày về Connection, Disconnect BLE

    1. Connection

    Sau khi bạn đã Scan thấy thiết bị của mình bằng cách quét tìm thiết bị, bạn phải kết nối với thiết bị đó bằng cách gọi connectGatt(). Nó trả về một đối tượng BluetoothGatt mà sau đó bạn sẽ sử dụng cho tất cả các hoạt động liên quan đến GATT như đọc ghi dữ liệu thông qua BLE (Read and Write). Các bạn chú ý, có 2 version của phương thức connectGatt()

    image

    Hay cụ thể hơn

    image

    Nào chúng ta cùng đi vào sâu hơn một số đối số trong hàm Connect này.

    1. “autoConnect” parameter: Đối số chỉ ra rằng bạn có muốn kết nối ngay lập tức hay không.
    • Trong trường hợp này mình set giá trị là “False” tức là ‘connect immediately’. Theo mình tìm hiểu, Android sẽ cố gắng connect trong 30s, nếu không thành công đẩy ra lỗi Timeout (Thường với mã lỗi là 133). Có một lưu ý là bạn chỉ có thể tạo một kết nối tại một thời điểm bằng cách sử dụng false, vì Android sẽ hủy mọi kết nối khác có giá trị false, nếu có.
    • Vậy nếu mình set là “True” thì sao nhỉ? Android sẽ kết nối bất cứ khi nào nó nhìn thấy thiết bị và cuộc gọi này sẽ không bao giờ Timeout. Theo mình tìm hiểu, bên trong stack sẽ tự quét và khi nhìn thấy thiết bị, nó sẽ kết nối với thiết bị đó. Bạn có thể cân nhắc đến bài toán Reconnect device trong trường hợp này. Bạn chỉ cần tạo một đối tượng BluetoothDevice và gọi connectGatt với value là “True” tương ứng với đối số autoConnect.
    1. “transport” parameter: Đưa ra các mode cho việc connect giữa Device và ứng dụng. Nếu trong trường hợp của việc kết nối BLE thì các bạn nên sử dụng value TRANSPORT_LE để tránh những lỗi không mong muốn trong quá trình connect. Có một giá trị mà các bạn cũng cần lưu ý là TRANSPORT_AUTO, hỗ trợ cho việc connection giữa điện thoại và thiết bị hỗ trợ kết nối cả BLE và Bluetooth classic.

    Sau khi mình gọi việc connect, System sẽ đẩy kết quả và các callback tương ứng thông qua BluetoothGattCallback

    image

    2. Disconnection

    Mình có tìm hiểu có rất nhiều source mẫu handle việc gọi disconnect BLE sai cách. Sau đây là các đúng mà mình tìm hiểu được nếu bạn muốn Disconnect BLE

    • Call disconnect()
    • Đợi Callback onConnectionStateChange với trạng thái Disconnect
    • Call close()
    • Dispose gatt object

    image

    image

    Lệnh disconnect() thực sự sẽ thực hiện ngắt kết nối và cũng sẽ cập nhật trạng thái kết nối nội bộ của ngăn xếp Bluetooth. Sau đó, nó sẽ kích hoạt gọi lại onConnectionStateChange để thông báo cho bạn rằng trạng thái mới hiện đã bị ‘ngắt kết nối’.

    Lệnh gọi close() sẽ hủy đăng ký BluetoothGattCallback của bạn và giải phóng ‘client interface’.

    Cuối cùng, việc xử lý đối tượng BluetoothGatt sẽ giải phóng các tài nguyên khác liên quan đến kết nối.

    Ở biết tiếp theo mình sẽ tiếp tục với việc Discovery Service BLE sau khi Connection thành công và Transfer BLE Data của BLE. Hẹn các bạn trong bài viết sắp tới.

  • Android Bluetooth Low Energy (BLE)

    Android Bluetooth Low Energy (BLE)

    Chào các bạn, hiện tại mình đang làm một dự án liên quan đến BLE (Bluetooth Low Energy). Mình tìm trên mạng thì thấy khá nhiều tài liệu liên quan đến Bluetooth Classic, còn tài liệu về BLE thì còn hạn chế hơn rất nhiều. Nhưng mình lại để ý rằng các ứng dụng phục vụ kết nối với thiết bị thì hiện tại sử dụng BLE là một giải pháp kết nối là khá thông dụng. Vậy nên hôm nay mình sẽ chia sẻ một chút kiến thức cơ bản liên quan đến chủ đề này. Đâu đấy sẽ giúp các bạn có thể Overview qua về hướng tiếp cận, để từ đó dễ dàng investigate và triển khai theo yêu cầu.

    Bài của mình liên bao gồm những phần chính sau:

    1. Giới thiệu về BLE
    2. Find BLE Device
    3. Connection and Disconnect BLE Device
    4. Transfer BLE Data

    Let’s start

    1. Giới thiệu về BLE

    Đặt vấn đề, trong hầu hết các trường hợp, các nhà thiết kế thiết bị có thể đeo, ngoại vi, cũng như tất cả các mặt hàng khác cần mở rộng chức năng của chúng với điện thoại thông minh. Đều cần tìm một giải pháp để kết nối, điều khiển và chia sẻ dữ liệu. Và Bluetooth Classic và BLE chính là giải pháp. Trong phạm trù bài chia sẻ hôm nay mình chỉ tập trung vào nền tảng Android. Như các bạn đã biết, Bluetooth Classic cho phép thiết bị trao đổi dữ liệu không dây với các thiết bị Bluetooth khác. Khung ứng dụng cung cấp quyền truy cập vào chức năng Bluetooth thông qua API Bluetooth. Các API này cho phép các ứng dụng kết nối vớ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.

    Android version 4.3 (API 18) and above BLE ra đời. BLE được thiết kế để tiêu thụ điện năng thấp hơn đáng kể. Điều này cho phép các ứng dụng giao tiếp với các thiết bị BLE có yêu cầu năng lượng nghiêm ngặt hơn. Cụ thể: Các trường hợp sử dụng phổ biến bao gồm:

    • Truyền một lượng nhỏ dữ liệu giữa các thiết bị lân cận.
    • Tương tác với các cảm biến tiệm cận để cung cấp cho người dùng trải nghiệm tùy chỉnh dựa trên vị trí hiện tại của họ.

    2. Find BLE Device

    Đây là bước đầu tiên trước khi sử dụng các tính năng Bluetooth Classic hoặc BLE. Các bạn cần đảm bảo rằng tất cả các quyền và tính năng cần thiết đều được áp dụng và cho phép.

    Permission:

    • android.permission.BLUETOOTH – các tính năng Bluetooth classic và BLE cơ bản
    • android.permission.BLUETOOTH_ADMIN – các thao tác BC và BLE nâng cao như bật/tắt module _ Bluetooth, discovery device, ….
    • android.permission.ACCESS_COARSE_LOCATION – cần thiết để quét BLE trên Android 5.0 (API 21) trở lên. Lưu ý: Đây là Runtime Permission từ API 23.

    Mình có đọc thêm chú ý về một quyền mà mình thấy hay được khai báo khi xử lý vs BLE. Các bạn chú ý cũng cần đảm bảo rằng điện thoại hay Tablet có bộ điều hợp Bluetooth tích hợp. Nếu muốn không khả dụng cho các thiết bị không có Bluetooth, chúng tôi chỉ cần thêm một khai báo:

    <uses-feature android:name="android.hardware.bluetooth"/>
    

    Tuy nhiên, theo Document mình tìm hiểu, không bắt buộc nếu khai báo quyền Bluetooth và đặt phiên bản Android 5 trở lên.

    image

    Để tìm các thiết bị BLE, bạn sử dụng startScan()stopScan(). Phương thức này lấy leScanCallback làm tham số. Bạn phải triển khai lệnh gọi lại này vì đó là cách trả về kết quả quét. Chú ý đến việc optimize khi thực thi, ví dụ stopScan() khi đã tìm thấy device mà các bạn muốn hoặc đặt một interval time scan nhất định …

    image

    Bạn cũng có thể cân nhắc đến mode khi Scan trong trường hợp xuống Background và lên ForceGround tương ứng để optimize năng lượng của thiết bị.

    image

    Ở biết tiếp theo mình sẽ tiếp tục với việc Connection, Disconnect BLE Device và Transfer BLE Data của BLE.

    Hẹn các bạn trong bài viết sắp tới.

  • Splash Screen

    Splash Screen

    Hôm nay mình quay lại với chủ đề khá quen thuộc liên quan đến Splash Screen. Như chúng ta đã biết splash screen (màn hình khởi động ứng dụng) là trải nghiệm và là thứ đầu tiên người dùng nhìn thấy đối với mỗi ứng dụng.

    Những chức năng cụ thể của nó là show lên hình ảnh của App, một kiểu nhận dạng của App đó. Nhưng có thể sâu xa hơn của việc tạo ra màn hình này là thực hiện một số việc trước khi vào giao diện chính của ứng dụng, có thể liệt kể ở đâu là lấy và đồng bộ dữ liệu ở đâu đó (có thể là server, database, … ), chuẩn bị data hiển thị ở màn hình chính, …

    Bài viết dưới đây mình xin giới thiệu qua một số cách tiếp cận và triển khai khi thực hiện việc implement một Splash screen. Và một số ưu, nhược điểm của nó. Mình có thể liệt kê 3 cách chính mà thông thường mình thấy trong thời điểm hiện tại.

    1. Tạo một Activity và 1 layout XML tương ứng của nó

    Cách này theo mình thấy là cách phổ biến nhất mà các app trước khi có Android 12 đang sử dụng. Thực hiện cách này, lập trình viên chỉ cần tạo một giao diện XML theo đúng Splash UI theo requirement và setContentView cho nó. Khi muốn kết thúc, chúng ta chỉ cần start Activity chính tương bằng Intent và nhớ finish() Splash Screen hiện tại

    2. Tạo một Activity và set them background cho chúng

    Nếu thật sự các bạn thực hiện giống ở cách một, nhiều khả năng các bạn sẽ gặp phải trường hợp khi bập bật màn hình nên bạn thường thấy màn hình trắng một lát trước khi ảnh được hiện lên là do layout của bạn chỉ được hiển thị sau khi ứng dụng đã khởi tạo xong. Vì vậy để tránh việc này thay vì bạn tạo một layout cho màn hình này thì bạn sẽ tạo theme cho riêng Splash Activity này. Background mà bạn thiết lập giống như layout của Splash Screen mà bạn muốn triển khai.

    Các bước thực hiện

    Để có thể làm splash screen bằng theme, đầu tiên bạn cần tạo một XML drawable splash_background.xml bên trong thư mục res/drawable

    image

    Bước tiếp theo ta set splash_background.xml cho hình nền của theme. Ta thêm SplashTheme cho splash activity như sau:

    image

    Sau đó ta cấu hình SplashTheme cho Activity Splash screen trong AndroidManifest.xml

    image

    Bước cuối cùng ta tạo một activity SplashActivity.java mà không cần setContentView cho nó

    image

    Bằng cách làm trên View sẽ hiển thị từ theme mà không cần khỏi tạo layout nên ứng dụng sẽ chạy nhanh hơn, và thỉnh thoảng sẽ không bị xuất hiện hiện tượng màn hình trắng. Đây là một cách hợp lý mà các bạn có thể cân nhắc nếu vẫn muốn khởi tạo một Activity riêng biệt cho Splash Screen.

    3. Sử dụng API Splash Screen Android 12

    Như tên gọi của nó, từ Android 12 ra mắt một cách triển khai để tạo ra Splash Screen mới. Trải nghiệm mới mang đến các yếu tố thiết kế tiêu chuẩn cho mọi lần khởi chạy ứng dụng, nhưng nó cũng có thể tùy chỉnh để ứng dụng của bạn có thể duy trì thương hiệu riêng của ứng dụng.

    Vậy Splash Screen hoạt động thế nào Khi một app khởi chạy thì có thể rơi vào 1 trong các giai đoạn sau:

    • Cold Start (app chạy lần đầu). Ví dụ như ứng dụng của bạn được khởi chạy lần đầu tiên kể từ khi thiết bị khởi động hoặc kể từ khi hệ thống kill app
    • Warm Start (Khởi chạy các tiến trình nhưng chưa tạo Activity). Ví dụ: Khi đưa app xuống background một thời gian dài, hệ thống tự loại bỏ ứng dụng ra khỏi bộ nhớ Stack hiện tại, khi đó các tác vụ và process cần khởi động lại.
    • Hot start Khi mới bắt đầu, tất cả những gì hệ thống làm là đưa Activity của chúng ta lên hàng đầu của Stack. Nếu tất cả các Activity của ứng dụng vẫn nằm trong bộ nhớ, thì ứng dụng có thể tránh phải lặp lại quá trình khởi tạo đối tượng, lạm phát bố cục và hiển thị. Màn Splash này sẽ không chạy khi ở giai đoạn Hot start.

    Bài tiếp theo mình sẽ giới thiệu tiếp liên quan đến các yếu tố, cơ chế và triển khai cụ thể kèm code của API Splash Screen nhé.

  • Android Notification

    Android Notification

    Xin chào mọi người. Hôm nay có dịp đọc lại phần liên quan đến notification của code cũ mình đã từng implement. Mình muốn chia sẻ một chút kiến thức liên quan đến phần này. Về bản chất việc gửi nhận của notification và các implement nó. Cụ thể hơn mình xin giới thiệu 2 Platform support việc này là Firebase và Baidu (dành riêng cho thị trường Trung Quốc, vì bển đó hầu như không support các dịch vụ Google Service)

    1. Cơ chế

    Đầu tiên là cơ chế: Bạn có thể tham khảo mô hình cơ chế của thằng Firebase Cloud Messaging (FCM) với phương thức truyền nhận image 1. Thiết bị tích hợp GCM / FCM sẽ gửi yêu cầu cung cấp Registration ID đến GCM / FCM server 2. GCM trả lời dựa trên thông tin App và trả về registration ID riêng cho từng thiết bị App. 3. Thiết bị App sẽ gửi ID lên server yêu cầu lưu thông tin của ID vừa nhận được tương ứng cho thiết bị lên Server đó 4. Mỗi khi server của chúng ta cần gửi yêu cầu push, nó sẽ gửi push message lên GCM, kèm cái ID mà thiết bị App đã gửi lên để lưu ở bước 3 5. GCM / FCM sẽ xem tin có hợp lệ hay không, và xem registration ID có tồn tại không, rồi gửi gói tin message. Và thiết bị App sẽ nhận được tin push.

    1. FCM

      1. Message Các bạn lưu ý có 2 loại chính trong việc gửi nhận với FCM. Từ những message này, lập trình viên chúng ta sẽ định dạng, nhận và handle hiển thị nó tương ứng trên Application.

        1. Notification Message: Message này được xử lý bởi Firebase SDK tích hợp trong ứng dụng. Chúng bao gồm tin nhắn, icon, tiêu đề. Các tin này có thể được gửi từ Firebase Console UI. Message kiểu này có dạng:

        <pre> { "to" : “registration ID …”, "notification" : { "body" : “Body “, "title" : "Title Message“, "icon" : “Icon Message“ } } </pre>

        1. Data Message: Message loại này sẽ cần được xử lý bởi lập trình viên. Loại Message này không thể được gửi từ Firebase Console như trên. Với message loại này, các bạn cần tự xây dựng backend riêng của mình, message có dạng <pre> { "to" : "registration ID …", "data" : { "name" : “Name message “, “Message content” : “Content”, “Icon” : "Icon"
          } } </pre>
      2. Đối tượng nhận tin

        1. Global: Notification sẽ được gửi đến tất cả các đối tượng đã đăng kí với Server (Ví dụ thông báo tin nhắn chung mà tất cả những ai đã cài app đều có thể nhận được)
        2. Topic: Tin nhắn sẽ được gửi cho các đối tượng đã đăng ký một topic nào đó (Ví dụ bài toán này là việc khi app muốn chỉ những người tại một ngôn ngữ, hay một nhóm nhất định, chỉ nhóm đó mới nhận được, hoặc để từng nhóm có thể nhận được các loại khác nhau. Trong trường hợp này Topic sẽ phát huy tác dụng của nó)
        3. Individual: Đây là việc nhận gửi tin nhắn trực tiếp đến một đối tượng của thể thông qua ID đăng kí duy nhất của nó (Ví dụ bài toán này có thể kể tới là các app chat, gửi củ thể đến từng người riêng biệt)
      3. Thực hiện

        1. Config FCM trên Firebase Các bạn có thể theo hướng dẫn https://firebase.google.com/docs/cloud-messaging/android/client. Điều lưu ý là các bạn kiểm tra kĩ file json config được thêm dưới Project App.

        2. Đăng kí Topic: Sau đây mình xin giới thiệu về việc đăng kí theo loại Topic và handle chúng. image

          image

          image

          Sau khi lấy dữ liệu, bạn có thể sử dụng đối tượng NotificationCompat.Builder để thực hiện setup hiển thị data lên UI Notification tương ứng.

    2. Baidu Notification

      Để phục vụ cho thị trường China chặn hầu hết dịch vụ Google Service. Chúng ta sử dụng Baidu Notification như là một công cụ thay thế.

      Bước 1: Tạo tài khoản Nhà phát triển mới trên nền tảng Baidu. (Chú ý cần sử dụng Số điện thoại của China để tạo tài khoản)

      Bước 2: Tải xuống Android SDK cho các dịch vụ đẩy Baidu từ link dưới đây: http://push.baidu.com/sdk/push_client_sdk_for_android

      Bước 3: Tích hợp SDK đã tải xuống ở trên vào ứng dụng của bạn giống như cách bạn làm đối với bất kỳ SDK nào có tệp * .so (Thư viện tệp đối tượng được chia sẻ). Việc chúng ta cần đưa chúng vào jniLibs folder image

      Bước 4: Sau đó, giống như trong GCM console, bạn tạo một ứng dụng mới với Id ứng dụng / Tên gói, trong Baidu bạn cũng cần làm như vậy. Đó là truy cập vào URL bên dưới (Đảm bảo bạn đã đăng nhập): http://push.baidu.com/console/app

      Trên trang bảng điều khiển này Tạo ứng dụng mới (Sử dụng plugin Google dịch trên Chrome).

      Bước 5: Sau khi ứng dụng mới được tạo, ứng dụng của bạn sẽ được cấp một API Key và Secret Key. Để nhận Thông báo đẩy, bạn cần sử dụng API Key theo hướng dẫn:

      http://push.baidu.com/doc/android/api

      Tất cả các bước phát triển được chỉ định trong liên kết trên. Chỉ cần dịch trang để làm theo cùng một.

      Bước 6: Trong GCM, bạn có thể gửi thông báo đẩy bằng bảng điều khiển GCM hoặc bạn có thể sử dụng máy chủ của riêng mình để gửi thông báo GCM tới thiết bị. Nó cũng vậy trên Baidu. Sau đây là đoạn code việc nhận và hiển thị Baidu notification các bạn có thể tham khảo. image

      image

      image

      Trong link hướng dẫn của Baidu document. Cũng có 1 project sample, các bạn cũng có thể tham khảo.

      Trên đây là bài viết của mình liên quan đến việc xử lý Notification của FCM và Baidu. Hy vọng có thể ít nhiều giúp các bạn có thể hiểu thêm và có thêm một nơi tham khảo cho việc xử lý này.

      Hẹn gặp lại mọi người ở bài viết sắp tới.

  • Android Scoped Storage

    Android Scoped Storage

    Chào mọi người,

    Hôm nọ mình có có làm task liên quan đến việc maintain một project được phát triển từ năm 2019 với API support là 29 (Android 9). Một loạt các functions phải cập nhật lại trong đó có functions liên quan đến storage. Chắc hẳn nhiều bạn cũng đoán được vấn đề này liên quan đến Scoped Storage – một tính năng mới ra mắt từ Android 10. Mình muốn chia sẻ một chút kiến thức của mình liên quan đến nó và các giải pháp xử lý khi gặp phải bài toán như của mình.

    Sau đây, là một số ý chính mình sẽ đề cập

    1. Vấn đề
    2. Scoped Storage là gì?
    3. Các tính năng chính của Scoped Storage
    4. Cách xử lý với những thay đổi

    Nào mình bắt đầu

    1. Vấn đề

      Trước Android 10, chúng ta có khái niệm về Shared Storage. Mỗi ứng dụng trong thiết bị đều có một số bộ nhớ riêng trong bộ nhớ trong và bạn có thể tìm thấy ứng dụng này trong thư mục android / data / your_package_name. Ngoài bộ nhớ trong này, phần còn lại của bộ nhớ được gọi là Shared Storage. Một phần bộ nhớ mà mọi ứng dụng có quyền lưu trữ đều có thể truy cập, bao gồm Media Collections và các tập tin khác của các ứng dụng khác nhau. Từ việc này phát sinh ra một số vấn đề:

      1. Thứ nhất, ứng dụng nào đó chỉ cần thực hiện một số thao tác nhỏ trên một phần bộ nhớ (ví dụ như chỉ lấy ảnh từ phần bộ nhớ này và tải lên và không làm gì khác). Câu hỏi ở đây là tại sao phải cũng cấp cho ứng dụng đó toàn bộ quyền truy cập vào bộ lưu trữ chung đó?

      2. Vấn đề thứ hai là khi ứng dụng có khả năng ghi rộng như vậy trên bộ lưu trữ thì các tệp do ứng dụng tạo ra bị phân tán và khi người dùng gỡ cài đặt ứng dụng thì các tệp do ứng dụng tạo ra chỉ còn trong bộ lưu trữ và không bị xóa và mất rất nhiều không gian.

    2. Scoped Storage là gì?

      Scoped Storage là một tính năng có tác dụng phân chia dung lượng lưu trữ thành các bộ sưu tập được chỉ định để giới hạn quyền truy cập vào bộ lưu trữ rộng. Hiểu một cách ngắn gọn, với Scoped Storage, mỗi ứng dụng sẽ được cung cấp thư mục riêng nhằm lưu trữ các tệp cần thiết dành cho dữ liệu người dùng và những ứng dụng khác không thể truy cập thư mục đó.

    3. Các tính năng chính của Scoped Storage

      1. Unrestricted access: Mọi ứng dụng đều có quyền truy cập không hạn chế vào bộ lưu trữ của riêng nó, tức là lưu trữ nội bộ cũng như bên ngoài. Vì vậy, với Android 10, bạn không cần cung cấp quyền lưu trữ để ghi tệp vào thư mục ứng dụng của riêng bạn trên thẻ SD.

      2. Unrestricted media: Bạn có quyền truy cập không hạn chế để đóng góp các tệp vào bộ sưu tập phương tiện và tải xuống ứng dụng của riêng bạn. Vì vậy, không cần phải xin phép nếu bạn muốn lưu bất kỳ hình ảnh, video hoặc bất kỳ tệp phương tiện nào khác trong bộ sưu tập phương tiện. Bạn có thể đọc hoặc ghi các tệp phương tiện do bạn tạo nhưng để đọc tệp phương tiện của ứng dụng khác, bạn cần phải có quyền READ_EXTERNAL_STORAGE từ người dùng. Ngoài ra, quyền WRITE_EXTERNAL_STORAGE sẽ không được chấp nhận trong bản phát hành Android tiếp theo và bạn sẽ có quyền truy cập đọc nếu bạn sử dụng WRITE_EXTERNAL_STORAGE. Bạn phải yêu cầu người dùng chỉnh sửa rõ ràng các tệp phương tiện không được đóng góp bởi ứng dụng của bạn.

      3. Media location metadata: Có quyền mới được giới thiệu trong Android 10, tức là ACCESS_MEDIA_LOCATION. Nếu bạn muốn có được vị trí của phương tiện truyền thông thì bạn phải có sự cho phép này

    4. Cách xử lý với những thay đổi

      1. Xử lý tạm thời
      • Lưu ý: Cách xử lý này chỉ nên dùng để chuyển dữ liệu (migrate data) của ứng dụng từ phiên bản cũ lên phiên bản mới dùng Scoped Storage.

      • Vẫn triển khai thiết lập targetSdkVersion 29

      • Khai báo thêm trong thẻ application của manifest.xml, bổ sung thuộc tính requestLegacyExternalStorage bằng true

        image

      Lưu ý: Nếu bạn đang sử dụng Scoped Storage, thì bạn nên di chuyển tất cả các tệp media hoặc tất cả các tệp có trong Shared Storage vào thư mục của ứng dụng. Nếu không, bạn sẽ mất quyền truy cập vào các tập tin đó.

      1. Xử lý tương thích cho Android 10 và version cao hơn

        Bạn cần sử dụng bộ nhớ dành riêng cho mỗi ứng dụng (bộ nhớ trong và bộ nhớ ngoài). Các file lưu trong đó sẽ bị xóa bỏ khi gỡ cài đặt ứng dụng. Các file được tạo ra thường được mặc định là chỉ dùng cho ứng dụng, không nên dùng cho việc chia sẻ truy cập cho các ứng dụng khác. Bạn có thể tham khảo ở link hướng dẫn

        https://developer.android.com/training/data-storage/app-specific

        Các thao tác trên file: Save, Delete, Share

        1. Delete and Update file

          Việc xóa, update dữ liệu file không thuộc quyền quản lý của một ứng dụng hoặc không do ứng dụng tạo ra bây giờ sẽ cần xin quyền xác nhận từ User. Sau khi được User xác nhận cấp quyền, khi đó ứng dụng mới có thể thực hiện xóa file theo thao tác sau:

          image

        2. Read file

          Việc đọc file sẽ phải thông qua Content Uri. Việc truy cập file bằng đường dẫn trực tiếp ở bộ nhớ chia sẻ (SDCard) sẽ không thể thực hiện, trả về SecurityException. Để truy cập file thông qua Uri, bạn có thể sử dụng cách tạo Uri từ file ID như ví dụ dưới đây:

          image

        3. Share file

          Các ứng dụng cần thực hiện public file ra bộ nhớ chia sẻ. Các phương pháp cũ sử dụng MediaScannerConnection.scanFile() không còn hoạt động được nữa. Ví dụ dưới đây là thao tác lưu một file âm thanh ra bộ nhớ chia sẻ.

          image

          image

          image

    Kết luận: Mong là chia sẻ của mình đâu đấy sẽ giúp được một số bạn hiểu rõ hơn và có thể giải quyết một số vấn đề có thể các bạn sẽ gặp phải trong quá trình làm việc. Hẹn gặp lại các bạn trong các bài viết tiếp theo.

    Source Code https://github.com/android/storage-samples/tree/main/MediaStore

  • Android Local Maven (Android Library Part 2)

    Android Local Maven (Android Library Part 2)

    Chào mọi người. Bài viết trước mình đã giới thiệu về Android Library và cách publish lên remote (cụ thể ở đây là Jitpack.io)

    Tiếp nối chuỗi bài liên quan đến Library này.

    Hôm nay mình đưa ra một tình huống. Khi bạn muốn thay đổi (add, update, fix, …) thứ gì đó trên Library. Sau khi thay đổi source xong, theo thứ tự bạn phải update version của nó -> tạo bản release trên git -> send chúng lên JitPack -> Mong đợi chúng không có lỗi gì. Tiếp đến là bên Project chính, các bạn update version của Library trên Project chính và tiếp đó check nó chạy ổn không. Các bước này sẽ tiếp tục lặp lại nếu như source mà bạn code trong Lib của bạn bị fail.

    Tình huống trên gây ra sự mất time và sự chờ đợi. Để giải quyết bài toán trên mình thấy có một giải pháp đó là Local Maven Repository.

    Tất nhiên Lib của bạn thường chứa Sample App để sử dụng tất cả các tính năng của Library, vì vậy bạn có thể kiểm tra xem nó có hoạt động hay không mà không cần toàn bộ quá trình mình liệt kê ở trên, nhưng đôi khi vẫn chưa đủ và bạn cần phải kiểm tra trên dự án mà bạn thật sự muốn triển khai Library trên đó.

    Những lợi ích mà Local Maven Repository có thể thấy được là:

    1. Đưa cho bạn 1 lựa chọn nếu bạn chỉ muốn lưu Android Library hoặc module dưới local như là một maven repository trong máy tính của bạn. Từ đó bạn có import dependency một cách trực tiếp vào Project của bạn như thể nó đã từng được publish.
    2. Đưa ra giải pháp phù hợp để tiết kiệm về mặt thời gian, tránh sự chờ đợi không cần thiết, cũng như tạo ra sự chủ động cho Developer trong quá trình phát triển.

    Nào chúng ta cùng đi vào các bước để triển khai.

    1. Đầu tiên như thiết lập 2 project mà chúng ta đã setup trước đó: CalculatedApp (Main Project) and CalculatedLib (Lib)

    2. Library

      1. Trong file build.gradle, thêm plugin

      image

      1. Định nghĩa artifactId và groupId

      image

      1. Add Config

        1. Nếu Lib của bạn là single module, hoặc là có nhiều modules nhưng các modules không có sự phụ thuộc lẫn nhau.

        image

        1. Nếu Project của bạn có nhiều module là các thư viện độc lập và một module là là tập hợp của các lib đó.

        image

        1. Tip: Nếu project của bạn chứa nhiều modules. Để config chúng, chúng ta phải tạo cho mỗi một Lib một config giống như trên. Để đơn giản hơn chúng ta có thể tạo 1 file publish_local.gradle file. Trong file này mình cài đặt trong một config chung cho các Lib.

        image

        image

        Trong mỗi lib mình chỉ cần set-up groupId và artifactId tương ứng

        image

        Đặc biệt là add link dẫn đến file config tổng

        image

        1. Publish To Maven Local

        Tất cả những gì cần lúc này là run task: publishToMavenLocal

        image

        Hoặc chạy lệnh Terminal:

        <pre> ./gradlew clean ./gradlew build ./gradlew publishToMavenLocal
        </pre>

        image

        Cụ thể ở đây là 2 file:

        1. File .pom chứa thông tin của Lib (dạng XML)

        2. File .aar là file Lib được build ra

        3. Cuối cùng là Add dependency và config vào Project chính sử dụng thư viện của bạn

        image

        • Enable mavenLocal() repository vào file Build Systems
        • Chú ý là add mavenLocal() vào đầu danh sách. Cần lưu ý rằng việc có mavenLocal ở đầu danh sách sẽ giúp bạn luôn chọn các thư viện có sẵn trong thư mục ~/ .m2 / repository / trước tiên

        Tiếp Theo, Add dependency tương ứng vào Project chính và sử dụng functions Library trong source code chính.

        image

        Vâng. Đó là chia sẻ nhỏ của mình liên quan đến Library, publish remote vs local của chúng.

        Các bạn có thể tham khảo source code mình để bên dưới.

        Mong rằng bài viết của mình đâu đấy sẽ giúp các bạn trong cộng đồng Android GST mình. Hẹn gặp lại trong bài viết sắp tới.

        Source code:

        Library

        Main Project