Ứng dụng Android hết bộ nhớ - đã thử mọi cách mà vẫn thua


87

Tôi đã dành 4 ngày trọn vẹn để thử mọi thứ có thể để tìm ra lỗi rò rỉ bộ nhớ trong một ứng dụng mà tôi đang phát triển, nhưng mọi thứ đã ngừng hoạt động từ lâu.

Ứng dụng tôi đang phát triển có tính chất xã hội, vì vậy hãy nghĩ đến các Hoạt động (P) và liệt kê các Hoạt động kèm theo dữ liệu - ví dụ như huy hiệu (B). Bạn có thể chuyển từ hồ sơ đến danh sách huy hiệu sang hồ sơ khác, đến danh sách khác, v.v.

Vì vậy, hãy tưởng tượng một luồng như thế này P1 -> B1 -> P2 -> B2 -> P3 -> B3, v.v. Để nhất quán, tôi đang tải hồ sơ và huy hiệu của cùng một người dùng, vì vậy mỗi trang P giống nhau và tương tự mỗi trang B.

Ý chính chung của vấn đề là: sau khi điều hướng một chút, tùy thuộc vào kích thước của mỗi trang, tôi nhận được một ngoại lệ hết bộ nhớ ở những nơi ngẫu nhiên - Bitmap, Chuỗi, v.v. - có vẻ như không nhất quán.

Sau khi làm tất cả mọi thứ có thể tưởng tượng để tìm ra lý do tại sao tôi cạn kiệt bộ nhớ, tôi không nghĩ ra gì cả. Điều tôi không hiểu là tại sao Android không giết P1, B1, v.v. nếu nó hết bộ nhớ khi tải và thay vào đó bị treo. Tôi mong đợi những hoạt động trước đó sẽ chết và hồi sinh nếu tôi quay lại chúng qua onCreate () và onRestoreInstanceState ().

Hãy để một mình điều này - ngay cả khi tôi làm P1 -> B1 -> Quay lại -> B1 -> Quay lại -> B1, tôi vẫn gặp sự cố. Điều này cho thấy một số loại rò rỉ bộ nhớ, nhưng ngay cả sau khi kết xuất hprof và sử dụng MAT và JProfiler, tôi không thể xác định nó.

Tôi đã vô hiệu hóa tải hình ảnh từ web (và tăng dữ liệu thử nghiệm được tải để bù đắp và làm cho quá trình thử nghiệm trở nên công bằng) và đảm bảo rằng bộ nhớ cache hình ảnh sử dụng SoftRefferences. Android thực sự cố gắng giải phóng một số SoftRefferences mà nó có, nhưng ngay trước khi nó bị hỏng bộ nhớ.

Các trang huy hiệu lấy dữ liệu từ web, tải nó vào một mảng EntityData từ BaseAdapter và cấp nó vào ListView (Tôi thực sự đang sử dụng MergeAdapter tuyệt vời của CommonsWare , nhưng trong hoạt động Badge này, thực sự chỉ có 1 bộ điều hợp, nhưng tôi muốn đề cập đến thực tế này theo cách nào đó).

Tôi đã xem qua mã và không thể tìm thấy bất kỳ thứ gì có thể bị rò rỉ. Tôi đã xóa và vô hiệu hóa mọi thứ tôi có thể tìm thấy và thậm chí System.gc () trái và phải nhưng ứng dụng vẫn bị treo.

Tôi vẫn không hiểu tại sao các hoạt động không hoạt động có trong ngăn xếp lại không được gặt hái và tôi thực sự muốn tìm ra điều đó.

Tại thời điểm này, tôi đang tìm kiếm bất kỳ gợi ý, lời khuyên, giải pháp ... bất cứ điều gì có thể giúp.

Cảm ơn bạn.


Vì vậy, nếu tôi đã mở 15 hoạt động trong 20 giây qua (người dùng đang trải qua rất nhanh), đó có thể là vấn đề? Tôi nên thêm dòng mã nào để xóa hoạt động sau khi nó đã hiển thị? Tôi nhận được một outOfMemorylỗi. Cảm ơn!
Ruchir Baronia

Câu trả lời:


109

Tôi vẫn không hiểu tại sao các hoạt động không hoạt động có trong ngăn xếp lại không được gặt hái và tôi thực sự muốn tìm ra điều đó.

Đây không phải là cách mọi thứ hoạt động. Việc quản lý bộ nhớ duy nhất mà vòng đời hoạt động tác động là toàn cầu bộ nhớ trên tất cả các quy trình, như Android quyết định rằng nó đang ở mức thấp vào bộ nhớ và vì vậy cần phải giết quá trình nền để có được một số trở lại.

