Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Mọi ứng dụng đều cần có một file AndroidManifest.xml (file có cấu trúc XML) ở thư mục gốc của dự án. File mô tả này để cung cấp các thông tin cần thiết về ứng dụng cho hệ thống Android. Tóm tắt lại thì các thứ trong file mô tả Manifest này cần theo cấu trúc sau:

  • Chứa tên package dùng cho ứng dụng (tên package là duy nhất nhằm xác định đến package).
  • Mô tả các thành phần của ứng dụng: các Activity, Service, Broadcast Receiver, Content Provider.
  • Xác định các tiến trình chạy trong ứng dụng.
  • Khai báo các quyền truy cập hệ thống.

Cấu trúc cơ bản của file AndroidManifest.xml

File này có định dạng có thể gồm các thành phần chứa trong thẻ như sau:

<?xml version="1.0" encoding="utf-8"?> <manifest>     <uses-permission />     <permission />     <permission-tree />     <permission-group />     <instrumentation />     <uses-sdk />     <uses-configuration />       <uses-feature />       <supports-screens />       <compatible-screens />       <supports-gl-texture />       <application>         <activity>             <intent-filter>                 <action />                 <category />                 <data />             </intent-filter>             <meta-data />         </activity>         <activity-alias>             <intent-filter> . . . </intent-filter>             <meta-data />         </activity-alias>         <service>             <intent-filter> . . . </intent-filter>             <meta-data/>         </service>         <receiver>             <intent-filter> . . . </intent-filter>             <meta-data />         </receiver>         <provider>             <grant-uri-permission />             <meta-data />             <path-permission />         </provider>         <uses-library />     </application> </manifest>

Như vậy nó thường có các thẻ sau bên trong:

<action>, <activity>, <activity-alias>, <application>, <category>, <data>, <grant-uri-permission>, <instrumentation>, <intent-filter>, <manifest>, <meta-data>, <permission>, <permission-group>, <permission-tree>, <provider>, <receiver>, <service>, <supports-screens>, <uses-configuration>, <uses-feature>, <uses-library>, <uses-permission>, <uses-sdk> 

Dưới đây cập nhật về các thẻ hay dùng khi lập trình Android

Mô tả receiver

<receiver android:directBootAware=["true" | "false"] <!--Có nhận được Broadcast khi khóa máy hay không-->           android:enabled=["true" | "false"] <!--Có kích hoạt hay không-->           android:exported=["true" | "false"] <!--Có nhận được Broadcast từ thành phần bên ngoài ứng dụng hay không-->           android:icon="drawable resource"           android:label="string resource"           android:name="string"           android:permission="string"           android:process="string" >     . . . </receiver>

Thẻ receiver khai báo một BroadcastReceiver như là một thành phần của ứng dụng, cho phép ứng dụng nhận các chỉ thị phát đi bởi hệ thống, ứng dụng nhận được các chỉ thị này thậm chí khi các thành phần của ứng dụng đang không chạy.  Ngoài cách mô tả bằng Manifest như trên ứng dụng có thể khai báo nhận Broadcast trong code bằng cách gọi Context.registerReceiver(). Hết sức lưu ý về số lượng Broadcast cho ứng dụng vì có thế là nguyên nhân tới hao tổn pin của thiết bị.

android:name

Tên của lớp triển khai mã broadcast receiver, là một lớp kế thừa từ BroadcastReceiver được code trong ứng dụng của bạn. Ví dụ bạn xây dựng một lớp kế thừa từ BroadcastReceiver có tên Myreceiver trong package: com.example.myproject thì tên này sẽ là: com.example.myproject.Myreceiver. Trường này không có giá trị mặc định bắt buộc cần khai báo.

Receiver có thể chứa bên trong intent-filter, meta-data.

Mô tả intent-filter

<intent-filter android:icon="drawable resource"                android:label="string resource"                android:priority="integer" >     . . . </intent-filter>

Thể này mô tả kiểu intent (chỉ thị) mà một Activity, Service hay Broadcast Receiver nhận được.  Nội dung của intent-filter được diễn tả bởi các thẻ bên trong nó gồm thẻ: action, category, meta-data. Trong đó thẻ action bắt buộc phải có.

Mô tả action

<action android:name="string" />

Chỉ ra tên của Action của intent-filter. Các action tiểu chuẩn của hệ thống thường là một chuỗi với tên theo dạng: ACTION_string constants.  Tên này là của hệ thống hoặc do bạn tự định nghĩa khi gửi đi Broadcast trong ứng dụng. Nếu bạn tự định nghĩa tên này thì nên theo chuẩn com.example.project.YOURNAMEACTION.

