Dẫn nhập
Ở các bài học trước, chúng ta đã tìm hiểu CÁCH SỬ DỤNG CƠ BẢN ANDROID STUDIO , cách tạo và khởi chạy một project Android hoàn chỉnh. Tuy nhiên ác mộng của những ai làm lập trình hẳn các bạn cũng đã biết: BUG.
Trong bài học này, chúng ta cùng tìm hiểu về Cách gỡ lỗi cơ bản (debug) chương trình bằng Android Studio, cũng như một số trường hợp lỗi thường gặp.
Tổng quan
Cũng như nhiều IDE khác, Android Studio có cung cấp khả năng debug ứng dụng rất hiệu quả cho các ứng dụng chạy trên máy thật lẫn máy ảo. Bạn có thể:
- Chọn thiết bị để debug.
- Đặt các breakpoint (điểm dừng) trong code.
- Quan sát và kiểm tra các giá trị biến / biểu thức trong runtime.
- Chụp ảnh màn hình ứng dụng.
Nội dung
Để bắt đầu thực hành, chúng ta cần có một project cơ bản. Mời các bạn xem lại bài viết trước: CÁC CHỨC NĂNG CƠ BẢN CỦA ANDROID STUDIO để tạo ra project mẫu. Rất nhanh thôi!
Trường hợp 1: Ứng dụng của bạn chưa được chạy ở chế độ Debug:
Để bắt đầu debug, các bạn nhấn vào nút Debug trên thanh công cụ (khi trỏ vào sẽ có chữ “Debug ‘app’”).
Lúc này Android Studio sẽ build ứng dụng ra file APK, ký (sign) file APK bằng key debug, và cài đặt lên thiết bị của bạn. Cuối cùng, cửa sổ Debug sẽ được mở ra:
(Ảnh minh họa cửa sổ Debug, liệt kê các tác vụ chạy ở main thread và cây đối tượng của một biến).
Nếu như cửa sổ Select Deployment Target hiện lên nhưng không có thiết bị nào trong danh sách sau khi bạn nhấn nút Debug :
- Hãy kết nối thiết bị Android của bạn.
- Tham khảo lại bài viết trước:
- CÁC CHỨC NĂNG CƠ BẢN CỦA ANDROID STUDIO
- Xem cách bật chức năng cho phép debug ứng dụng trên thiết bị của bạn ở Bước 4: Activity đầu tiên.
Nếu thực hiện đúng các bước thì cửa sổ Select Deployment Target sẽ hiển thị danh sách thiết bị như sau:
Trường hợp 2: Ứng dụng của bạn đã chạy (trên máy ảo/máy thật):
Trong trường hợp này, bạn có thể bắt đầu Debug bằng các bước sau:
- Click vào nút Attach Debugger to Android Process:
- Cửa sổ Choose Process sẽ hiện lên. Lúc này bạn sẽ chọn process tương ứng để trình Debug của Android Studio theo dõi nó.
Mặc định thì trình Debugger hiển thị thiết bị và process của app ở project hiện tại, cùng với các thiết bị đã được chạy hoặc kết nối với máy tính đang làm việc.
Chọn Show all processes để hiển thị tất cả các thiết bị, các process service do app bạn đang làm tạo ra, hoặc các tiến trình của thiết bị tạo ra (nếu nó đã được root).
Ở lựa chọn Debugger, bạn chon Auto hoặc Java. Nếu project của bạn có chứa code C++ và/hoặc code JavaScript (làm app Hybrid) thì sẽ có thêm 2 lựa chọn là Hybrid và Native. Lúc này chúng ta chỉ có Java mà thôi.
Sử dụng Log
Khác với Terminal của các ứng dụng Java thông thường. Với Android Studio chúng ta có một công cụ mới là LogCat. Về bản chất của chúng thì như nhau, nhưng LogCat cho phép người khác viết plugin để hoạt động linh hoạt hơn, và nó là một công cụ tách rời trong Android SDK. Phần này chúng ta cùng tìm hiểu cách sử dụng LogCat, không đề cập đến việc viết plugin.
Để sử dụng LogCat, thay vì viết System.out.println() như code Java thường thì bạn thay bằng các hàm của lớp Log.
Log giúp chúng ta hiểu được thứ tự khởi chạy của code bằng cách thu nhặt dữ liệu của hệ thống trong khi chúng ta chạy app, và cho chúng ta biết được ứng dụng lỗi ở điểm nào.
Bước 1: Đặt Log trong code
Quay trở lại với ví dụ mà chúng ta đang thao tác (HelloWorld). Chúng ta đã có một class có tên MainActivity.java với nội dung như sau:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
Sửa lại một chút như sau:
public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Log.e(TAG, “savedInstanceState is null”); } else { Log.e(TAG, “savedInstanceState is not null”); } setContentView(R.layout.activity_main); } }
Các bạn sẽ thấy chữ Log bị bôi đỏ và có tooltip android.util.Log? Alt+Enter hiện ra như hình dưới. Bạn chưa import class Log vào nên nó hiển thị vậy thôi. Nhấn Alt+Enter là Log sẽ được tự động import vào class MainActivity:
Bước 2: Đọc Log
Và bây giờ, chạy app (chạy – Run hoặc Debug đều được, vì chúng ta chỉ cần xem Log thôi mà):
Chạy xong, các bạn xem phần (1) Android Monitorvà để ý đến dòng bôi đậm (2) như hình:
Vậy tức là tham số savedInstanceState là null.
Các bạn có để ý rằng: Ở đầu code trước hàm onCreate() mình có đặt một biến TAG như sau:
public static final String TAG = MainActivity.class.getSimpleName();
Và sau đó gọi biến TAG này trong phần Log.e():
Log.e(TAG, “savedInstanceState is null”);
Đặt biến TAG ở đầu mỗi class như thế này rất hữu ích. Nó sẽ giúp các bạn biết được Log mình đang xem nằm ở class nào. Vì sau này khi thực hiện những dự án lớn, những app phức tạp, sẽ có rất nhiều class / interface được tạo ra.
Cả 2 cửa sổ Android DDMS (Dalvik Debug Monitor Server) và Android Monitor (hình mà chúng ta vừa xem ở ngay bên trên) đều cung cấp Log cho chúng ta trong quá trình app chạy.
Để chạy công cụ Android DDMS, các bạn click vào menu Tools > Android > Android Device Monitor:
Và theo dõi cửa sổ LogCat, các bạn cũng thấy được dòng log như trong Android Monitor đã đề cập ở đầu Bước 2: Đọc Log.
Làm việc với Breakpoint
Android Studio có cung cấp cơ chế đặt điểm dừng (Break Point) để khởi chạy một thao tác gỡ lỗi ứng dụng nào đó. Trong đó phổ biến nhất là đặt breakpoint để dừng ứng dụng tạm thời khi một dòng code nào đó được gọi tới.
Sau khi app được dừng (pause), các bạn có thể kiểm tra thông số các biến, kiểm tra biểu thức, hoặc chạy tiếp code từng dòng một để tìm ra điểm lỗi trong code.
Để đặt Breakpoint trong code, các bạn làm như sau:
- Tìm dòng code muốn đặt Breakpoint, click chuột trái vào khoảng trống ở cột trái trước dòng code. Hoặc đặt nháy con trỏ chuột ở dòng code muốn đặt Breakpoint và bấm tổ hợp phím Ctrl+F8 (hoặc Command+F8 với máy Mac)
- Nếu app đang chạy, bạn không cần sửa thêm gì mà chỉ cần nhấn nút Attach Debugger to Android process
. Còn nếu chưa chạy thì chạy app ở chế độ debug bằng nút
Khi code chạy đến đúng điểm dừng đã đặt, Android Studio sẽ dừng ứng dụng lại (màn hình sẽ bị dừng lại – đóng băng, không thể thao tác được gì thêm).
- Để xem cây object cho một biến, quan sát vùng Variables và mở các mũi tên của nó.
- Để phân tích biểu thức tại điểm đặt, nhấn nút Evaluate Expression
- Để nhảy đến dòng code tiếp theo (mà không vào đầu phương thức), nhấn nút Step Over hoặc nút F6 trên bàn phím.
- Để nhảy vào dòng đầu tiên trong một phương thức, nhấn Step Into
hoặc phím F5.
- Để chạy code lại như bình thường, nhấn nút Resume Program
hoặc phím F8.
Ví dụ: Tiêu diệt NullPointerException
Về cơ bản thì debug trên Android là như vậy. Bây giờ chúng ta hãy thử làm một ví dụ nhỏ: Tự tạo một bug trong chính ứng dụng của mình và debug nó. Chúng ta sẽ đặt một nút Button khai báo nhưng chưa khởi tạo (nghĩa là nó sẽ null), sau đó đặt text cho Button này.
Tất nhiên là ứng dụng sẽ báo lỗi và đóng app lại ngay, lỗi ở đây chính là NullPointerException (biến trả về giá trị rỗng) – một lỗi rất phổ biến và cũng khiến nhiều bạn mới làm quen với lập trình ngán ngẩm.
Nhưng đừng sợ hãi! NullPointerException thật ra lại chính là lỗi dễ xử lý nhất.
Và sau đây Kteamsẽ chứng minh cho các bạn:
Phần 1: Tạo bug
- Vẫn lấy code HelloWorld ở bài trước làm ví dụ. chúng ta vẫn chỉ có một class là java với nội dung sau khi chỉnh sửa ở phầnSử dụng Log – Bước 1 như sau:
public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Log.e(TAG, “savedInstanceState is null”); } else { Log.e(TAG, “savedInstanceState is not null”); } setContentView(R.layout.activity_main); } }
- Chúng ta khai báo một biến Button ngay phía dưới biến TAG ở trên, và đặt null như sau:
public static final String TAG = MainActivity.class.getSimpleName(); Button button;
- Cuối cùng, chúng ta đặt chữ cho Button đó như sau:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Log.e(TAG, “savedInstanceState is null”); } else { Log.e(TAG, “savedInstanceState is not null”); } setContentView(R.layout.activity_main); button.setText(“Hello Kteam”); }
- Toàn bộ code của class java lúc này có nội dung đầy đủ như sau:
package com.howkteam.helloworld; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.Button; public class MainActivity extends AppCompatActivity { public static final String TAG = MainActivity.class.getSimpleName(); Button button; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { Log.e(TAG, “savedInstanceState is null”); } else { Log.e(TAG, “savedInstanceState is not null”); } setContentView(R.layout.activity_main); button.setText(“Hello K-Team”); } }
- Chạy thử app dưới chế độ debug bằng cách nhấn nút Debug và…
App của chúng ta crash ngay lập tức. Đừng lo, đây là một vụ án có sắp đặt từ trước. Thủ phạm chính là chúng ta, vậy hãy tìm cách xóa sạch chứng cứ.
Phần 2: Gỡ bug
- Ngay sau khi ứng dụng lỗi thì việc ta làm đầu tiên luôn là mở Android Monitor ra kiểm tra, và lúc này chúng ta có như sau:
Vâng! một tràng dài những dòng Log được xuất ra. Nhưng hãy để ý phần được bôi đậm, nó có kèm link đến dòng code lỗi (ở đây làMainActivity.javadòng số 24). Nguyên nhân lỗi được khoanh ở khung đỏ, và không gì khác ngoài NullPointerException.
- Nào, cùng xóa tang chứng vật chứng. Dòng số 24 ở đây chính là dòng:
button.setText(“Hello K-Team”);
Lý do rất đơn giản: Chúng ta đã khai báo một Button ở đầu class nhưng chưa khởi tạo cho nó. Một Button cần nhận đầu vào ở constructor là một Context (hoặc Activity). Bản thân MainActivity.java đã là một Activity nên chỉ cần từ khóa this là đủ. Chúng ta làm như sau:
button = new Button(this);button.setText(“Hello K-Team”);
- Chạy lại app, và lỗi đã biến mất.
Tuy nhiên cái nút không hiển thị trên màn hình vì layout chưa được chỉ định “thêm” nút này vào. Vấn đề này sẽ được giải quyết ở bài sau: CÁC THÀNH PHẦN GIAO DIỆN (UI VIEWS) CƠ BẢN, DRAWABLES VÀ XML.
Kết luận
Qua bài này chúng ta đã nắm được cách debug chương trình, biết về DDMS, Android Monitor, Android Debugger và các phím tắt để thực hiện debug chương trình nhanh chóng. Các bạn cũng đã được làm thử một ví dụ thực tế: Debug một lỗi rất phổ biến trong lập trình Android là NullPointerException.
Bài sau chúng ta sẽ tìm hiểu về CÁC THÀNH PHẦN GIAO DIỆN (UI) CƠ BẢN
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ÌNH LUẬ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.