Nếu ứng dụng của bạn đang ở chế độ nền trước bắt đầu ngày càng nhiều hoạt động, nó sẽ không bao giờ ở chế độ nền, vì vậy nó sẽ luôn đạt đến giới hạn bộ nhớ xử lý cục bộ trước khi hệ thống gần kết thúc quá trình của nó. (Và khi nó giết quá trình của nó, nó sẽ giết quá trình lưu trữ tất cả các hoạt động, bao gồm bất cứ thứ gì hiện đang ở phía trước.)

Vì vậy, tôi nghe có vẻ như vấn đề cơ bản của bạn là: bạn đang để quá nhiều hoạt động chạy cùng một lúc và / hoặc mỗi hoạt động đó đang sử dụng quá nhiều tài nguyên.

Bạn chỉ cần thiết kế lại điều hướng của mình để không phụ thuộc vào việc sắp xếp một số lượng tùy ý các hoạt động có khả năng nặng. Trừ khi bạn thực hiện một lượng lớn nội dung trong onStop () (chẳng hạn như gọi setContentView () để xóa phân cấp chế độ xem của hoạt động và xóa các biến của bất kỳ thứ gì khác mà nó có thể đang giữ), bạn sẽ chỉ dùng hết bộ nhớ.

Bạn có thể muốn xem xét sử dụng các API Fragment mới để thay thế ngăn xếp hoạt động tùy ý này bằng một hoạt động duy nhất quản lý chặt chẽ hơn bộ nhớ của nó. Ví dụ: nếu bạn sử dụng các cơ sở ngăn xếp phía sau của các phân đoạn, khi một phân đoạn nằm trên ngăn xếp phía sau và không còn hiển thị nữa, phương thức onDestroyView () của nó được gọi để loại bỏ hoàn toàn hệ thống phân cấp chế độ xem, giảm đáng kể dấu chân của nó.

Bây giờ, cho đến khi bạn gặp sự cố trong luồng mà bạn nhấn trở lại, đi đến một hoạt động, nhấn trở lại, chuyển đến hoạt động khác, v.v. và không bao giờ có một ngăn xếp sâu, thì có, bạn chỉ có một rò rỉ. Bài đăng trên blog này mô tả cách gỡ lỗi rò rỉ: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
Trong sự bảo vệ của OP, đây không phải là những gì tài liệu cho thấy. Trích dẫn developer.android.com/guide/topics/fundamentals/… "(Khi một hoạt động bị dừng), nó không còn hiển thị với người dùng và nó có thể bị hệ thống giết khi cần bộ nhớ ở nơi khác." "Nếu một hoạt động bị tạm dừng hoặc bị dừng, hệ thống có thể xóa hoạt động đó khỏi bộ nhớ ... bằng cách yêu cầu nó kết thúc (gọi phương thức finish ())" "(onDestroy () được gọi) bởi vì hệ thống đang tạm thời hủy phiên bản này của hoạt động để tiết kiệm không gian. "
CommonsWare

42
Trên trang tương tự, "Tuy nhiên, khi hệ thống phá hủy một hoạt động để khôi phục bộ nhớ". Những gì bạn đang chỉ ra là Android không bao giờ phá hủy các hoạt động để lấy lại bộ nhớ, mà sẽ chỉ chấm dứt quá trình để làm như vậy. Nếu vậy, trang này cần được viết lại nghiêm túc, vì nó liên tục gợi ý rằng Android sẽ phá hủy một hoạt động để lấy lại bộ nhớ. Cũng lưu ý rằng nhiều đoạn trích dẫn cũng tồn tại trong ActivityJavaDocs.
CommonsWare

6
Tôi nghĩ rằng tôi đã đặt cái này ở đây, nhưng tôi đã đăng yêu cầu cập nhật tài liệu cho cái này: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller 27/02/12

15
Đó là năm 2013 và các tài liệu chỉ trình bày điểm sai rõ ràng hơn: developer.android.com/training/basics/activity-lifecycle/… , "Sau khi hoạt động của bạn bị dừng, hệ thống có thể phá hủy phiên bản nếu nó cần khôi phục bộ nhớ hệ thống. Trong trường hợp EXTREME, hệ thống có thể chỉ giết quá trình ứng dụng của bạn "
kaay 14/02