Mô tả meta-data 

<meta-data android:name="string"            android:resource="resource specification"            android:value="string" />

Mô tả cặp dữ liệu TÊN-GIÁ TRỊ được cung cấp cho thẻ cha của meta-data.

  • android:name : tên của dữ liệu.
  • android:value: giá trị
  • android:resource : Chỉ ra tài nguyên (Resource) là giá trị gán cho dữ liệu.

Ở các bài CÁC THÀNH PHẦN GIAO DIỆN CƠ BẢN, chúng ta đã cùng nhau tìm hiểu về:

  • View.
  • ViewGroup.
  • Drawables.
  • Cách đổ View vào Activity.

Và ở bài học hôm nay, chúng ta sẽ cùng tìm hiểu về Intent và Manifest. Do tính chất của 2 khái niệm này dày đặc lý thuyết, ít hình vẽ (ít chứ không phải không có), mình sẽ cố gắng đưa ra nhiều hình minh họa nhất có thể. Bù lại, nội dung của bài khá ngắn, dễ đọc.

Nội dung

Để đọc hiểu bài này tốt nhất các bạn nên có kiến thức cơ bản về các phần:

  • Cấu trúc của một project Android.
  • Cách mở / import một project Android bằng Android Studio.

Trong bài học này, chúng ta sẽ cùng tìm hiểu các vấn đề:

  • Intent là gì? Chúng được sử dụng ra sao?
  • Manifest là gì? Những thành phần trong file Manifest?

Truyền thuyết về Intent

Ngày xửa ngày xưa, khi chế tạo ra Android, các nhà khoa học đã nghĩ về một hình thức truyền dữ liệu giữa các màn hình (Activity), tiến trình ngầm (Service) hay các Broadcast Receiver.

Nếu như trong lập trình iOS hay Windows Phone, chúng ta muốn nhập một ký tự bất kỳ ở màn hình thứ nhất, nhấn nút, sang màn hình thứ 2 hiển thị đúng ký tự đó thì:

  • Truyền dữ liệu thẳng vào URL dạng bbb://v=1&c=2
  • Rồi sau đó ở màn hình thứ 2, lấy dữ liệu ra với key bằng vc tương ứng.

Thì trong Android, mọi thứ hoàn toàn khác: Những dữ liệu được đưa vào một đối tượng thuộc lớp Bundle. Và đối tượng bundle này được chứa trong Intent.

Ví dụ: Ở một màn hình A, bạn kích hoạt chức năng chụp ảnh (tức là màn hình B), chụp xong bạn lấy ảnh về màn hình A. Lúc này Intent chính là:

  • Hành động kích hoạt chức năng chụp ảnh.
  • Hoạt động quay trở về màn hình A sau khi chụp.

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Như vậy, không chỉ là chuyển qua chuyển lại giữa các màn hình, Intent còn mang theo dữ liệu giữa các màn hình đó, khiến việc chuyển đổi trở nên đa nhiệm, linh hoạt hơn.

Sau đây là các trường hợp chúng ta có thể sử dụng Intent. Các trường hợp này trong quá trình làm việc bây giờ lẫn về sau, các bạn sẽ sử dụng rất thường xuyên:

Đơn giản là để khởi chạy một activity khác

Để chuyển sang một Activity B từ Activity A, các bạn gọi phương thức startActivity(intent) của Activity đó.

Quay trở lại với Project HelloWorld mà chúng ta đã làm từ bài 1 đến bài 4 trước đó. Lần này ta sẽ tạo một Activity mới và thực hành:

  • Tạo một Activity mới, rỗng, bằng cách chuột phải vào module “app” ở cột Project bên trái, chọn New > Activity > Empty Activity:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

  • Ta đặt tên cho nó là SecondActivity, và Android Studio sẽ tự động sinh ra file layout xml cho nó, sau khi hoàn thành bước này chúng ta sẽ có 2 file là activity_second.xmlSecondActivity.java:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

  • Để tiện phân biệt với MainActivity, chúng ta sẽ đặt màu cho SecondActivity là màu đen bằng cách thêm thuộc tính android:background trong file activity_second.xml như sau (phần tô vàng là phần thêm vào):
<RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"     xmlns:tools="http://schemas.android.com/tools"     android:id="@+id/activity_second"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:background="#000"     android:paddingBottom="@dimen/activity_vertical_margin"     android:paddingLeft="@dimen/activity_horizontal_margin"     android:paddingRight="@dimen/activity_horizontal_margin"     android:paddingTop="@dimen/activity_vertical_margin"     tools:context="com.howkteam.helloworld.SecondActivity"> </RelativeLayout>

Trong file activity_main.xml, xóa hết các thành phần bên trong FrameLayout, chỉ để lại một Button với code như sau:

activity_main.xml

<FrameLayout     xmlns:android="http://schemas.android.com/apk/res/android"     android:id="@+id/frame_layout"     android:layout_width="match_parent"     android:layout_height="match_parent">     <Button         android:id="@+id/btnClickMe"         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:text="Click me"/> </FrameLayout>

Và trong file Java tương ứng, gọi ra phương thức startActivity. Code đầy đủ như sau:

MainActivity.java

public class MainActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         Button testButton = (Button) findViewById(R.id.btnClickMe);         testButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View view) {                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);                 startActivity(intent);             }         });     } }
  • Vậy là chúng ta sẽ có được 2 activity:
    • Activity đầu sẽ có một nút Click me.

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

  • Khi nhấn vào nút Click me, app sẽ chuyển sang màn hình thứ 2:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Sử dụng Intent để khởi chạy Services

Services là một loại hoạt động ngầm trong Android. Để khởi chạy Services, tương tự như trên, ta gọi đến phương thức startService(Intent).

Service là một thành phần của ứng dụng, đại diện cho ứng dụng để làm một tác vụ dài hơi, mà không tương tác trực tiếp với người dùng (ví dụ như khi download một file dung lượng lớn từ internet). Service cũng có thể được dùng để hỗ trợ các ứng dụng khác.

Chi tiết về Service, chúng ta sẽ nói rõ hơn trong bài Service. Sau đây chúng ta sẽ tạo một Service bù nhìn để minh họa cho Intent nhé.

Bước 1: Tạo một class có tên ServiceExample và kế thừa lớp Service.

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Trình soạn thảo code sẽ báo lỗi, là do chúng ta chưa override hàm onBind của Service. Chúng ta sẽ sửa code thành:

package com.howkteam.helloworld; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; public class ExampleService extends Service {     @Nullable     @Override     public IBinder onBind(Intent intent) {         return null;     } }

Và thế là lỗi sẽ biến mất.

Bước 2: Service cũng có vòng đời gần như Activity, nên nó cũng có hàm onCreate để override. Chúng ta sẽ override hàm này để kiểm chứng sự tồn tại của Service:

package com.howkteam.helloworld; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; public class ExampleService extends Service {     @Nullable     @Override     public IBinder onBind(Intent intent) {         return null;     }     @Override     public void onCreate() {         super.onCreate();         Log.e("Kteam", "Service da duoc khoi tao");     } }

Do đặc thù của Service: Nó là thành phần chạy không có giao diện nên khi đóng app lại, Service vẫn còn nguyên gây lãng phí bộ nhớ không cần thiết. Do vậy ta sẽ hủy nó ngay khi được tạo, và ở công đoạn bị hủy (onDestroy), chúng ta đặt thêm một log nữa.

package com.howkteam.helloworld; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.support.annotation.Nullable; import android.util.Log; public class ExampleService extends Service {     @Nullable     @Override     public IBinder onBind(Intent intent) {         return null;     }     @Override     public void onCreate() {         super.onCreate();         Log.e("Kteam", "Service da duoc khoi tao");         this.stopSelf();     }     @Override     public void onDestroy() {         super.onDestroy();         Log.e("Kteam", "Service da duoc huy");     } }

Và cuối cùng, tạo Intent trong MainActivity và khởi chạy:

package com.howkteam.helloworld; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         Intent serviceIntent = new Intent(this, ExampleService.class);         startService(serviceIntent);     } }

Bước 3: Khai báo Service trong AndroidManifest, tương tự như Activity. Vì là Service đơn giản nên việc khai báo cũng vô cùng đơn giản:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"           package="com.howkteam.helloworld">     <application         android:allowBackup="true"         android:icon="@mipmap/ic_launcher"         android:label="@string/app_name"         android:supportsRtl="true"         android:theme="@style/AppTheme">         <activity android:name=".MainActivity">             <intent-filter>                 <action android:name="android.intent.action.MAIN"/>                 <category android:name="android.intent.category.LAUNCHER"/>             </intent-filter>         </activity>         <activity android:name=".SecondActivity">         </activity>         <service android:name=".ExampleService"/>     </application> </manifest>

