Được rồi. Tôi đã trải qua địa ngục và trở lại vấn đề này. Đây là cách tiến hành. Có lỗi. Bài đăng này mô tả cách phân tích lỗi trong quá trình triển khai và giải quyết các vấn đề.
Tóm lại, đây là cách mọi thứ hoạt động. Các dịch vụ đang chạy sẽ được quét và chấm dứt định kỳ sau mỗi 30 phút hoặc lâu hơn. Các dịch vụ muốn tồn tại lâu hơn thời gian này phải gọi Service.startForeground, nơi đặt thông báo trên thanh thông báo, để người dùng biết rằng dịch vụ của bạn đang hoạt động vĩnh viễn và có khả năng ngốn pin. Chỉ có 3 quy trình dịch vụ có thể tự đề cử làm dịch vụ nền tại bất kỳ thời điểm nào. Nếu có nhiều hơn ba dịch vụ nền trước, Android sẽ chỉ định dịch vụ cũ nhất làm ứng cử viên để thu thập và chấm dứt.
Thật không may, có những lỗi trong Android liên quan đến việc ưu tiên các dịch vụ nền trước, được kích hoạt bởi nhiều tổ hợp cờ ràng buộc dịch vụ khác nhau. Mặc dù bạn đã chỉ định chính xác dịch vụ của mình làm dịch vụ nền, Android vẫn có thể chấm dứt dịch vụ của bạn, nếu bất kỳ kết nối nào với các dịch vụ trong quy trình của bạn đã từng được thực hiện với một số kết hợp cờ ràng buộc nhất định. Thông tin chi tiết được đưa ra bên dưới.
Lưu ý rằng rất ít dịch vụ cần phải là dịch vụ nền trước. Nói chung, bạn chỉ cần là một dịch vụ nền nếu bạn có một kết nối internet hoạt động liên tục hoặc lâu dài thuộc một loại nào đó mà người dùng có thể bật và tắt hoặc hủy. Ví dụ về các dịch vụ cần trạng thái nền: máy chủ UPNP, tải xuống lâu dài các tệp rất lớn, đồng bộ hóa hệ thống tệp bằng wi-fi và phát nhạc.
Nếu bạn chỉ thỉnh thoảng thăm dò ý kiến, hoặc chờ đợi trên bộ thu phát sóng của hệ thống hoặc các sự kiện hệ thống, tốt hơn hết bạn nên đánh thức dịch vụ của mình trên bộ hẹn giờ hoặc phản hồi với bộ thu phát sóng và sau đó để dịch vụ của bạn chết sau khi hoàn tất. Đó là hành vi như được thiết kế cho các dịch vụ. Nếu bạn chỉ đơn giản là phải sống sót, thì hãy đọc tiếp.
Sau khi chọn các hộp trên các yêu cầu đã biết (ví dụ: gọi Service.startForeground), nơi tiếp theo cần xem xét là các cờ bạn sử dụng trong các lệnh gọi Context.bindService. Các cờ được sử dụng để ràng buộc ảnh hưởng đến mức độ ưu tiên của quy trình dịch vụ mục tiêu theo nhiều cách không mong muốn. Đặc biệt nhất, việc sử dụng một số cờ ràng buộc nhất định có thể khiến Android hạ cấp sai dịch vụ nền của bạn xuống dịch vụ thông thường. Mã được sử dụng để chỉ định mức độ ưu tiên của quy trình đã bị xáo trộn khá nhiều. Đáng chú ý, có những bản sửa đổi trong API 14+ có thể gây ra lỗi khi sử dụng cờ ràng buộc cũ hơn; và có một số lỗi nhất định trong 4.2.1.
Bạn của bạn trong tất cả những điều này là tiện ích sysdump, có thể được sử dụng để tìm ra mức độ ưu tiên mà Người quản lý hoạt động đã chỉ định cho quy trình dịch vụ của bạn và phát hiện các trường hợp nó đã chỉ định mức độ ưu tiên không chính xác. Thiết lập và chạy dịch vụ của bạn, sau đó đưa ra lệnh sau từ dấu nhắc lệnh trên máy tính chủ của bạn:
adb shell kết xuất các quy trình hoạt động> tmp.txt
Sử dụng notepad (không phải wordpad / write) để kiểm tra nội dung.
Trước tiên, hãy xác minh rằng bạn đã quản lý thành công để chạy dịch vụ của mình ở trạng thái nền trước. Phần đầu tiên của tệp kết xuất chứa mô tả về các thuộc tính ActivityManager cho mỗi quy trình. Tìm một dòng như sau tương ứng với ứng dụng của bạn trong phần đầu tiên của tệp kết xuất:
APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}
Xác minh rằng foregroundServices = true trong phần sau. Đừng lo lắng về các cài đặt ẩn và trống; chúng mô tả trạng thái của các Hoạt động trong quy trình và dường như không liên quan đặc biệt đến các quy trình có dịch vụ trong đó. Nếu foregroundService không đúng, bạn cần gọi Service.startForeground để biến nó thành true.
Điều tiếp theo bạn cần xem là phần gần cuối tệp có tiêu đề "Process LRU list (sắp xếp theo oom_adj):". Các mục trong danh sách này cho phép bạn xác định xem Android có thực sự phân loại ứng dụng của bạn là dịch vụ nền hay không. Nếu quy trình của bạn ở cuối danh sách này, thì đó là một ứng cử viên chính cho việc tiêu diệt tóm tắt. Nếu quy trình của bạn ở gần đầu danh sách, thì nó hầu như không thể phá hủy được.
Hãy xem một dòng trong bảng này:
Proc
Đây là một ví dụ về dịch vụ nền trước được thực hiện đúng mọi thứ. Trường quan trọng ở đây là trường "adj =". Điều đó cho biết mức độ ưu tiên mà quy trình của bạn đã được ActivityManagerService chỉ định sau khi mọi thứ đã được nói là xong. Bạn muốn nó là "adj = prcp" (dịch vụ nền trước có thể nhìn thấy); hoặc "adj = vis" (quy trình hiển thị với một hoạt động) hoặc "fore" (quy trình có hoạt động tiền cảnh). Nếu đó là "adj = svc" (quy trình dịch vụ) hoặc "adj = svcb" (dịch vụ kế thừa?) Hoặc "adj = bak" (quy trình nền trống), thì quy trình của bạn có khả năng bị chấm dứt và sẽ bị chấm dứt thường xuyên hơn 30 phút một lần ngay cả khi không có bất kỳ áp lực nào để lấy lại bộ nhớ. Các cờ còn lại trên dòng hầu hết là thông tin gỡ lỗi chẩn đoán cho các kỹ sư của Google. Quyết định chấm dứt được thực hiện dựa trên các trường điều chỉnh. Một cách ngắn gọn, / FS chỉ ra một dịch vụ nền trước; / FA chỉ ra một quá trình tiền cảnh với một hoạt động. / B cho biết một dịch vụ nền. Nhãn ở cuối cho biết quy tắc chung mà quy trình được chỉ định mức độ ưu tiên. Thường thì nó phải khớp với trường adj =; nhưng giá trị adj = có thể được điều chỉnh lên hoặc xuống trong một số trường hợp do các cờ ràng buộc trên các ràng buộc đang hoạt động với các dịch vụ hoặc hoạt động khác.
Nếu bạn đã vấp phải một lỗi với cờ ràng buộc, đường kết xuất sẽ trông như thế này:
Proc
Lưu ý rằng giá trị của trường adj được đặt không chính xác thành "adj = bak" (quy trình nền trống), dịch gần nghĩa là "vui lòng chấm dứt hoạt động của tôi ngay bây giờ để tôi có thể chấm dứt sự tồn tại vô nghĩa này" cho mục đích nhặt rác của quy trình. Cũng lưu ý cờ (fg-service) ở cuối dòng cho biết rằng "các quy tắc dịch vụ nền được sử dụng để xác định cài đặt" adj ". Mặc dù thực tế là các quy tắc fg-service đã được sử dụng, quy trình này được chỉ định một cài đặt điều chỉnh "bak", và nó sẽ không tồn tại được lâu. Nói một cách dễ hiểu, đây là một lỗi.
Vì vậy, mục tiêu là đảm bảo rằng quy trình của bạn luôn nhận được "adj = prcp" (hoặc tốt hơn). Và phương pháp để đạt được mục tiêu đó là điều chỉnh các cờ ràng buộc cho đến khi bạn quản lý để tránh các lỗi trong phân công ưu tiên.
Đây là những lỗi tôi biết. (1) Nếu BẤT KỲ dịch vụ hoặc hoạt động nào đã từng ràng buộc với dịch vụ bằng Context.BIND_ABOVE_CLIENT, bạn có nguy cơ cài đặt adj = sẽ bị hạ cấp xuống "bak" ngay cả khi ràng buộc đó không còn hoạt động nữa. Điều này đặc biệt đúng nếu bạn cũng có ràng buộc giữa các dịch vụ. Một lỗi rõ ràng trong các nguồn 4.2.1. (2) Chắc chắn không bao giờ sử dụng BIND_ABOVE_CLIENT để ràng buộc dịch vụ với dịch vụ. Cũng không sử dụng nó cho các kết nối hoạt động với dịch vụ. Cờ được sử dụng để triển khai hành vi BIND_ABOVE_CLIENT dường như được đặt trên cơ sở từng quy trình, thay vì cơ sở từng kết nối, do đó, nó gây ra lỗi với các ràng buộc giữa dịch vụ với dịch vụ ngay cả khi không có hoạt động nào đang hoạt động ràng buộc với bộ cờ. Dường như cũng có vấn đề với việc thiết lập mức độ ưu tiên khi có nhiều dịch vụ trong quá trình này, với các ràng buộc giữa dịch vụ với dịch vụ. Sử dụng Context.BIND_WAIVE_PRIORITY (API 14) trên các ràng buộc dịch vụ với dịch vụ dường như hữu ích. Context.BIND_IMPORTANT dường như là một ý tưởng tốt ít nhiều khi liên kết từ Hoạt động với một dịch vụ. Làm như vậy sẽ đẩy mức độ ưu tiên quy trình của bạn cao hơn một bậc khi Hoạt động ở nền trước, mà không gây tác hại rõ ràng nào khi Hoạt động bị tạm dừng hoặc kết thúc.
Nhưng nhìn chung, chiến lược là điều chỉnh các cờ bindService của bạn cho đến khi sysdump chỉ ra rằng quy trình của bạn đã nhận được ưu tiên chính xác.
Đối với mục đích của tôi, sử dụng Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT đối với ràng buộc Hoạt động với dịch vụ và Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY để ràng buộc dịch vụ với dịch vụ dường như làm đúng. Số dặm của bạn có thể khác.
Ứng dụng của tôi khá phức tạp: hai dịch vụ nền, mỗi dịch vụ có thể độc lập giữ trạng thái dịch vụ nền trước, cộng với một phần ba cũng có thể có trạng thái dịch vụ nền; hai trong số các dịch vụ ràng buộc với nhau theo điều kiện; cái thứ ba liên kết với cái đầu tiên, luôn luôn. Ngoài ra, các Activites chạy trong một quy trình riêng biệt (làm cho hoạt ảnh mượt mà hơn). Việc chạy các Hoạt động và Dịch vụ trong cùng một quy trình dường như không tạo ra bất kỳ sự khác biệt nào.
Có thể tìm thấy việc triển khai các quy tắc cho quy trình quét rác (và mã nguồn được sử dụng để tạo nội dung của tệp sysdump) trong tệp android lõi
frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.
Cơ hội tốt.
Tái bút: Đây là cách giải thích các chuỗi sysdump cho Android 5.0. Tôi chưa làm việc với họ, vì vậy hãy làm cho họ những gì bạn sẽ làm. Tôi tin rằng bạn muốn 4 là 'A' hoặc 'S', và 5 là "IF" hoặc "IB" và 1 là càng thấp càng tốt (có thể là dưới 3, vì chỉ có 3 ba quy trình dịch vụ nền trước được duy trì hoạt động trong cấu hình mặc định).
Example:
Proc # : prcp F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)
Format:
Proc # {1}: {2} {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}
1: Order in list: lower is less likely to get trimmed.
2: Not sure.
3:
B: Process.THREAD_GROUP_BG_NONINTERACTIVE
F: Process.THREAD_GROUP_DEFAULT
4:
A: Foreground Activity
S: Foreground Service
' ': Other.
5:
-1: procState = "N ";
ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
ActivityManager.PROCESS_STATE_TOP: procState = "T ";
ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
ActivityManager.PROCESS_STATE_HOME: procState = "HO";
ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";
{6}: trimMemoryLevel
{8} Process ID.
{9} process name
{10} appUid