Android - làm cách nào để điều tra ANR?


152

Có cách nào để tìm ra nơi ứng dụng của tôi đã ném ANR (Ứng dụng không phản hồi). Tôi đã xem tệp traces.txt trong / data và tôi thấy một dấu vết cho ứng dụng của mình. Đây là những gì tôi thấy trong dấu vết.

DALVIK THREADS:
"main" prio=5 tid=3 TIMED_WAIT
  | group="main" sCount=1 dsCount=0 s=0 obj=0x400143a8
  | sysTid=691 nice=0 sched=0/0 handle=-1091117924
  at java.lang.Object.wait(Native Method)
  - waiting on <0x1cd570> (a android.os.MessageQueue)
  at java.lang.Object.wait(Object.java:195)
  at android.os.MessageQueue.next(MessageQueue.java:144)
  at android.os.Looper.loop(Looper.java:110)
  at android.app.ActivityThread.main(ActivityThread.java:3742)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
  at dalvik.system.NativeStart.main(Native Method)

"Binder Thread #3" prio=5 tid=15 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x434e7758
  | sysTid=734 nice=0 sched=0/0 handle=1733632
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #2" prio=5 tid=13 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433af808
  | sysTid=696 nice=0 sched=0/0 handle=1369840
  at dalvik.system.NativeStart.run(Native Method)

"Binder Thread #1" prio=5 tid=11 NATIVE
  | group="main" sCount=1 dsCount=0 s=0 obj=0x433aca10
  | sysTid=695 nice=0 sched=0/0 handle=1367448
  at dalvik.system.NativeStart.run(Native Method)

"JDWP" daemon prio=5 tid=9 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x433ac2a0
  | sysTid=694 nice=0 sched=0/0 handle=1367136
  at dalvik.system.NativeStart.run(Native Method)

"Signal Catcher" daemon prio=5 tid=7 RUNNABLE
  | group="system" sCount=0 dsCount=0 s=0 obj=0x433ac1e8
  | sysTid=693 nice=0 sched=0/0 handle=1366712
  at dalvik.system.NativeStart.run(Native Method)

"HeapWorker" daemon prio=5 tid=5 VMWAIT
  | group="system" sCount=1 dsCount=0 s=0 obj=0x4253ef88
  | sysTid=692 nice=0 sched=0/0 handle=1366472
  at dalvik.system.NativeStart.run(Native Method)

----- end 691 -----

Làm thế nào tôi có thể tìm ra vấn đề ở đâu? Các phương thức trong theo dõi là tất cả các phương thức SDK.

Cảm ơn.


2
Tôi có một báo cáo loại này, cũng xảy ra tại android.os.MessageQueue.nativePollOnce(Native Method). Tôi có thể yên tâm bỏ qua nó?
rds

Câu trả lời:


124

Một ANR xảy ra khi một số hoạt động dài diễn ra trong luồng "chính". Đây là chuỗi vòng lặp sự kiện và nếu bận, Android không thể xử lý bất kỳ sự kiện GUI nào nữa trong ứng dụng và do đó sẽ xuất hiện hộp thoại ANR.

Bây giờ, trong dấu vết bạn đã đăng, chủ đề chính dường như đang hoạt động tốt, không có vấn đề gì. Nó đang không hoạt động trong MessageQueue, đang chờ một tin nhắn khác đến. Trong trường hợp của bạn, ANR có thể hoạt động lâu hơn, chứ không phải là thứ đã chặn luồng vĩnh viễn, do đó, chuỗi sự kiện đã phục hồi sau khi hoạt động kết thúc và dấu vết của bạn đã đi qua sau ANR.

Phát hiện nơi ANR xảy ra là dễ dàng nếu đó là một khối vĩnh viễn (ví dụ như bế tắc có được một số khóa), nhưng khó hơn nếu đó chỉ là một sự chậm trễ tạm thời. Đầu tiên, đi qua mã của bạn và tìm kiếm các điểm có thể thay đổi và các hoạt động chạy dài. Ví dụ có thể bao gồm sử dụng ổ cắm, khóa, ngủ luồng và các hoạt động chặn khác từ trong chuỗi sự kiện. Bạn nên chắc chắn rằng tất cả những điều này xảy ra trong các chủ đề riêng biệt. Nếu không có gì có vẻ là vấn đề, sử dụng DDMS và kích hoạt chế độ xem luồng. Điều này cho thấy tất cả các chủ đề trong ứng dụng của bạn tương tự như dấu vết bạn có. Tái tạo ANR và làm mới luồng chính cùng một lúc. Điều đó sẽ cho bạn thấy chính xác những gì đang diễn ra tại thời điểm ANR