Và cùng check kết quả, chú ý chọn đúng máy ảo và tiến trình như những vùng khoanh đỏ:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Tạo và gửi đi Intent dạng explicit hoặc implicit

Trước tiên chúng ta cần hiểu về 2 loại Intent trong Android:

  • Intent Explicit là Intent có đích đến rõ ràng (là các class khác), có thể bao gồm dữ liệu thêm (extra data). Ví dụ chúng ta vừa làm ở trên chính là một Explicit Intent.
Intent intent = new Intent(MainActivity.this, SecondActivity.class); intent.putExtra("Key", "Value"); startActivity(intent);
  • Intent Implicit thì hơi khác, đây là dạng Intent mà có thể có nhiều đích đến xử lý nó: Ví dụ khi muốn mở 1 URL thì các trình duyệt web của điện thoại đều có thể bắt được Intent này:
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://howkteam.com/")); startActivity(i);

Với Intent Explicit, chúng ta đã có ví dụ ở trên (Đơn giản chỉ là khởi chạy một Activity khác).

Với Intent Implicit, chúng ta làm một ví dụ nhỏ, cũng không cần sửa đổi gì nhiều, chỉ cần chỉnh code lại ở file MainActivity.java một chút:

package com.howkteam.helloworld; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         Button testButton = (Button) findViewById(R.id.btnClickMe);         testButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View view) {                 Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://howkteam.com/"));                 startActivity(i);             }         });     } }

Run app , và bấm nút Click me, chúng ta sẽ thấy:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Tại sao?

Ở trên mình đã truyền vào Intent một Uri (đường link) đến website của Howkteam. Và Intent ở đây chỉ định “View” (xem) cái Uri đó. Vì là địa chỉ web nên hiển nhiên là ta cần dùng trình duyệt.

Trong máy kiểm thử ở trên có 2 trình duyệt là Chrome Dev và trình duyệt mặc định, nên Android sẽ cho chúng ta chọn xem sử dụng cái nào để mở Uri.

Truyền dữ liệu giữa các Activity

Một Intent có thể chứa thêm dữ liệu phụ. Các dữ liệu này được chứa trong đối tượng của lớp Bundle, có thể lấy ra được bằng phương thức getExtras().

Các dữ liệu trong Bundle được lưu dạng giống như Map (key-value). Key luôn là String, còn value thuộc kiểu dữ liệu nguyên thủy (primitive types) hoặc có các kiểu String, Bundle, Parcelable, Serializable.

Phía bên Activity nhận sẽ lấy thông tin này ra bằng phương thức getAction() hoặc getData() của đối tượng Intent. Đối tượng Intent được lấy ra bằng phương thức getIntent().

Thành phần nhận dữ liệu sẽ gọi ra phương thức getIntent().getExtras() để lấy dữ liệu ra.

Ví dụ:

Bundle extras = getIntent().getExtras(); String data = extras.getString(Intent.EXTRA_TEXT);

Một activity có thể được đóng lại bằng cách nhấn nút Back trên điện thoại. Trong trường hợp này, phương thức finish() sẽ được gọi ra. Nếu activity được khởi tạo bằng phương thức startActivity(Intent) thì bên activity gọi sẽ không yêu cầu dữ liệu trả về.

Mặt khác, nếu như activity thứ hai được khởi tạo bằng phương thức startActivityForResult() thì dữ liệu có thể sẽ được trả về nhờ tham số resultCode đầu vào. Và để lấy dữ liệu ra, chúng ta override phương thức onActivityResult() của activity đang làm việc. Và tất nhiên phương thức này sẽ có tham số resultCode đầu vào để biết được acitivity nào đã gọi đến nó trước đó:

  • Activity thứ nhất sẽ khởi chạy activity thứ 2 và đặt cờ hiệu mong dữ liệu trả về:
Intent i = new Intent(MainActivity.this, SecondActivity.class); i.putExtra("Value1", "This value one for ActivityTwo "); i.putExtra("Value2", "This value two ActivityTwo"); int REQUEST_CODE = 9; startActivityForResult(i, REQUEST_CODE);
  • Activity thứ 2 lúc này được gọi là sub-activity. Phương thức setResult sẽ trả về dữ liệu mà acitivty thứ nhất mong muốn.
@Override public void finish() {     Intent data = new Intent();     data.putExtra("returnKey1", "Swinging on a star. ");     data.putExtra("returnKey2", "You could be better then you are. ");     setResult(RESULT_OK, data);     super.finish(); }

Code đầy đủ của SecondActivity.java sẽ là:

package com.howkteam.helloworld; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; public class SecondActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_second);         finish();     }     @Override     public void finish() {         Intent data = new Intent();         data.putExtra("returnKey1", "Gia tri tra ve thu nhat. ");         data.putExtra("returnKey2", "Gia tri tra ve thu hai. ");         setResult(RESULT_OK, data);         super.finish();     } }
  • Activity thứ 1 giờ sẽ lấy dữ liệu từ activity thứ 2 thông qua việc override phương thức onActivityResult.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {     if (resultCode == RESULT_OK && requestCode == 9) {         if (data.hasExtra("returnKey1")) {             Toast.makeText(this, data.getExtras().getString("returnKey1"),                     Toast.LENGTH_SHORT).show();         }     } }

