Tag: android

  • Flow trong Coroutines Andorid

    Flow trong Coroutines Andorid

    Flow Trong Coroutines

    Trong coroutines , Flow là một kiểu có thể trả về nhiều giá trị một cách tuần tự trái ngược với việc dừng lại các hàm chỉ trả về 1 giá trị duy nhất để dễ hiểu thì khi bạn nhận giá trị trả về từ database là một list thì bạn sẽ phải tốn thời gian đợi database select hết giá trị rồi truyền vào list còn trong flow thì lấy được giá trị nào bạn có thể collect luôn được giá trị đó, rất tiện phải không nào , giả sử cái database của bạn có hàng chục triệu bản ghi xem , sài List là vã mồ hôi ngay =))

    Về cơ bản thì mọi anh em sẽ thấy Flow với Sequence khá giống nhau như kiểu cùng cha khác mẹ với ,thằng Flow thì nó xử lý bất đồng bộ còn Sequence là xử lý đồng bộ. Nếu bạn không hiểu thì cứ hiểu rằng Flow nó chạy song song với MainThread còn thằng Sequence sẽ Block MainThread lại để nó chạy xong đã .

    Code minh họa nhé

    Đây là sequence

    fun foo(): Sequence<Int> = sequence { 
        for (i in 1..3) {
            Thread.sleep(1000)
            yield(i) 
        }
    }
    
    fun main() = runBlocking {
        
        launch {
            println(Thread.currentThread().name)
            for (k in 1..3) {
                delay(1000)
                println("I'm blocked $k")
            }
        }
        val time = measureTimeMillis {
            foo().forEach { value -> println(value) }
        }
        println("$time s")
    }
    

    Output

    1
    2
    3
    3000 s
    main
    I'm blocked 1
    I'm blocked 2
    I'm blocked 3
    

    Anh em có thể thấy khi thằng Foo() chạy sẽ Lock MainThread lại không cho dòng For kia chạy mà đợi nó chạy xong thì mới unlock MainThread còn Flow thì sao nhìn ví dụ nhé

    fun foo(): Flow<Int> = flow {
        for (i in 1..3) {
            delay(1000)
            emit(i) 
        }
    }
    
    fun main() = runBlocking {
        launch {
            println(Thread.currentThread().name)
            for (k in 1..3) {
                delay(900)
                println("Running after Flow collect $k")
            }
        }
      
        val time = measureTimeMillis {
            foo().collect { value -> println(value) }
        }
        println("$time s")
    }
    

    Output

    main
    1
    Running after Flow collect 1
    2
    Running after Flow collect 2
    3
    Running after Flow collect 3
    3000 s
    

    Rõ ràng nó sẽ không Block MainThread lại mà cả hàm Foo và For đều chạy song song với nhau.

    1.Thêm Flow vào Andorid

    Vì nó nằm trong coroutines nên anh em import coroutines vào là xong, dễ mà vào build.gradle mà implementation thôi

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
    

    2.Flow là các Cold Stream

    Trong RxJava, mỗi Observables đại diện cho một luồng dữ liệu, và phần thân của nó không được thực thi cho đến khi nó được đăng ký (subcribed) bởi một người đăng ký (subcriber) và sẽ nhận được dữ liệu khi nguồn phát ra dữ liệu.

    Flow hoạt động tương tự như vậy, nó cũng không nhận được dữ liệu cho đến khi collect dữ liệu.

    runBlocking {
        coroutinesFlow.foo()
        println("Delay 2s")
        delay(2000)
        coroutinesFlow.foo().collect {
           println(it)
        }
    }
    
    fun foo(): Flow<Int> = flow {
        println("Flow started")
        emit(1)
        delay(500)
        emit(2)
    }
    
    Delay 2s
    Flow started
    1
    2
    

    3.Flow cancellation

    Flow tuân thủ việc các nguyên tắc cancellation chung của coroutines. Việc collect của flow chỉ có thể bị hủy khi và chỉ khi flow đang bị suspend (chẳng hạn như gặp hàm delay) và ngược lại flow không thể bị hủy.

    Khi chúng ta sẽ sử dụng scope launch{} sẽ trả về 1 Job, từ Job này chúng ta có thể cancel scope đó, và nếu chúng ta đặt flow bên scope và khi cancel thì flow cũng bị cancel theo.

    fun cancelFlow() = runBlocking {
       val job = launch(Dispatchers.Default) {
           flow {
               (1..9).forEach {
                   delay(1000)
                   emit(it)
               }
           }.collect {
               println("value $it")
           }
       }
       delay(5000)
       job.cancel()
    }
    
    value 1
    value 2
    value 3
    value 4
    

    Kết quả chính xác sẽ là in ra từ value 1 -> value 9, nhưng do chúng ta delay 5s rồi cancel job nên flow cũng bị cancel từ đó luôn.

    Từ ví dụ này chúng ta biết được các scope lồng nhau thì khi scope cha bị cancel thì các scope con cũng bị cancel theo

    fun cancelScope() = runBlocking {
       val startTime = System.currentTimeMillis()
       val job = launch(Dispatchers.Default) {
           var nextPrintTime = startTime
           var i = 0
           while (i < 5) {
               if (System.currentTimeMillis() >= nextPrintTime) {
                   println("job: I'm sleeping ${i++} ...")
                   nextPrintTime += 1000L
               }
           }
       }
       delay(1300L)
       println("main: I'm tired of waiting!")
       job.cancel()
       println("main: Now I can quit.")
    }
    
    job: I'm sleeping 0 ...
    job: I'm sleeping 1 ...
    main: I'm tired of waiting!
    main: Now I can quit.
    job: I'm sleeping 2 ...
    job: I'm sleeping 3 ...
    job: I'm sleeping 4 ...
    

    Mặc dù đã gọi cancel để huỷ coroutine rồi nhưng vòng while kia vẫn chạy bất chấp, đó là bởi vì khi gọi cancel thì nó sẽ set lại 1 property là isActive từ true sang false, và mọi hàm support từ coroutine như delay, emit đều check xem isActive còn bằng true hay không? Nếu bằng false thì nó sẽ huỷ bỏ tiến trình đó luôn.

    Vậy sửa đoạn code trên ta chỉ cần thay điều kiện while (i < 5) sang while (isActive) là được.

    Qua ví dụ này chúng ta biết được để xem 1 coroutine bị cancel hay chưa, chỉ cần check property isActive

    4.Cách tạo Flow

    flowOf () – Nó được sử dụng để tạo luồng từ một tập giá trị nhất định.

    flowOf(4, 2, 5, 1, 7).onEach { delay(500) }
    

    asFlow () – Đây là một hàm mở rộng giúp chuyển đổi kiểu thành các luồng.

    (1..5).asFlow().onEach{ delay(500)}
    

    flow {} – Đây là một scope trình tạo để xây dựng các luồng tùy ý như các ví dụ trên.

    channelFlow {} (cold stream) – Cách này tạo ra luồng dữ liệu bằng cách sử dụng hàm send, giống như onNext trong RxJava Ví dụ:

    channelFlow {
        send(1)
    }.flowOn(Dispatchers.Default)
    

    Kết luận

    Anh em có thể thấy Flow rất mạnh trong việc xử lý bất đồng bộ cũng không kém gì Rxjava cả , các bạn có thể thấy Flow thường hay được sử dụng với LiveData trong Android Jetpack. Ở bài viết này còn rất nhiều thứ mình muốn đề cập đến như các toán tử , context , exception trong flow nhưng mình xin đề cập ở phần sau.

    Tài liệu tham khảo

    1. Kotlin Coroutines Flow

    2. Developer Kotlin Flow

    3. LiveData with Flow

  • Google Instant Apps

    Android Instant Apps – Trải nghiệm mới về Ứng dụng Android

    Mở đầu

    Tại sự kiện I/O 2016, Google đã công bố một khái niệm hoàn toàn mới về ứng dụng Android. Đó là Android Instant App. Đúng như tên gọi thì Instant Apps là những ứng dụng có thể được sử dụng ngay cả khi không cần cài đặt. Cũng giống như lúc đi chợ, khi "lựa hành" trên CH Play chúng ta đều muốn kiểm tra, "lật qua lật lại" "món hàng" mình định mua. Instant App hỗ trợ người dùng có thể "test nhanh" ứng dụng rồi mới quyết định có muốn tải về hay không, thay vì bị lừa bởi hình ảnh, video intro rồi tải về và xóa ngay lập tức vì thất vọng.

    Bài viết hôm nay chúng ta sẽ tìm hiểu về Android Instant Apps.

    Instant App là gì và sự khác biệt với Normal App như thế nào ?

    • Như phần giới thiệu, bạn cũng đoán được Instant App là gì. Hiểu một cách đơn giản, Instant App là một ứng dụng native giúp cho người dùng có thể ngay lập tức sử dụng ứng dụng của các nhà phát triển mà không cần cài đặt. Để xây dựng ứng dụng như vậy, các nhà phát triển cần tạo ra các module riêng và tích hợp với deep link, sau đó người dùng có thể nhấp vào URL để dùng thử ứng dụng.
    • Instant App vẫn được tải về như các app bình thường khác nhưng thay vì ở lại trên điện thoại của bạn, nó làm việc giống như bạn truy cập một website và sau đó thoát ra, nó chỉ lưu ứng dụng đó tạm thời và được xóa ngay khi không còn sử dụng.
    • Tiết kiệm thời gian và bộ nhớ sử dụng khi không chiếm dụng tài nguyên thiết bị cũng như mất thời gian download (những ngày nghỉ mà đứt cáp thì bạn sẽ càng hiểu rõ sự khác biệt này)
    • Một ví dụ ưu điểm nữa là khi bạn phát triển một ứng dụng như Instagram hoặc Youtube, muốn chia sẻ với bạn bè thì với Instant App thích hợp, chỉ cần đơn giản gửi 1 link là họ có thể dễ dàng và cực kỳ nhanh chóng xem được mà không cần phải cài đặt ứng dụng.

    Ưu điểm của Instant App (Góc nhìn của lập trình viên)

    1. Tăng khả năng khám phá Instant App đủ điều kiện để được giới thiệu trên trang chủ của Google Play Games, có khả năng tiếp cận với người dùng.
    2. Nhiều người chơi hơn Nếu người dùng không phải lo lắng về vấn đề cài đặt, họ có nhiều khả năng sẽ tham gia vào trò chơi của bạn.
    3. Khả năng kiếm tiền Các dịch vụ mua hàng trong ứng dụng và quảng cáo được hỗ trợ
    4. Trải nghiệm chất lượng cao Mọi thứ hoạt động ngay khi người dùng ấn vào "Instant Play"

    Làm thế nào để sử dụng Instant App ?

    Phần giới thiệu, ưu điểm có vẻ cực kỳ hoành tráng, thời gian ra mắt cũng được 5 năm nhưng nhiều bạn sẽ thắc mắc là lướt CH Play mấy năm trời có thấy cái Instant App nào đâu ?! Là do các bạn chưa kích hoạt nó mà thôi !

    • Vào Setting, tìm đến các cài đặt cho Google và tìm đến Google Play Instant. Tại đây bạn kích hoạt nó lên là có thể sử dụng Instant App của Google. (Tùy mỗi dòng điện thoại sẽ có những cách bố trí mục cài đặt khác nhau nên mình không thể chỉ rõ step-by-step được)

    Vậy làm thế nào để tạo ra một Instant App ?

    Cuối cùng thì phần mà các lập trình viên hóng nhất cũng đến. Cùng tìm hiểu cách tạo một Instant App đơn giản nào !

    I. Permission

    Vì sự nhỏ gọn, tiện lợi, Instant App không thể thực thi tất cả những tác vụ mà một App bình thường có thể làm. Cụ thể, nó chỉ có thể sử dụng các permissions sau:

    • ACCESS_COARSE_LOCATION
    • ACCESS_FINE_LOCATION
    • ACCESS_NETWORK_STATE
    • BILLINGDeprecated as of Play Billing Library 1.0.
    • CAMERA
    • INSTANT_APP_FOREGROUND_SERVICEOnly in Android 8.0 (API level 26) and higher.
    • INTERNET
    • READ_PHONE_NUMBERSOnly in Android 8.0 (API level 26) and higher.
    • RECORD_AUDIO
    • VIBRATE
    • WAKE_LOCK

    Thêm vào đó, các Instant App cũng không thể :

    • Sử dụng background services
    • Send notifications khi chạy trên background

    II. Kết nối với các ứng dụng đã được cài đặt

    Khi phát triển một Instant App, hãy nhớ rằng chỉ có thể tương tác với các ứng dụng đã cài đặt trên thiết bị nếu thỏa mãn một trong các điều kiện sau :

    • Một hoặc nhiều Activity trong các ứng dụng đó được cài đặt android:android:visibleToInstantApps=true – chỉ có sẵn cho Android 8.0 (API 26) trở lên.
    • Ứng dụng đã cài đặt chứa intent filter bao gồm CATEGORY_BROWSABLE
    • Instant App đang gửi một Intent bằng ACTION_SEND, ACTION_SENDTO hoặc ACTION_SEND_MULTIPLE

    III. Cấu hình Project

    1. Thêm khai báo sau vào app module build.gradle :

      implementation("com.google.android.gms:play-services-instantapps:17.0.0")
      
    2. Update targetSandboxVersion :

      <manifest
          xmlns:android="http://schemas.android.com/apk/res/android"
          ...
          android:targetSandboxVersion="2" ...>
      
    3. Khai báo các module hỗ trợ Instant App

      • View > Tool Windows > Project
      • Ấn chuột phải vào module, chọn Refactor > Enable Instant Apps Support
      • Chọn module tại dropdown menu
      • OK Android Studio thêm khai báo sau vào manifest của module:
      <manifest ... xmlns:dist="http://schemas.android.com/apk/distribution">
          <dist:module dist:instant="true" />
          ...
      </manifest>
      
    4. Code bất cứ thứ gì có thể vào module bạn đã chọn

    5. Deploy Instant App

      • Nếu bạn đã cài đặt ứng dụng (với bất kỳ phiên bản nào) trên thiết bị, uninstall nó đi
      • Run > Run/Debug Configurations, kích hoạt Deploy as instant app
      • Run > Run hoặc ấn biểu tượng Run trên toolbar để chạy Instant App.

    Nếu bước 4 của bạn được "thuận buồm xuôi gió" một Instant App sẽ được hiển thị lên thiết bị của bạn. Hãy thử back ra home screen và tìm một vòng xem có app nào được cài đặt không nhé ! – Dĩ nhiên là không rồi.

    Kết thúc

    Trong tương lai gần, có thể nói Instant App là một bước tiến lớn đối với trải nghiệm người dùng. Khi mà tốc độ các kết nối ngày càng nhanh chóng và lưu trữ đám mây trở nên phổ biến; thì việc sử dụng một ứng dụng ngay lập tức và không cần cài đặt là một điều thực sự tuyệt vời.

    Trên đây là một vài giới thiệu tổng quan nhất về Instant App, cũng như tầm phát triển và ý nghĩa mà nó mang lại. Cảm ơn các bạn đã giành thời gian theo dõi.

    Bạn có thể tìm hiểu sâu hơn về Instant App, xây dựng một ứng dụng tại: Android Developer – Google Play Instant

  • Android Architecture – Tại sao chọn MVVM hơn là MVP

    Android Architecture – Tại sao chọn MVVM hơn là MVP

    Bài viết này, mình sẽ trình bày tại sao chọn MVVM hơn là MVP.

    I. Vấn đề

    Một số vấn đề để một lập trình viên quyết định chọn mô hình xây dựng ứng dụng như là làm sao để tái sử dụng code, dễ maintenance, dễ viết unit test hay dễ đọc hiểu với người mới vào trong dự án. Một số vấn đề trên dẫn đến việc chọn lựa mô hình khi bắt đầu một dự án mới là một điều hết sức quan trọng đối với mỗi lập trình viên. Hiện nay, có thể thấy 2 mô hình phổ biến nhất là MVVM và MVP. Trong bài viết này, chúng ta sẽ cùng tìm hiểu về chúng và xem cái nào ưu việt hơn.

    II. Giải pháp

    Bản thân Android được viết dưới dạng MVC trong đó Activity chịu trách nhiệm cho rất nhiều thứ trong đó bao gồm tất cả các logic. Với những ứng dụng đơn giản thì có thể mọi thứ vẫn còn dễ dàng, nhưng khi ứng dụng đủ lớn, số lượng logic tăng lên và mức độ vấn đề cũng tăng theo. Có nhiều mô hình tiếp cận khác nhau như MVP, MVVM,… được chứng minh là có thể giải quyết các vấn đề trên. Người ta có thể sử dụng bất kỳ cách tiếp cận nào, chúng thích ứng với các cách thay đổi một cách nhanh chóng,…

    III. Mục tiêu

    Xây dựng mọi thứ một cách phân tán như vậy để tách biệt dữ liệu – logic – view ra để đối với những project lớn khi số lượng logic và dữ liệu đủ lớn sẽ hữu ích trong việc mở rộng, bảo trì, test,…

    IV. Tại sao là MVVM?

    Có khá nhiều bài viết về MVP về sự sử dụng rộng rãi của mô hình này: Model — View — Presenter. Đó là một mô hình trưởng thành và ở mức độ nhất định, có thể giải quyết vấn đề nhưng vẫn có khá nhiều hạn chế và nó cần phải cải thiện một số thứ.

    Một mô hình MVP đơn giản như sau:

    Mô hình MVP

    Và một mô hình MVVM đơn giản như sau:

    Mô hình MVVM

    Hãy bắt đầu vào những hạn chế của MVP và cách chúng ta có thể khắc phục chúng bằng cách sử dụng MVVM.

    Đối với mỗi View thì đều yêu cầu 1 Presenter, đây là quy tắc ràng buộc cứng nhắc. Presenter giữ tham chiếu đến View và View cũng giữ tham chiếu đến Presenter. Mối quan hệ 1:1 và đó là vấn đề lớn nhất.

    Khi sự phức tạp hay độ lớn của ứng dụng tăng lên dẫn đến việc duy trì và xử lý mối quan hệ này cũng vậy.

    Chính vì những hạn chế trên của Presenter, MVVM được giới thiệ

    ViewModel là các class mô phỏng tương tác với logic/model layer và chỉ hiện trạng thái/ dữ liệu mà không quan tâm ai hoặc dữ liệu sẽ được tiêu thụ thế nào. Chỉ View giữ tham chiếu đến ViewModel và không có trường hợp ngược lại, điều này giải quyết vấn đề của Presenter và View. Một View có thể giữ tham chiếu nhiều ViewModel. Ngay cả một View cũng có thể giữ tham chiếu đến nhiều ViewModel.

    V. Khả năng test

    Bởi vì Presenter bị ràng buộc chặt chẽ với View, dẫn đến việc unit test trở nên hơi khó khăn. ViewModel thâm chí còn thân thiện hơn với Unit test dù chúng chỉ hiển thị trạng thái và do đó có thể được kiểm tra độc lập mà không yêu cầu kiểm tra dữ liệu được tiêu thụ như thế nào. Đây là 2 lý do chính làm cho sự phân biệt, lựa chọn rõ ràng ảnh hưởng đến khả năng unit test của 2 mô hình.

    VI. Tổng kết

    Các mô hình này đang tiếp tục phát triển và MVVM có thể nói tiềm năng để trở nên mạnh mẽ, hữu ích nhưng tuyệt vời để thực hiện. MVP cũng rất hữu dụng và phổ biến nhưng chưa có thể hoàn hảo. Không có thể chắc chắn về tương lai, phù hợp tốt cho tất cả các giải pháp. Người ta có thể thích hoặc không thích MVVM nhưng đó cũng không quá quan trọng, miễn là chúng ta đạt được mục tiêu đang đáp ứng tốt cho project phát triển. Đây cũng là một số cảm nhận khi mình sử dụng qua 2 mô hình này. Do chưa có kinh nghiệm nên bài viết này không thể tránh khỏi sai sót, rất mong nhận được góp ý từ mọi người.

    Trên đây là 1 số chia sẻ về hạn chế của MVP và nó có thể khắc phục bằng MVVM. Cảm ơn các bạn đã theo dõi bài viết!

    Tham khảo: https://android.jlelse.eu/why-to-choose-mvvm-over-mvp-android-architecture-33c0f2de5516

  • Lottie Animations in an Android App

    Lottie Animations in an Android App

    Lottie is an open source animation file format that’s tiny, high quality, interactive, and can be manipulated at runtime. The top 500 apps on the App store now use Lottie to engage users and enhance conversions.A Lottie is an open-source text and vector-based file format that is easy to ship. Its cross-platform capabilities, tiny file size, and scriptable and interactive nature make it popular with designers and developers. Motion evokes emotion. It humanizes your app or website, adds empathetic experiences that entertain and engage. Lottie is the easiest way to bring motion to your apps and platforms.

    Getting Started

    Choose your Lottie

    Choose the Lottie for your Android app. You may have your own or if you don’t , you can select one from Lottie Files if you can’t find a lottie that matches your Android App , you can create your own with Adobe After Effects but this is actually quite difficult

    It’s important to test your selected Lottie using the LottieFiles app for Android to make sure the animation you’ve chosen will play the same Android as sometimes not all animations are built with features supported in the Lottie Android player.

    Just scan the QR code under the animation on LottieFiles with the app to preview the animation.

    Setup your project

    Getting started with Lottie is very straightforward. This guide assumes that you are using Android Studio as your IDE. If you’re using a different IDE, you can still use the same instructions.

    Download

    dependencies {
        def lottieVersion = "3.4.0" 
        implementation 'com.airbnb.android:lottie:$lottieVersion'
    }
    

    Bundling animations with your app

    If you need your animations to work offline, you can bundle your animations with your application by including them in your projects raw resources.

    If your project does not have one, create it by going to File>New>Folder>Raw Resources Folder. If your animation contains images, you can bundle them all together in a .zip with your .json and follow the same procedure.

    Download the animation, rename it to animation.json or animation.zip depending on your use-case, and place into your raw resources folder.

    Add LottieAnimationView to your layout

         <com.airbnb.lottie.LottieAnimationView
                    android:layout_marginEnd="20dp"
                    app:lottie_autoPlay="true"
                    app:lottie_loop="true"
                    app:lottie_rawRes="@raw/lottieAnimation"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_width="30dp"
                    android:layout_height="30dp"/>
    

    Your first Android project with Lottie animations is ready to go!. Now you can build your Android App and you can see your app look pretty good or better than use normal view. You can learn more in The official documentation for lottie-android

    That’s how you get started but there’s so much more you can do. For more tips on how to use Lottie with your projects I will mention in next Post

    Authors

    [email protected]

  • Tại sao nên hạn chế sử dụng Singleton, static function(util class, Helper class)?

    Tại sao nên hạn chế sử dụng Singleton, static function(util class, Helper class)?

    Đây thực sự là một câu hỏi không dễ để trả lời… Một cách thông thường, trong suy nghĩ của hầu hết lập trình viên sẽ là: chỗ nào giống nhau, gọi lại nhiều lần thì nên gom thành common class, static cho dễ gọi.

    Ok, mọi thứ đều ổn, nhưng nếu ko để ý & quá tay một chút thì nó đã vi phạm nghiêm trọng đến design ứng dụng – nó phá vỡ ranh giới của các element,module hoặc class

    Tại sao lại như vậy? Bản chất Lập trình OOP là mô phỏng thực tế cuộc sống vào chương trình bằng ngôn ngữ lập trình. Thay vì dùng văn tả cảnh, thì lập trình viên tả lại cuộc sống bằng ngôn ngữ lập trình trên một framework nào đấy. Ở ngoài cuộc sống có gì, thì chương trình cũng có cái đó, chúng ta có Sinh viên, có Tài khoản ngân hàng, có Ô tô…etc.

    Nhưng nếu bạn để ý, trong cuộc sống Util class, Helper class, Common class… ko tồn tại. Ai đó tạo lên cuộc sống hẳn phải là một lập trình viên cực kỳ vĩ đại.

    Phân ranh giới. (Boundaries)

    Software architect là một nghệ thuật để tạo ra các ranh giới, nhằm phân tách các element, class, module..etc
    

    Cùng xem xét ví dụ sau:

    DisplayHelper – static function

    Alt

    ZxingDecoder cần lấy ra orientation device, hàm lấy orientation dùng ở rất nhiều nơi nên tạo sẵn một class DisplayHelper để dùng. Hàm get orientation cần context nên parameter của ZxingDecoder là context

    Display – interface

    Alt

    Mình tạo ra 1 interface là Display để có thể get orientation của device, parameter đầu vào của ZxingDecoder là display.

    Điểm khác biệt lớn nhất là ở cách viết 2 – ZxingDecoder đã tách khỏi(1 phần tách khỏi) framework android – khi nó xóa đi sự hiện diện của context, tức là mình đang cố gắng phân rõ ranh giới của ZxingDecoder khỏi android framework Nói một cách khác mình đang cố gắng decouple ZxingDecoder khỏi android framework

    ZxingDecoder là 1 bộ decode barcode – nó ko phụ thuộc vào framwork, việc decode này mang ý nghĩa – class mình viết có thể chạy được ở bất cứ đâu, không chỉ là trên android framework

    Ngoài ra, nếu sử dụng DisplayHelper – khi có càng nhiều nơi, càng nhiều class, layer, module gọi hàm get orientation, hoặc một function của DisplayHelper thì các bạn có thể tượng tượng rằng DisplayHelper như một sợi xích đâm xuyên tất cả các layer, module, class, và buộc chặt các thành phần này lại với nhau và hoàn toàn không thể tách rời. Nói cách khác, khi đó ko thể tạo ra các ranh giới Boundaries cho bất cứ thành phần nào dùng chung DisplayHelper. Tất cả đều phẳng, và dính chặt vào android framework thông qua context của DisplayHelper

    Dễ code(Create) – khác với việc dễ maintain(Update).

    Phân ranh giới. (Boundaries) – là target cho hầu hết các task refactor bạn phải làm

    Làm thế nào để phân ranh giới? à, cái ý mình ko dám nói 😀 (ở bài viết này)

  • The Good, The Bad and the Ugly

    (Một bộ phim kinh điển mà mình cực kỳ thích nên lấy nó làm title cho bài viết này)
    Đây là bản “hồi ký” khi mình tìm solution cho một bài toán, hi vọng nó sẽ giúp ai đó định hướng được đường phải đi.

    Bài toán: mix 2 file audio trên android mà không dùng thêm library nào

    • Suy nghĩ đầu tiên là tìm xem android có chìa API nào ra để mix file ko – bỏ đi, các cậu ko cần search, android ko chìa api nào ra để làm việc này đâu
    • Suy nghĩ thứ 2: Đưa dữ liệu âm thanh(mp3..etc) về dạng raw data và thử mix các bit vào nhau? có vẻ khả thi

    The bad:

    
    private byte[] mBufData1 = null;
    private byte[] mBufData2 = null;
    private ArrayList mBufMixedData = new ArrayList<>();
    
    private void loadMixedData() {
            int length1 = mBufData1.length;
            int length2 = mBufData2.length;
            int max = Math.max(length1, length2);
            int tempSplitSize;
            if (length1 == length2) {
                for (int i = 0; i < length1; i++) {
                    mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
                }
            } else {
                if (length2 > length1) {
                    tempSplitSize = length1;
                } else {
                    tempSplitSize = length2;
                }
                for (int i = 0; i < tempSplitSize; i++) {
                    mBufMixedData.add((byte) (mBufData1[i] + mBufData2[i]));
                }
                if (length2 > length1) {
                    for (int i = tempSplitSize; i < max; i++) {
                        mBufMixedData.add((mBufData2[i]));
                    }
                } else {
                    for (int i = tempSplitSize; i < max; i++) {
                        mBufMixedData.add((mBufData1[i]));
                    }
                }
    
            }
        }

    Chạy được thật, âm thanh đã được mix lại như file karaoke ngoài hàng. Nhưng nhìn source code thì chỉ có đứa mù dở mới ko nhìn thấy vấn đề OOM chắc chắn phát sinh.
    Nếu dừng ở đây – Ok, nó chạy được, chúng ta sẽ giấu đi đoạn OOM kia, kệ cho dự án hót shit vì còn lâu họ mới test ra issue ý, lúc phát hiện chúng ta đã cao chạy xa bay rồi. Ka ka ka
    Chúng ta là những kẻ tồi tệ

    The Ugly

    Cải tiến hơn 1 chút, để tránh OOM, mình ko load hết dữ liệu lên ram nữa mà đưa vào DataOutputStream để write từng bit xuống file

    private void createWaveMixing(String p1, String p2, String p3) throws IOException {
            int size1 = 0;
            int size2 = 0;
            int size1;
            int size2;
    
            FileInputStream fis1 = null;
            FileInputStream fis2 = null;
            try {
                fis1 = new FileInputStream(p1);
                fis2 = new FileInputStream(p2);
                size1 = fis1.available();
                size2 = fis2.available();
                long totalAudioLen = size1;
                if (size1 < size2) {
                    totalAudioLen = size2;
                }
                long totalDataLen = totalAudioLen + WavUtil.LENGTH_EXTENDED;
                long longSampleRate = WavUtil.getSampleRate(p1);//44100
                long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
                long longSampleRate = MediaCodecUtils.getSampleRate(p1);//44100
                int channels = 2;
                long byteRate = WavUtil.RECORDER_BPP * longSampleRate * channels / 8;
                long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
                DataOutputStream out = null;
                try {
                    out = new DataOutputStream(new FileOutputStream(p3));
                    WavUtil.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    out.write(toByteArray(mBufMixedData));
                } catch (Exception e) {
                    Log.e(TAG, "#createWaveMixing():" + e.getMessage(), e);
                } finally {
                    if (out != null) {
                        out.close();
                    }
                }
            } finally {
                if (fis1 != null) {
                    fis1.close();
                }
                if (fis2 != null) {
                    fis2.close();
                }
            }
    
        }
    
        private byte[] toByteArray(ArrayList in) {
            byte[] data = new byte[in.size()];
            for (int i = 0; i < data.length; i++) {
                data[i] = in.get(i);
            }
            return data;
        }
    }

    Cách làm này tránh được OOM, nhưng phải nói là nó chậm, thực sự chậm, chậm kinh khủng. Đâu đó mất khoảng 50 -60 s cho 2 file music raw dài 3 phút.
    Nói chung là chạy được, ko có issue gì cả, chỉ chậm thôi. Chậm thì tự tìm cách mà improve đi, kêu gì – đúng ko các cậu? – lại chả phải quá.
    Nếu dừng ở đây, chúng ta là những gã lừa đảo.

    Nhưng dù sao mình cũng là người vừa đẹp trai lại tốt tính, nên mới xuất hiện tình huống thứ 3

    The Good

    Lần này mình optimize bằng cách sử dụng FileChannel, thay vì handle từng bit một, thì mình bóc một nhóm lớn ra để mix(buôn sỉ mới nhanh giầu)

    private void createWaveMixing(String p1, String p2, String p3) throws IOException {
            int size1;
            int size2;
    
            FileInputStream fis1 = null;
            FileInputStream fis2 = null;
            try {
                fis1 = new FileInputStream(p1);
                fis2 = new FileInputStream(p2);
                size1 = fis1.available();
                size2 = fis2.available();
                long totalAudioLen = size1;
                if (size1 < size2) {
                    totalAudioLen = size2;
                }
                long totalDataLen = totalAudioLen + WavUtils.LENGTH_EXTENDED;
                long longSampleRate = MediaExtractorUtils.getSampleRate(p1);//44100
                int channels = 2;
                long byteRate = WavUtils.RECORDER_BPP * longSampleRate * channels / 8;
                DataOutputStream out = null;
    
                FileChannel fc1 = fis1.getChannel();
                FileChannel fc2 = fis2.getChannel();
                long length1 = fc1.size();
                long length2 = fc2.size();
                try {
                    out = new DataOutputStream(new FileOutputStream(p3));
                    WavUtils.writeWaveFileHeader(out, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
                    {
                        ByteBuffer buff1 = ByteBuffer.allocate(BUFFER_SIZE);
                        ByteBuffer buff2 = ByteBuffer.allocate(BUFFER_SIZE);
    
                        ByteBuffer mixedBuffer = null;
                        if (length1 == length2) {
                            while (fc1.read(buff1) > 0) {
                                fc2.read(buff2);
                                mixedBuffer = mixByteBuffer(buff1, buff2);
                                out.write(mixedBuffer.array());
                                buff1.clear();
                                buff2.clear();
                                mixedBuffer.clear();
                            }
                            if (mixedBuffer != null) {
                                mixedBuffer.clear();
                            }
                        } else {
                            if (length2 > length1) {
                                while (fc1.read(buff1) > 0) {
                                    fc2.read(buff2);
                                    mixedBuffer = mixByteBuffer(buff1, buff2);
                                    out.write(mixedBuffer.array());
                                    buff1.clear();
                                    buff2.clear();
                                    mixedBuffer.clear();
                                }
                                while (fc2.read(buff2) > 0) {
                                    out.write(buff2.array());
                                    buff2.clear();
                                }
                            } else {
                                while (fc2.read(buff2) > 0) {
                                    fc1.read(buff1);
                                    mixedBuffer = mixByteBuffer(buff1, buff2);
                                    out.write(mixedBuffer.array());
                                    buff1.clear();
                                    buff2.clear();
                                    mixedBuffer.clear();
                                }
                                while (fc1.read(buff1) > 0) {
                                    out.write(buff1.array());
                                    buff1.clear();
                                }
                            }
    
                        }
    
                    }
                } catch (Exception e) {
                    Log.e("test", "#createWaveMixing():" + e.getMessage(), e);
                } finally {
                    if (out != null) {
                        out.close();
                    }
                    fc1.close();
                    fc2.close();
                }
            } finally {
                if (fis1 != null) {
                    fis1.close();
                }
                if (fis2 != null) {
                    fis2.close();
                }
            }
    
        }

    Kết quả tốc độ tăng gấp 6-8 lần mà lại ko có issue gì cả
    Lần này(dường như) mình sẽ có thể là người tốt

    Cùng 1 bài toán, sẽ có các cách giải khác nhau
    Khoảng thời gian cho phép khác nhau, chúng ta cũng sẽ phải chọn các giải pháp khác nhau(chấp nhận được, chạy có issue, chậm…etc)

    1 ngày làm được ko? được – the bad
    1 tuần làm được ko? được – the ugly
    1 tháng làm được ko? được – the good

    Cái gì cũng sẽ có giá của nó, tùy vào deadline, tùy vào thời điểm, chúng ta hãy cố chọn ra được cách làm tốt nhất

    1 phút dành cho quảng cáo, GDK đã có 1 phiên bản có thể record, mix, nối audio nhé, anh em có thể search gdk-soundutilities

  • Binding library android -Xamarin Android

    Kết quả hình ảnh cho binding android xamarin

    Với một vài dự án khách hàng đã code sẵn cho 1 cái library android rồi, và họ sẽ gửi cho bạn sử dụng cái lib đó. Hoặc là bạn có 1 cái lib rất ngon trên android. Nhưng muốn dùng trong project Xamarin của bạn mà không muốn code lại logic của nó. Như vậy bạn cần binding nó vào project của bạn và chỉ việc dùng không cần code lại 1 tí logic nào.

    I. Input:
    Project dự án code bằng ngôn ngữ Xamarin
    Các file android libraries .aar (Android Archive Library)
    Bản chất của file này là file .zip gồm
    – Compiled Java code
    – Resource Ids (File R)
    – Resources
    – Meta-data (for example, Activity declarations, permissions)

    II. Output
    Project Xamarin có thể gọi và sử dụng được class chứa trong file .aar

    III.Guideline
    1. Tạo 1 project binding với 1 file .aar cơ bản
    1) Add new project Binding library(Android)
    2) Add file .aar vào folder Jar
    3) Set build action cho .AAR file là LibraryProjectZip
    4) Chọn target framework cho project lib nếu cần
    5) Build Bindings Library.
    Sau khi tạo xong và build thành công chúng ta sẽ add file đó vào project Xamarin của chúng ta muốn sử dụng.
    Vậy là chúng ta có thể call tới class chứa trong file .aar.
    Mình chỉ nói các bước thực hiện thôi, chi tiết các bạn nên tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/binding-an-aar

    Như ở trên là cách tạo và add 1 project binding library với file source code trong file aar là đơn giản.
    Với những file .aar phức tạp và chúng có link đến nhau thì sao ?

    2. Tạo project binding nâng cao
    1. Đầu tiên các bạn vẫn phải thực hiện các bước như trên. Vì xamarin có 1 nhược điểm là 1 file aar bắt buộc bạn cần phải tạo ra 1 project binding
    2. Cần phải xác định file aar nào sử dụng file aar nào.
    Ví dụ chúng ta có 2 file aar, file thứ nhất call đến 1 class trong file thứ 2
    Như vậy làm thế nào để biết được file nào dùng đến file nào
    – Bạn cần 1 tool để decode java trong file aar (Android studio chẳng hạn)
    – Sau đó bạn mở những class trong file aar ra. Xem phần import của chúng sử dụng những class nào có chứa trong file aar khác không
    – Đôi khi trong file aar nó sẽ sử dụng dependence đến 1 lib nào mà k chứa trong tất cả file aar của bạn. Thì bạn cần xem nó là những lib nào.
    Click chuột phải vào Packages của Project đó chọn Add packages. Rồi tìm xem tên lib đó có trong nuget không.
    Nếu không có, thì bạn có thể tìm và tải file jar của lib ấy về rồi copy vào folder Jar. Build action bạn để là EmbeddedJar
    Bạn thử build xem còn lỗi gì không.
    Nếu còn lỗi. Bạn cần phải customizing binding.
    3.Customizing Binding
    Với android chúng ta chỉ cần customize lại file Metadata.xml Transform File.
    Trong file Metadata.xml thì sẽ có 3 element :
    – add-node
    – attr
    – remove-node

    Mình cũng làm 1 vài project liên quan đến binding library. Và cũng customize file metadata.xml này, mình thấy như sau

    khi thấy error bạn chọn vào 1 item error

    Bạn có thể nhìn thấy dòng // Metadata.xml Xpath classs ….
    hãy coppy từ chỗ path=”…”
    Bạn có thể sử dụng remove-node trong file metadata.xml với lỗi ”do not exist in the namespace …’
    Thực ra bạn remove node này nó không ảnh hưởng gì đến code trong project cả. Việc generate class java sang c# ở đây chỉ là Android.Runtime.Register thôi.
    chứ không phải là nó generate all source code của cả class java sang c# .

    Một số trường hợp bạn không nhìn thấy class mình dùng được generate ra
    Trường hợp này chúng ta sẽ add bằng tay dùng add-node
    Ví dụ :

    <add-node path="/api/package[@name='your package name']">	
            <class abstract="false" deprecated="not deprecated" extends="java.lang.Object" 	
                extends-generic-aware="java.lang.Object" final="false" name=" your class name" static="true" visibility="public">	
            </class>	
    </add-node>	

    Một số trường hợp khác như tên classs trùng nhau, rồi đổi visibility của class … thì bạn dùng thằng att để thay đổi attribute của nó.
    Về chi tiết các bạn có thể tham khảo tại : https://docs.microsoft.com/en-us/xamarin/android/platform/binding-java-library/customizing-bindings/java-bindings-metadata
    Một vài lưu ý nữa là kể cả việc file aar bị mã hoá cũng không ảnh hưởng gì đến việc binding nhé. Vì những class a, b, c trong đó bạn trả bao giờ dùng đến cả.

    Bài viết của mình xin hết, nếu bạn nào dùng mà cảm thấy không hiểu thì contact với mình.

    Nếu các bạn cần binding với xamarin ios xem tại: https://magz.techover.io/2019/12/30/binding-objective-c-libraries-xamarin-ios/

    Thanks you!

  • Tổng quan về Mobile App

    Tổng quan về Mobile App

    Ghi chú:  Bài viết này chỉ là một góc nhìn chủ quan của tác giả về mảng mobile app. vì vậy có gì không đúng mọi người có thể đóng góp ở phần comment nhé!! Thank.

    Mở đầu:
    Ở thời điểm hiện tại việc xây dựng ứng dụng native không phải là lựa chọn duy nhất để tạo lên một một ứng dụng mobile app. Ngày nay chúng ta có thể dựa vào yêu cầu của khách hàng, các chức năng của sản phẩm để lựa chọn được hướng đi phù hợp hơn. Ta có thể dựa trên vào công nghệ web (HTML5, CSS3 và JavaScript) đang phát triển mạnh mẽ trên mobile. Hoặc tận hưởng những lợi ích của các công cụ phát triển đa nền tảng như React Native hoặc Flutter. Dưới đây, bạn sẽ tìm thấy chìa khóa để giải quyết vấn đề khó khăn này khi chọn phương pháp phát triển ứng dụng di động.

    Native App

    Native app hay còn được gọi là ứng dụng gốc. Vốn dĩ nó có cái tên này là bởi vì nó được viết bằng chính các ngôn ngữ lập trình gốc thần nhất dành riêng cho từng nền tảng cụ thể. Hai nền tảng di động phổ biến nhất hiện nay là Android và iOS (Windows Phone thì đã bị khai tử vào tháng 10/ 2017 ). Từ đó, các ngôn ngữ lập trình tương ứng được chính các công ty mẹ tạo ra phù hợp với từng nền tảng. Chẳng hạn như Apple đã có Swift, Objecive-C được dành cho lập trình ứng dụng trên nền tảng iOS. Lập trình trên Android thì dùng Java, mặc dù đây không phải ngôn ngữ do Google tạo ra.

    Viết Native App nghĩa là lập trình viên sẽ sử dụng IDE, SDK mà nhà sản xuất cung cấp để lập trình ra một ứng dụng, build ứng dụng đó thành file cài và gửi lên App Store để kiểm duyệt. Người dùng sẽ phải tìm ứng dụng trên App Store, tải về máy và chạy.  

    Với những hệ thống lớn, cần đồng bộ, ta vẫn phải viết phần back-end trên server. Server sẽ đưa ra một số API. Native app lấy dữ liệu về máy, truyền dữ liệu lên server thông qua các API này.

    Ưu điểm

    • Tận dụng được toàn bộ những tính năng của device: Chụp ảnh, nghiêng máy, rung, GPS, notification.
    • Có thể chạy được offline.
    • Performance rất nhanh vì code native sẽ được chạy trực tiếp.
    • UX phù hợp với từng nền tảng
    • Là lựa chọn duy nhất cho các ứng dụng game, xử lý hình ảnh hay video …

    Khuyết điểm

    • Cần cài đặt nặng nề (Android Studio, XCode, Android SDK, …), khó tiếp cận.
    • Với mỗi hệ điều hành, ta phải viết một ứng dụng riêng. Khó đảm bảo sự đồng bộ giữa các ứng dụng (1 button trên Android sẽ khác 1 button trên iOS, pop cũng khác).
    • Cần phải submit app lên App Store, mỗi lần update phải thông báo người dùng.
    • Code mệt và lâu hơn so với Mobile Web dẫn đến một khuyết điểm là chi phí phát triển cao.

    Kĩ năng cần có

    • Ngôn ngữ lập trình: Java / Kotlin cho Android, Objective-C / Swift cho iOS
    • Kiến thức chuyên sâu về ứng dụng: View, Action, Adapter trong Android …
    • Cách xây dựng Web Serivce, Restful API, cách gọi API từ device, …

    __________________________________________________________________________

    Hybrid App

    Hybrid App kết hợp những ưu điểm của Mobile Web và Native App. Ta xây dựng một ứng dụng bằng HTML, CSS, Javascript, chạy trên WebView của mobile. Tuy nhiên, Hybrid App vẫn có thể tận dụng những tính năng của device: chụp hình, GPS, rung, ….

    Hybrid App sẽ được viết dựa trên một cross-platform framework: Cordova, Phonegap, Ionic …. Ta sẽ gọi những chức năng của mobile thông qua API mà framework này cung cấp, dưới dạng Javascript. Bạn chỉ cần viết một lần, những framework này sẽ tự động dịch ứng dụng này ra các file cài đặt cho Android, iOS . Một số ứng dụng không quá nặng về xử lý, cần tận dụng chức năng của device sẽ chọn hướng phát triển này.

    Ưu điểm

    • Chỉ cần biết HTML, CSS, JS .
    • Viết một lần, chạy được trên nhiều hệ điều hành
    • Tận dụng được các chức năng của device.

    Khuyết điểm

    • Không ổn định, khó debug. Framework sẽ dịch code của bạn thành code native, việc sửa lỗi ứng dụng khá khó vì bạn không biết code sẽ được dịch ra như thế nào.
    • Performance chậm.
    • Cần cài đặt nhiều thứ (phải cài đặt SDK này nọ thì mới build ứng dụng được).

    Kiến thức cần biết

    • HTML, CSS, Javscript cơ bản.
    • Cách dùng một số framework CSS, Javascript: jQuery Mobile, Ionic Framework, AngularJS, Bootstrap, …
    • Kiến thức về các cross-platform framework: Cordova, Phonegap
    • Cách xây dựng Web Serivce, Restful API, cách gọi API từ device, … (Hybrid app cũng sẽ kết nối với server thông qua API như Native App).

    __________________________________________________________________________

    Cross-Platform App

    Được sinh ra nhằm mục đích để giải quyết bài toán hiệu năng của Hybrid và bài toán chi phí khi mà phải viết nhiều loại ngôn ngữ native cho từng nền tảng di động. Nhưng chúng ta lại hay nhầm lẫn giữa Hybrid AppCross-Platform App, trên thực tế thì chúng khác hoàn toàn nhau. Có lẽ, đặc điểm chung duy nhất giữa chúng là khả năng chia sẻ source code. Lập trình viên chỉ cần lập trình một lần và biên dịch hoặc phiên dịch ra thành nhiều bản Native App tương ứng với từng nền tảng khác nhau.

    Công cụ quan trọng nhất để thực hiện các dự án ứng dụng đa nền tảng (Cross Platform) chính là Frameworks đa nền tảng. Có rất nhiều Framework đa nền tảng. Mỗi loại sẽ có những điểm mạnh và điểm yếu khác nhau. Tùy vào mục tiêu xây dựng App mà lập trình viên sẽ lựa chọn Framework nào cho phù hợp.

    Nổi tiếng và phổ biến nhất là Framework Xamarin. Ngôn ngữ lập trình chủ đạo trong Xamarin là C#, ngoài ra còn có Objective-C, Swift và Java. Ngoài ra, còn một số cái tên mà khá hot đó là React-Native (thằng này có ông bô là Facebook ), Flutter (thằng này có ông bác là Google)…

    Ưu điểm

    • Tận dụng được những tính năng của device: Chụp ảnh, nghiêng máy, rung, GPS, notification.
    • Hiệu năng tương đối ổn định.
    • Tiết kiệm tiền.
    • Hiệu quả về mặt thời gian khi mà bạn muốn phát triển một ứng dụng nhanh chóng.
    • Trải nghiệm người dùng tốt hơn là hybrid app.

    Nhược điểm

    • Hiệu năng sẽ thấp hơn với app native code.
    • Khó học vẫn đòi hỏi kiến thức native code.
    • Vẫn còn có hạn chế từ framework

    Kĩ năng cần có

    • Kiến thức C# (đối với Xamarin ), JS (đối với React-Native ), Dart(đối với Flutter) Objective-C, Swift và Java cơ bản.
    • Kiến thức về một số framework React-Native, Xamarin …

    __________________________________________________________________________

    Web App

    Hướng Mobile Web thường được áp dụng khi các bạn đã có sẵn một website đang hoạt động. Ta sẽ tạo thêm 1 trang web riêng cho mobile, sử dụng HTML, CSS, một số framework hỗ trợ mobile và responsive (Bootstrap, jQuery Mobile, Materialize). Người dùng sẽ trang web dành cho mobile để dùng ứng dụng.

    Các xử lý khác liên quan đến backend như database sẽ được thực hiện phía trên server. Với một số framework như Angular, VueJS … một trang web có thể giống y hệt một ứng dụng di động thật sự.

    Ưu điểm

    • Chỉ cần có kiến thức về web là viết được
    • Viết một lần, chạy được trên mọi hệ điều hành
    • Người dùng không cần phải cài app, có thể vào thẳng trang web
    • Không cần phải thông qua App Store, tiết kiệm tiền
    • Dễ nâng cấp (Chỉ việc nâng cấp web là xong)

    Nhược điểm

    • Với một số máy đời cũ, Web App sẽ bị bể giao diện, hiển thị sai, hoặc javascript không chạy.
    • Performance chậm
    • Không thể tận dụng được các tính năng của di động: Push notification, chụp hình, nghiêng máy, định vị GPS…

    Kĩ năng cần có

    • Kiến thức HTML, CSS, Javascript cơ bản.
    • Kiến thức về một số framework responsive/mobile như: jQuery Mobile, Bootstrap, …
    • Một số framework javascript để viết Single Page Application: AngularJS, VueJS, …

    Kết Bài

    Sorry các bạn bài viết hơi dài, sau khi nhìn tổng quan về mobile app thì các bạn đã chọn cho mình hướng đi nào chưa? còn mình thì sẽ tiếp tục theo hướng Cross-Platform app.
    Cảm ơn các bạn đã đọc đến đây nhé.

    Tham khảo: https://railsware.com/blog/native-vs-hybrid-vs-cross-platform/

  • Media Player (Part1) – RTSP Player Android

    Media Player (Part1) – RTSP Player Android

    Có nhiều cách để play RTSP trên Android, ứng cử viên hàng đầu là VideoView sẵn có trên Android SDK. Tuy nhiên Video View có rất nhiều hạn chế khi chúng ta cần custom lại, ví dụ chỉnh sửa thêm thắt vào protocol,  add các hiệu ứng hình ảnh vào video khi đang play, record, chuyển đổi các track..etc. Khi đó chúng ta phải lựa chọn một giải pháp khác, cụ thể ở đây mình đang nói đến

    1. Exoplayer
    2. IJKPlayer

    Exoplayer (https://github.com/google/ExoPlayer) open source, apache licence, java core, rất dễ dàng để anh em lập trình android/java tiếp xúc nhưng điểm trừ của nó là độ trễ – low latency

    Khi test với 1 số server RTSP, ExoPlayer cho ra độ trễ vào khoảng 1.2s đến 2.5s tùy chất lượng video out put. Còn IJKPlayer(https://github.com/bilibili/ijkplayer) thì có thể đạt 0.6s đến 0.8s.  Điểm trừ của IJKPlayer là licence, IJKPlayer sử dụng FFMPEG, một thư viện rất nổi tiếng, nên nếu ko muốn dính dáng đến pháp lý khi bán sản phẩm, bạn nên cẩn thận khi lựa chọn nó cho khách hàng hoặc nhúng vào sản phẩm.

    Để build được IJKPlayer cũng khá đơn giản(nếu bạn chuẩn bị đủ môi trường), hướng dẫn này mình viết chạy trên môi trường MAC OS nhé, Window hoặc Ubuntu cách làm tương tự, tuy nhiên môi trường chuẩn bị sẽ khác đi 1 chút

    Check out source code

    Các bạn có thể lấy source IJK từ nhánh chính ở trên hoặc từ repo bên dưới, repo này mình đã add thêm một vài  chỉnh sửa liên quan đến RTSP và Recorder

    Option 1: https://github.com/baka3k/IjkPlayerRecorder(có recorder)

    Option 2: https://github.com/bilibili/ijkplayer(nguyên bản)

    Cài đặt NDK cho Mac: mình test thực tế thì thấy build trên bản NDK 10, NDK16(khá cũ) thì ok nhất, ko cần sửa lỗi và chỉnh lại config build, tất nhiên các bạn pro hơn có thể thử với version NDK khác

    https://dl.google.com/android/repository/android-ndk-r10e-darwin-x86_64.zip

    Giải nén NDK, chỉnh lại enviroment path về thư mục NDK vừa download

    Với window thì System>Advanced System setting>Enviroment variable, giống khi các bạn add path cho adb hoặc sdk

    Với Mac OS thì sửa file  ~/.bash_profile hoặc ~/.zshrc hoặc ~/.profile – tùy thuộc vào bạn đang dùng command build nào nhé 🙂

    # export ANDROID_SDK=<your sdk path> # export ANDROID_NDK=<your ndk path>

    Buid nào:

    Kiểm tra kết nối internet(khi build cần tải 1 số package về, nên tốt nhất là full mạng ko qua proxy gì cả)

    Install home brew, git, yasm (chạy lần lượt các lệnh sau theo step nhé)

    # install homebrew, git, yasm
    ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
    brew install git
    brew install yasm

     Build Android

    git clone https://github.com/baka3k/IjkPlayerRecorder.git
    cd IjkPlayerRecorder
    ./init-android.sh
    
    cd android/contrib
    ./compile-ffmpeg.sh clean
    ./compile-ffmpeg.sh all
    
    cd ..
    ./compile-ijk.sh all

    Sau khi build xong các bạn có thể tìm thấy file .so trong thư mục

    - /IjkPlayerRecorder/android/ijkplayer/ijkplayer-armv7a/src/main/libs/armeabi-v7a
    - /IjkPlayerRecorder/android/ijkplayer/ijkplayer-armv5/src/main/libs/armeabi
    - /IjkPlayerRecorder/android/ijkplayer/ijkplayer-arm64/src/main/libs/arm64-v8a

    Và có thể copy những file .so này vào thư mục sample sẵn có để chạy thử

    Sample đặt tại đường dẫn

    - IjkPlayerRecorder/android/ijkplayer/ijkplayer-example

    Lúc này nó như là 1 app android thông thường, đã có sẵn JNI để call xuống .so nhé 🙁

    Have fun 🙂