6
vấn đề duy nhất là "tái tạo ANR" :-). bạn có thể giải thích làm thế nào mà dấu vết ngăn xếp cho thấy chủ đề chính là 'không hoạt động', điều đó sẽ rất tuyệt.
Blundell

20
Theo dõi ngăn xếp cho thấy luồng chính nằm trong Looper (thực hiện vòng lặp thông báo) và thực hiện chờ đợi theo thời gian thông qua Object.wait. Điều này có nghĩa là các vòng lặp tin nhắn hiện không có bất kỳ tin nhắn nào để gửi và đang chờ tin nhắn mới đến. ANR xảy ra khi hệ thống nhận ra một vòng lặp tin nhắn đang dành nhiều thời gian để xử lý tin nhắn và không xử lý các tin nhắn khác trong xếp hàng. Nếu các vòng lặp đang chờ tin nhắn, rõ ràng điều này không xảy ra.
Sooniln

3
Tài khoản ngoài ra nó có thông tin như VMWAIT, RUNNABLE, NATIVE
minhaz

1
Ứng dụng của tôi dựa trên NDK, tôi thấy ANR tương tự. Ngoài ra, chủ đề chính là tốt. Tôi đã thử DDMS và làm mới luồng công nhân của tôi khi nó đóng băng. Thật không may, tất cả những gì tôi nhận được là một dòng duy nhất NativeStart :: run. Là xem chủ đề DDMS thậm chí có khả năng kiểm tra các chủ đề NDK bản địa? Ngoài ra: StrictMode không tìm thấy gì.
Bram

6
Xem elliotth.blogspot.com/2012/08/ Khăn để được giải thích tốt về đầu ra.
Sooniln

96

Bạn có thể bật StrictMode trong API cấp 9 trở lên.

StrictMode được sử dụng phổ biến nhất để bắt truy cập mạng hoặc ổ đĩa tình cờ trên luồng chính của ứng dụng, nơi các hoạt động UI được nhận và hoạt ảnh diễn ra. Bằng cách giữ cho luồng chính của ứng dụng của bạn phản hồi, bạn cũng ngăn các hộp thoại ANR hiển thị cho người dùng.

public void onCreate() {
    StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                           .detectAll()
                           .penaltyLog()
                           .penaltyDeath()
                           .build());
    super.onCreate();
}

bằng cách sử dụng, penaltyLog()bạn có thể xem đầu ra của logb adb trong khi bạn sử dụng ứng dụng của mình để xem các vi phạm khi chúng xảy ra.


StrictMode không thể được giải quyết thành một loại. Có bất cứ điều gì tôi cần phải nhập trước? Nhấn CTRL + SHIFT + O không giúp ích.
kuchi

23
mẹo nhỏ - sử dụng if (BuildConfig.DEBUG) ... để ngăn việc đưa vào sản xuất
Amir Uval

@uval bạn có ý gì khi "ngăn chặn việc đưa vào sản xuất"? !!
Muhammed Refaat

2
@MuhammedRefaat nó không ngăn chặn bất kỳ ANR nào. Nó sẽ sập ứng dụng ngay lập tức thay vì sau 5 giây. Ví dụ: nếu bạn truy cập cơ sở dữ liệu trên luồng chính và mất 2 giây, bạn sẽ không nhận được ANR, nhưng StrictMode sẽ làm hỏng ứng dụng. StrictMode hoàn toàn dành cho giai đoạn gỡ lỗi của bạn chứ không phải sản xuất.
Amir Uval

1
@MuhammedRefaat đã thêm câu trả lời của tôi cho câu hỏi của bạn.
Amir Uval

80

Bạn đang tự hỏi nhiệm vụ nào giữ một UI Thread. Trace file cung cấp cho bạn một gợi ý để tìm nhiệm vụ. bạn cần điều tra trạng thái của từng luồng

Trạng thái của chủ đề

  • đang chạy - thực thi mã ứng dụng
  • đang ngủ - được gọi là Thread.s ngủ ()
  • màn hình - chờ đợi để có được một khóa màn hình
  • chờ đợi - trong Object.wait ()
  • mã gốc thực thi
  • vmwait - chờ đợi trên một tài nguyên VM
  • zombie - chủ đề đang trong quá trình chết
  • init - thread đang khởi tạo (bạn không nên thấy điều này)
  • bắt đầu - chủ đề sắp bắt đầu (bạn cũng không nên thấy điều này)

Tập trung vào trạng thái TẠM NGỪNG, MONITOR. Trạng thái giám sát cho biết luồng nào được điều tra và trạng thái TẠM NGỪNG của luồng có lẽ là lý do chính cho sự bế tắc.