2
Chà, tào lao. Tôi chắc chắn luôn nghĩ (vì tài liệu) rằng hệ thống quản lý các hoạt động đã dừng trong quy trình của bạn và sẽ tuần tự hóa và giải mã hóa (hủy và tạo) chúng khi cần thiết.
dcow

21

Một số lời khuyên:

  1. Đảm bảo rằng bạn không bị rò rỉ bối cảnh hoạt động.

  2. Đảm bảo rằng bạn không giữ các tham chiếu trên bitmap. Làm sạch tất cả ImageView của bạn trong Activity # onStop, giống như sau:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Tái chế ảnh bitmap nếu bạn không cần chúng nữa.

  4. Nếu bạn sử dụng bộ nhớ đệm, như memory-lru, hãy đảm bảo rằng nó không sử dụng nhiều bộ nhớ.

  5. Không chỉ hình ảnh chiếm nhiều bộ nhớ, hãy đảm bảo rằng bạn không giữ quá nhiều dữ liệu khác trong bộ nhớ. Điều này có thể dễ dàng xảy ra nếu bạn có danh sách vô hạn trong ứng dụng của mình. Cố gắng lưu dữ liệu vào bộ nhớ cache trong DataBase.

  6. Trên Android 4.2, có một lỗi (stackoverflow # 13754876) với khả năng tăng tốc phần cứng, vì vậy nếu bạn sử dụng hardwareAccelerated=truetrong tệp kê khai của mình, nó sẽ làm rò rỉ bộ nhớ. GLES20DisplayList- tiếp tục giữ các tham chiếu, ngay cả khi bạn đã thực hiện bước (2) và không ai khác đang tham chiếu đến bitmap này. Ở đây bạn cần:

    a) vô hiệu hóa tăng tốc phần cứng cho api 16/17;
    hoặc
    b) tách chế độ xem đang giữ bitmap

  7. Đối với Android 3+, bạn có thể thử sử dụng android:largeHeap="true"trong của mình AndroidManifest. Nhưng nó sẽ không giải quyết các vấn đề về trí nhớ của bạn, chỉ cần trì hoãn chúng.

  8. Nếu bạn cần, chẳng hạn như điều hướng vô hạn, thì Fragment - sẽ là lựa chọn của bạn. Vì vậy, bạn sẽ có 1 hoạt động, sẽ chỉ chuyển đổi giữa các phân đoạn. Bằng cách này, bạn cũng sẽ giải quyết được một số vấn đề về bộ nhớ, như số 4.

  9. Sử dụng Trình phân tích bộ nhớ để tìm ra nguyên nhân khiến bộ nhớ của bạn bị rò rỉ.
    Đây là video rất hay từ Google I / O 2011: Quản lý bộ nhớ cho Ứng dụng Android
    Nếu bạn xử lý bitmap, đây là điều cần phải đọc: Hiển thị Bitmap một cách hiệu quả


Vì vậy, nếu tôi đã mở 15 hoạt động trong 20 giây qua (người dùng đang thực hiện rất nhanh), đó có thể là vấn đề? Tôi nên thêm dòng mã nào để xóa hoạt động sau khi nó đã hiển thị? Tôi nhận được một outOfMemorylỗi. Cảm ơn!
Ruchir Baronia

4

Bitmap thường là thủ phạm gây ra lỗi bộ nhớ trên Android, vì vậy đó sẽ là một khu vực tốt để kiểm tra lại.


Vì vậy, nếu tôi đã mở 15 hoạt động trong 20 giây qua (người dùng đang thực hiện rất nhanh), đó có thể là vấn đề? Tôi nên thêm dòng mã nào để xóa hoạt động sau khi nó đã hiển thị? Tôi nhận được một outOfMemorylỗi. Cảm ơn!
Ruchir Baronia

2

Bạn có đang nắm giữ một số tham chiếu đến từng Hoạt động không? AFAIK đây là một lý do ngăn Android xóa các hoạt động khỏi ngăn xếp.

Chúng tôi bạn cũng có thể tạo lại lỗi này trên các thiết bị khác? Tôi đã gặp phải một số hành vi kỳ lạ của một số thiết bị Android tùy thuộc vào ROM và / hoặc nhà sản xuất phần cứng.


Tôi đã có thể tái tạo điều này trên Droid chạy CM7 với đống tối đa được đặt thành 16MB, đây cũng là giá trị mà tôi đang thử nghiệm trên trình giả lập.
Artem Russakovskii