Code đầy đủ của MainActivity.java

package com.howkteam.helloworld; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         Button testButton = (Button) findViewById(R.id.btnClickMe);         testButton.setOnClickListener(new View.OnClickListener() {             @Override             public void onClick(View view) {                 // Test khoi tao activity co du lieu tra ve                 Intent i = new Intent(MainActivity.this, SecondActivity.class);                 i.putExtra("Value1", "Gia tri thu nhat ");                 i.putExtra("Value2", "Gia tri thu hai ");                 int REQUEST_CODE = 9;                 startActivityForResult(i, REQUEST_CODE);             }         });     }     @Override     protected void onActivityResult(int requestCode, int resultCode, Intent data) {         if (resultCode == RESULT_OK && requestCode == 9) {             if (data.hasExtra("returnKey1")) {                 Toast.makeText(this, data.getExtras().getString("returnKey1"),                         Toast.LENGTH_SHORT).show();             }         }     } }

Do SecondActivity kết thúc rất nhanh (hàm finish được gọi ngay onCreate) nên chúng ta sẽ chỉ nhìn thấy thông báo như sau:

Ý nghĩa của tệp tin androidmanifest.xml trong ứng dụng android?

Manifest

Mọi ứng dụng đều bắt buộc phải có một file AndroidManifest.xml ở thư mục gốc. AndroidManifest cung cấp thông tin cơ bản của ứng dụng cho hệ điều hành Android ví dụ như:

  • Tên java package của ứng dụng.
  • Các thành phần của ứng dụng, như danh sách các thành phần của ứng dụng, như activity, services, broadcast receiver,…
  • Tính chất của các thành phần trong ứng dụng (ví dụ, một app có nhiều activity thì activity nào sẽ được gọi đầu tiên khi vào app).
  • Đánh giá các tiến trình chứa runtime của ứng dụng.
  • Danh sách các quyền truy cập của ứng dụng cần được người dùng cho phép.
  • Phiên bản Android tối thiểu.
  • Các thư viện liên kết với ứng dụng Android.

Một file AndroidManifest.xml sẽ có dạng cơ bản như sau, giả sử với ứng dụng HelloWorld đang làm:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"           package="com.howkteam.helloworld">     <application         android:allowBackup="true"         android:icon="@mipmap/ic_launcher"         android:label="@string/app_name"         android:supportsRtl="true"         android:theme="@style/AppTheme">         <activity android:name=".MainActivity">             <intent-filter>                 <action android:name="android.intent.action.MAIN"/>                 <category android:name="android.intent.category.LAUNCHER"/>             </intent-filter>         </activity>         <activity android:name=".SecondActivity">         </activity>     </application> </manifest>

Chúng ta dễ thấy: Ứng dụng có activity MainActivity là activity vào đầu tiên sau khi chạy app nhờ thuộc tính android:name = “android.intent.action.MAIN”. Và không có quyền truy cập gì đặc biệt vì không có thẻ <permission>

Kết luận

Qua bài này chúng ta đã nắm được cơ bản về Intent – luồng dữ liệu động giúp truyền thông tin giữa các màn hình và Manifest – file liệt kê các thành phần ứng dụng. Đây là 2 phần tối cơ bản của bất kỳ ứng dụng Android nào.

Bài sau chúng ta sẽ tìm hiểu về VÒNG ĐỜI CỦA MỘT ACTIVITY nhé. Sẽ có một câu đố nhỏ để giúp các bạn nắm chắc hơn (có lời giải, yên tâm).

Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”. 

Thảo luận

Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.