Các bước điều tra cơ bản

  1. Tìm "chờ khóa"
    • bạn có thể tìm thấy trạng thái màn hình "Binder Thread # 15" Prio = 5 tid = 75 MONITOR
    • bạn thật may mắn nếu tìm thấy "chờ khóa"
    • ví dụ: chờ khóa <0xblahblah> (a com.foo.A) được giữ bởi threadid = 74
  2. Bạn có thể nhận thấy rằng "tid = 74" giữ một nhiệm vụ ngay bây giờ. Vì vậy, đi đến tid = 74
  3. tid = 74 có thể trạng thái TẠM NGỪNG! tìm lý do chính!

dấu vết không phải lúc nào cũng chứa "chờ khóa". trong trường hợp này thật khó để tìm ra lý do chính.


1
Giải thích tốt đẹp. Bây giờ tôi dễ dàng hiểu được nhật ký ANR hơn. Nhưng tôi vẫn có một vấn đề cần hiểu vì ở bước 1 tôi có thể dễ dàng tìm thấy id id nhưng khi ở bước 2, tôi đang cố gắng đi đến đâu, để kiểm tra trạng thái, tôi không thể tìm thấy nó . Bất kỳ ý tưởng làm thế nào để tiến hành với nó?
THZ

1
Tôi có - waiting to lock an unknown objectbên trong "HeapTaskDaemon" daemon prio=5 tid=8 Blocked . Có nghĩa là ai đó có thể giúp đỡ?
Hilal

13

Tôi đã học Android trong vài tháng qua, vì vậy tôi không phải là một chuyên gia, nhưng tôi thực sự thất vọng với tài liệu về ANR.

Hầu hết các lời khuyên dường như đều hướng đến việc tránh chúng hoặc sửa chúng bằng cách mù quáng xem mã của bạn, điều này thật tuyệt, nhưng tôi không thể tìm thấy bất cứ điều gì khi phân tích dấu vết.

Có ba điều bạn thực sự cần tìm với nhật ký ANR.

1) Bế tắc: Khi một luồng ở trạng thái WAIT, bạn có thể xem qua các chi tiết để tìm ra ai là "Holdby =". Hầu hết thời gian, nó sẽ bị giữ bởi chính nó, nhưng nếu nó bị giữ bởi một luồng khác, đó có thể là một dấu hiệu nguy hiểm. Hãy nhìn vào chủ đề đó và xem những gì nó được tổ chức. Bạn có thể tìm thấy một vòng lặp, đó là một dấu hiệu rõ ràng cho thấy có gì đó không ổn. Điều này khá hiếm, nhưng đó là điểm đầu tiên bởi vì khi nó xảy ra, đó là một cơn ác mộng

2) Chờ đợi luồng chính: Nếu luồng chính của bạn ở trạng thái WAIT, hãy kiểm tra xem nó có bị giữ bởi luồng khác không. Điều này không nên xảy ra, vì luồng UI của bạn không nên được giữ bởi một luồng nền.

Cả hai kịch bản này, có nghĩa là bạn cần làm lại mã của mình một cách đáng kể.

3) Các thao tác nặng trên luồng chính: Đây là nguyên nhân phổ biến nhất của ANR, nhưng đôi khi là một trong những khó khăn hơn để tìm và sửa. Nhìn vào các chi tiết chủ đề chính. Cuộn xuống dấu vết ngăn xếp và cho đến khi bạn thấy các lớp bạn nhận ra (từ ứng dụng của bạn). Nhìn vào các phương thức trong theo dõi và tìm hiểu xem bạn có đang thực hiện các cuộc gọi mạng, cuộc gọi db, v.v. ở những nơi này không.

Cuối cùng, và tôi xin lỗi vì đã cắm mã của mình một cách đáng xấu hổ, bạn có thể sử dụng trình phân tích nhật ký python mà tôi đã viết tại https://github.com/HarshEvilGeek/Android-Log-Analyzer Điều này sẽ đi qua các tệp nhật ký của bạn, mở tệp ANR, tìm tệp bế tắc, tìm chủ đề chính đang chờ, tìm ngoại lệ chưa được lưu trong nhật ký đại lý của bạn và in tất cả ra trên màn hình theo cách tương đối dễ đọc. Đọc tệp ReadMe (mà tôi sắp thêm) để tìm hiểu cách sử dụng nó. Nó đã giúp tôi rất nhiều trong tuần qua!


4

Bất cứ khi nào bạn phân tích các vấn đề về thời gian, việc gỡ lỗi thường không giúp ích gì, vì việc đóng băng ứng dụng tại một điểm dừng sẽ khiến vấn đề biến mất.

Đặt cược tốt nhất của bạn là chèn nhiều cuộc gọi đăng nhập (Log.XXX ()) vào các luồng và cuộc gọi lại khác nhau của ứng dụng và xem độ trễ ở đâu. Nếu bạn cần một stacktrace, hãy tạo một Ngoại lệ mới (chỉ cần khởi tạo một cái) và đăng nhập nó.