Bạn có thể đang ở trên một cái gì đó. Khi hoạt động thứ 2 bắt đầu, hoạt động thứ nhất sẽ thực hiện onPause-> onStop hay chỉ onPause? Bởi vì tôi đang in tất cả các cuộc gọi trong vòng đời ******* và tôi đang thấy onPause -> onCreate, mà không có onStop. Và một trong những bãi chứa sự cố thực sự đã nói một cái gì đó như onPause = true hoặc onStop = false cho 3 hoạt động giống như nó đang giết.
Artem Russakovskii

OnStop sẽ được gọi khi hoạt động rời khỏi màn hình nhưng có thể không xảy ra nếu nó được hệ thống thu hồi sớm.
dten

Nó không được xác nhận quyền sở hữu vì tôi không thấy onCreate và onRestoreInstanceState được gọi nếu tôi nhấp lại.
Artem Russakovskii

theo vòng đời, những thứ đó có thể không bao giờ được gọi, hãy kiểm tra câu trả lời của tôi có liên kết đến blog chính thức của nhà phát triển, nhiều khả năng đó là cách bạn chuyển qua các bitmap của mình
vào

2

Tôi nghĩ rằng vấn đề có thể là sự kết hợp của nhiều yếu tố được nêu ở đây trong câu trả lời là những gì đang mang lại cho bạn vấn đề. Giống như @Tim đã nói, tham chiếu (tĩnh) đến một hoạt động hoặc một phần tử trong hoạt động đó có thể khiến GC bỏ qua Hoạt động. Đây là bài viết thảo luận về khía cạnh này. Tôi nghĩ rằng vấn đề có khả năng xuất phát từ điều gì đó giữ Hoạt động ở trạng thái "Quy trình hiển thị" hoặc cao hơn, điều này sẽ đảm bảo khá nhiều rằng Hoạt động và các tài nguyên liên quan của nó không bao giờ bị lấy lại.

Tôi đã gặp phải vấn đề ngược lại một thời gian với Dịch vụ, vì vậy đó là điều khiến tôi nghĩ đến điều này: có điều gì đó giữ Hoạt động của bạn ở vị trí cao trong danh sách ưu tiên của quy trình để nó không bị GC hệ thống, chẳng hạn như tham chiếu (@Tim) hoặc vòng lặp (@Alvaro). Vòng lặp không cần phải là một mục dài hoặc dài vô tận, chỉ cần một cái gì đó chạy rất nhiều như một phương thức đệ quy hoặc vòng lặp xếp tầng (hoặc một cái gì đó dọc theo những dòng đó).

CHỈNH SỬA: Theo tôi hiểu điều này, onPause và onStop được Android tự động gọi khi cần. Các phương pháp ở đó chủ yếu để bạn ghi đè để bạn có thể xử lý những gì bạn cần trước khi quá trình lưu trữ bị dừng (lưu biến, trạng thái lưu thủ công, v.v.); nhưng lưu ý rằng đã nêu rõ rằng onStop (cùng với onDestroy) có thể không được gọi trong mọi trường hợp. Ngoài ra, nếu quá trình lưu trữ cũng đang lưu trữ Hoạt động, Dịch vụ, v.v. có trạng thái "Ẩn" hoặc "Hiển thị", thì Hệ điều hành thậm chí có thể không xem xét việc dừng quá trình / chuỗi. Ví dụ: cả Hoạt động và Dịch vụ đều được xử lý trong cùng một quy trình và Dịch vụ trả về START_STICKYtừonStartCommand()quá trình tự động có ít nhất một trạng thái hiển thị. Đó có thể là chìa khóa ở đây, hãy thử khai báo một proc mới cho Activity và xem liệu điều đó có thay đổi gì không. Hãy thử thêm dòng này vào phần khai báo Hoạt động của bạn trong Tệp kê khai dưới dạng: android:process=":proc2"rồi chạy lại các bài kiểm tra nếu Hoạt động của bạn chia sẻ quy trình với bất kỳ thứ gì khác. Ý nghĩ ở đây là nếu bạn đã dọn dẹp Hoạt động của mình và khá chắc chắn rằng vấn đề không phải là Hoạt động của bạn thì vấn đề khác là vấn đề và thời gian để tìm kiếm điều đó.

Ngoài ra, tôi không thể nhớ mình đã xem nó ở đâu (nếu tôi thậm chí đã thấy nó trong tài liệu Android) nhưng tôi nhớ điều gì đó về việc PendingIntenttham chiếu đến một Hoạt động có thể khiến một Hoạt động hoạt động theo cách này.

Đây là một liên kết cho onStartCommand()trang với một số thông tin chi tiết về quy trình không giết người.