2
Cảm ơn lời khuyên về việc tạo một ngoại lệ mới nếu bạn cần một stacktrace. Điều đó rất hữu ích khi gỡ lỗi :)
kuchi

3

Những gì kích hoạt ANR?

Nói chung, hệ thống hiển thị ANR nếu ứng dụng không thể đáp ứng đầu vào của người dùng.

Trong mọi tình huống trong đó ứng dụng của bạn thực hiện một hoạt động có khả năng kéo dài, bạn không nên thực hiện công việc trên luồng UI mà thay vào đó hãy tạo một luồng công nhân và thực hiện hầu hết các công việc ở đó. Điều này giữ cho luồng UI (điều khiển vòng lặp sự kiện giao diện người dùng) chạy và ngăn hệ thống kết luận rằng mã của bạn đã bị đóng băng.

Làm thế nào để tránh ANR

Các ứng dụng Android thường chạy hoàn toàn trên một luồng theo mặc định là "luồng UI" hoặc "luồng chính"). Điều này có nghĩa là bất cứ điều gì ứng dụng của bạn đang thực hiện trong luồng UI mất nhiều thời gian để hoàn thành có thể kích hoạt hộp thoại ANR vì ứng dụng của bạn không có cơ hội xử lý sự kiện đầu vào hoặc phát sóng ý định.

Do đó, bất kỳ phương thức nào chạy trong luồng UI nên thực hiện càng ít công việc càng tốt trên luồng đó. Cụ thể, các hoạt động nên thực hiện ít nhất có thể để thiết lập trong các phương thức vòng đời chính như onCreate () và onResume (). Các hoạt động có thể chạy dài như hoạt động mạng hoặc cơ sở dữ liệu hoặc các phép tính đắt tiền được tính toán như thay đổi kích thước bitmap nên được thực hiện trong luồng công nhân (hoặc trong trường hợp hoạt động của cơ sở dữ liệu, thông qua yêu cầu không đồng bộ).

Mã: Chuỗi công nhân với lớp AsyncTask

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
    // Do the long-running work in here
    protected Long doInBackground(URL... urls) {
        int count = urls.length;
        long totalSize = 0;
        for (int i = 0; i < count; i++) {
            totalSize += Downloader.downloadFile(urls[i]);
            publishProgress((int) ((i / (float) count) * 100));
            // Escape early if cancel() is called
            if (isCancelled()) break;
        }
        return totalSize;
    }

    // This is called each time you call publishProgress()
    protected void onProgressUpdate(Integer... progress) {
        setProgressPercent(progress[0]);
    }

    // This is called when doInBackground() is finished
    protected void onPostExecute(Long result) {
        showNotification("Downloaded " + result + " bytes");
    }
}

Mã: Thực thi chủ đề công nhân

Để thực thi luồng worker này, chỉ cần tạo một thể hiện và gọi exec ():

new DownloadFilesTask().execute(url1, url2, url3);

Nguồn

http://developer.android.com/training/articles/perf-anr.html


1

vấn đề của tôi với ANR, sau nhiều công việc tôi phát hiện ra rằng một luồng đang gọi một tài nguyên không tồn tại trong bố cục, thay vì trả lại một ngoại lệ, tôi đã nhận được ANR ...


điều đó cực kỳ kỳ lạ
Nilabja


0

Cơ bản trên câu trả lời của @Horyun Lee, tôi đã viết một kịch bản python nhỏ để giúp điều tra ANR từ đó traces.txt.

ANR sẽ xuất ra dưới dạng đồ họa graphviznếu bạn đã cài đặt grapvhviztrên hệ thống của mình.

$ ./anr.py --format png ./traces.txt

Một png sẽ xuất ra như dưới đây nếu có ANR được phát hiện trong tệp traces.txt. Nó trực quan hơn.

nhập mô tả hình ảnh ở đây

Các traces.txttập tin mẫu được sử dụng ở trên đã nhận được từ đây .


0

Cân nhắc sử dụng thư viện ANR-Watchdog để theo dõi và ghi lại chính xác dấu vết ngăn xếp ANR ở mức độ chi tiết cao. Sau đó, bạn có thể gửi chúng đến thư viện báo cáo sự cố của bạn. Tôi khuyên bạn nên sử dụng setReportMainThreadOnly()trong kịch bản này. Bạn có thể làm cho ứng dụng ném ngoại lệ không gây tử vong cho điểm đóng băng hoặc khiến lực lượng ứng dụng thoát khi ANR xảy ra.

Lưu ý rằng các báo cáo ANR tiêu chuẩn được gửi đến bảng điều khiển Google Play Developer của bạn thường không đủ chính xác để xác định chính xác vấn đề. Đó là lý do tại sao cần có thư viện của bên thứ ba.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.