Dựa trên liên kết bạn đã cung cấp (cảm ơn), một hoạt động có thể bị hủy nếu onStop của nó được gọi, nhưng trong trường hợp của tôi, tôi nghĩ có điều gì đó đang ngăn onStop chạy. Tôi chắc chắn sẽ xem xét lý do tại sao. Tuy nhiên, nó cũng cho biết các hoạt động chỉ có onPause cũng có thể bị giết, điều này không xảy ra trong trường hợp của tôi (tôi thấy onPause được gọi nhưng không onStop).
Artem Russakovskii

1

vì vậy điều duy nhất tôi thực sự có thể nghĩ đến là nếu bạn có một biến tĩnh tham chiếu trực tiếp hoặc gián tiếp đến ngữ cảnh. Thậm chí một cái gì đó rất nhiều như một tham chiếu đến một phần của ứng dụng. Tôi chắc rằng bạn đã thử nó nhưng tôi sẽ đề xuất nó chỉ trong trường hợp, hãy thử hủy bỏ TẤT CẢ các biến tĩnh của bạn trong onDestroy () chỉ để đảm bảo rằng bộ thu gom rác nhận được nó


1

Nguồn rò rỉ bộ nhớ lớn nhất mà tôi tìm thấy là do một số tham chiếu toàn cầu, cấp cao hoặc lâu dài đối với ngữ cảnh. Nếu bạn đang lưu trữ "ngữ cảnh" trong một biến ở bất kỳ đâu, bạn có thể gặp phải tình trạng rò rỉ bộ nhớ không thể đoán trước.


Vâng, tôi đã nghe và đọc điều này nhiều lần, nhưng tôi gặp khó khăn khi ghim lại. Giá như tôi có thể tìm ra cách đáng tin cậy để theo dõi chúng.
Artem Russakovskii

1
Trong trường hợp của tôi, điều này được dịch thành bất kỳ biến nào ở cấp lớp được đặt bằng ngữ cảnh (ví dụ: Class1.variable = getContext ();). Nói chung, việc thay thế mọi cách sử dụng "ngữ cảnh" trong ứng dụng của tôi bằng một lệnh gọi mới tới "getContext" hoặc tương tự đã giải quyết được vấn đề bộ nhớ lớn nhất của tôi. Nhưng của tôi không ổn định và thất thường, không thể đoán trước được như trường hợp của bạn, vì vậy có thể là một cái gì đó khác.
D2TheC

1

Hãy thử truyền getApplicationContext () cho bất kỳ thứ gì cần Context. Bạn có thể có một biến toàn cục đang giữ một tham chiếu đến Hoạt động của bạn và ngăn chúng được thu thập rác.


1

Một trong những điều thực sự giúp ích cho vấn đề bộ nhớ trong trường hợp của tôi là thiết lập InPurnable thành true cho Bitmap của tôi. Xem Tại sao tôi KHÔNG bao giờ sử dụng tùy chọn inPurnable của BitmapFactory? và cuộc thảo luận của câu trả lời để biết thêm thông tin.

Câu trả lời của Dianne Hackborn và cuộc thảo luận sau đó của chúng tôi (cũng cảm ơn, CommonsWare) đã giúp làm rõ một số điều tôi đã nhầm lẫn, vì vậy cảm ơn bạn vì điều đó.


Tôi biết đây là một chủ đề cũ nhưng tôi thấy dường như tìm thấy chủ đề này trên nhiều tìm kiếm. Tôi muốn liên kết một hướng dẫn tuyệt vời, tôi học được rất nhiều từ đó bạn có thể tìm thấy ở đây: developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

Tôi gặp phải vấn đề tương tự với bạn. Tôi đang làm việc trên một ứng dụng nhắn tin tức thì, đối với cùng một số liên lạc, có thể khởi động ProfileActivity trong ChatActivity và ngược lại. Tôi chỉ thêm một chuỗi bổ sung vào mục đích để bắt đầu một hoạt động khác, nó lấy thông tin về loại lớp của hoạt động khởi đầu và id người dùng. Ví dụ: ProfileActivity bắt đầu một ChatActivity, sau đó trong ChatActivity.onCreate, tôi đánh dấu loại lớp vô hiệu là 'ProfileActivity' và id người dùng, nếu nó bắt đầu một Hoạt động, tôi sẽ kiểm tra xem nó có phải là 'ProfileActivity' cho người dùng hay không . Nếu vậy, chỉ cần gọi 'finish ()' và quay lại ProfileActivity cũ thay vì tạo một cái mới. Rò rỉ bộ nhớ là một điều khác.

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.