Ön plan hizmeti Android tarafından öldürülüyor


86

Güncelleme : Soruna gerçek bir çözüm bulamadım. Bulduğum şey, bağlantı her koptuğunda önceki bir bluetooth cihazına otomatik olarak yeniden bağlanmanın bir yöntemiydi. İdeal değil, ancak oldukça iyi çalışıyor gibi görünüyor. Bununla ilgili daha fazla öneri duymak isterim.

Bu sorudakiyle hemen hemen aynı sorunu yaşıyorum: Uyanık kilidi tutulurken ve cihaz dahil startForeground çağrıldıktan sonra servis öldürülüyor (Asus Transformer), servisin durdurulmasından önceki süre (30-45 dakika), wake lock, startForeground () kullanımı ve ekran kapandığında uygulama açıksa sorunun oluşmaması gerçeği.

Uygulamam başka bir cihazla bluetooth bağlantısı kuruyor ve ikisi arasında veri gönderiyor, bu nedenle verileri dinlemek için her zaman aktif olması gerekiyor. Kullanıcı servisi istediği zaman başlatıp durdurabilir ve aslında bu, servisi başlatmak veya durdurmak için uyguladığım tek yol. Hizmet yeniden başladığında, diğer cihazla olan bluetooth bağlantısı kesilir.

Bağlantılı sorudaki cevaba göre, startForeground () "bir hizmetin kesilme olasılığını azaltır, ancak engellemez". Durumun böyle olmasını anlıyorum, ancak bu soruna sahip olmayan diğer uygulamaların birçok örneğini gördüm (örneğin Tasker).

Hizmetin kullanıcı tarafından durduruluncaya kadar çalışabilme yeteneği olmadan uygulamamın kullanışlılığı büyük ölçüde azalacak. Bundan kaçınmamın bir yolu var mı???

Hizmet her durdurulduğunda bunu logcat'imde görüyorum:

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

DÜZENLEME: Ayrıca şunu da belirtmeliyim ki, bu bağlı olduğum diğer cihazda görülmüyor: HTC Legend, Cyanogen çalıştırıyor

DÜZENLEME: İşte şu çıktı adb shell dumpsys activity services:

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

Ve çıktı adb shell dumpsys activity:

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

Bunlar, hizmetin ön planda çalıştığını gösteriyor gibi görünüyor.


Bu cevaba bir göz atın - işinize yarayabilir stackoverflow.com/a/21157035/624109
Muzikant

Yanıtlar:


222

Okey dokey. Bu sorunu cehenneme çevirip geri döndüm. İşte nasıl devam edeceğiniz. Böcekler var. Bu gönderi, uygulamadaki hataların nasıl analiz edileceğini ve sorunların nasıl çözüleceğini açıklamaktadır.

Özetlemek gerekirse, işlerin nasıl yürümesi gerektiği burada. Çalışan hizmetler rutin olarak atılacak ve yaklaşık her 30 dakikada bir sonlandırılacaktır. Bundan daha uzun süre hayatta kalmak isteyen hizmetler Service.startForeground'u aramalıdır, bu da bildirim çubuğuna bir bildirim yerleştirir, böylece kullanıcılar hizmetinizin kalıcı olarak çalıştığını ve potansiyel olarak pil ömrünü emdiğini bilir. Herhangi bir zamanda yalnızca 3 hizmet süreci kendilerini ön plan hizmetleri olarak aday gösterebilir. Üçten fazla ön plan hizmeti varsa, Android en eski hizmeti toplama ve fesih için aday olarak gösterecektir.

Ne yazık ki, Android'de ön plan hizmetlerinin önceliklendirilmesiyle ilgili olarak çeşitli hizmet bağlama bayrakları kombinasyonları tarafından tetiklenen hatalar vardır. Hizmetinizi bir ön plan hizmeti olarak doğru bir şekilde aday göstermiş olsanız bile, sürecinizdeki hizmetlere herhangi bir bağlantı belirli bağlayıcı bayrak kombinasyonlarıyla yapılmışsa, Android hizmetinizi yine de sonlandırabilir. Detaylar aşağıda verilmiştir.

Çok az hizmetin ön plan hizmetleri olması gerektiğini unutmayın. Genel olarak, yalnızca açılıp kapatılabilen veya kullanıcılar tarafından iptal edilebilen bir tür sürekli aktif veya uzun süreli bir internet bağlantınız varsa, yalnızca bir ön plan hizmeti olmanız gerekir. Ön plan durumuna ihtiyaç duyan hizmetlere örnekler: UPNP sunucuları, çok büyük dosyaların uzun süreli indirmeleri, dosya sistemlerini wi-fi ile senkronize etme ve müzik çalma.

Sadece ara sıra yoklama yapıyorsanız veya sistem yayın alıcılarını veya sistem olaylarını bekliyorsanız, hizmetinizi bir zamanlayıcıda veya yayın alıcılarına yanıt olarak uyandırmanız ve ardından hizmetinizin tamamlandığında ölmesine izin vermeniz daha iyi olur. Hizmetler için tasarlanmış davranış budur. Hayatta kalmanız gerekiyorsa okumaya devam edin.

İyi bilinen gereksinimlerdeki kutuları işaretledikten sonra (örneğin, Service.startForeground'u çağırmak), bakılacak bir sonraki yer Context.bindService çağrılarında kullandığınız bayraklardır. Bağlamak için kullanılan bayraklar, hedef hizmet sürecinin önceliğini çeşitli beklenmedik şekillerde etkiler. Özellikle, belirli bağlama bayraklarının kullanılması, Android'in ön plan hizmetinizi yanlış bir şekilde normal bir hizmete düşürmesine neden olabilir. Proses önceliğini atamak için kullanılan kod oldukça yoğun bir şekilde karıştırıldı. Özellikle, API 14 + 'da eski bağlama bayraklarını kullanırken hatalara neden olabilecek revizyonlar vardır; ve 4.2.1'de kesin hatalar var.

Tüm bunlarda arkadaşınız, Etkinlik yöneticisinin hizmet sürecinize hangi önceliği atadığını anlamak ve yanlış bir öncelik atadığı durumları tespit etmek için kullanılabilecek sysdump yardımcı programıdır. Hizmetinizi çalıştırın ve çalıştırın ve ardından ana bilgisayarınızdaki komut isteminden aşağıdaki komutu verin:

adb shell dumpsys etkinlik işlemleri> tmp.txt

İçeriği incelemek için not defteri (wordpad / write değil) kullanın.

Öncelikle, hizmetinizi başarıyla ön planda çalıştırmayı başardığınızı doğrulayın. Dumpsys dosyasının ilk bölümü, her işlem için ActivityManager özelliklerinin bir açıklamasını içerir. Dumpsys dosyasının ilk bölümünde uygulamanıza karşılık gelen aşağıdakine benzer bir satır arayın:

APP UID 10068 ProcessRecord {41937d40 2205: tunein.service / u0a10068}

Aşağıdaki bölümde foregroundServices = true olduğunu doğrulayın. Gizli ve boş ayarlar konusunda endişelenmeyin; Süreçteki Faaliyetlerin durumunu açıklarlar ve içlerinde hizmetler bulunan süreçlerle özellikle alakalı görünmüyorlar. ForegroundService doğru değilse, bunu gerçekleştirmek için Service.startForeground'u aramanız gerekir.

Bakmanız gereken sonraki şey, dosyanın sonuna yakın olan "LRU listesini işle (oom_adj'ye göre sıralı):" başlıklı bölümdür. Bu listedeki girişler, Android'in uygulamanızı gerçekten bir ön plan hizmeti olarak sınıflandırıp sınıflandırmadığını belirlemenize olanak tanır. Süreciniz bu listenin en altındaysa, özet yok etme için birincil adaydır. Süreciniz listenin en üstüne yakınsa, neredeyse yok edilemez.

Bu tablodaki bir satıra bakalım:

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Bu, her şeyi doğru yapan bir ön plan hizmetinin bir örneğidir. Buradaki anahtar alan "adj =" alanıdır. Bu, her şey söylendikten sonra işleminizin ActivityManagerService tarafından atandığı önceliği gösterir. "Adj = prcp" (görünür ön plan hizmeti) olmasını istiyorsunuz; veya "adj = vis" (bir faaliyet içeren görünür süreç) veya "ön" (ön plan etkinliği olan süreç). "Adj = svc" (hizmet süreci) veya "adj = svcb" (eski hizmet?) Veya "adj = bak" (boş arka plan işlemi) ise, işleminiz büyük olasılıkla sonlandırma adayıdır ve sonlandırılır hafızayı geri kazanmak için herhangi bir baskı olmasa bile her 30 dakikadan daha az sıklıkta değil. Satırda kalan işaretler, çoğunlukla Google mühendisleri için teşhis hata ayıklama bilgileridir. Fesih kararları, adj alanlarına göre verilir. Kısaca, / FS bir ön plan hizmetini belirtir; / FA, bir etkinlik içeren bir ön plan sürecini gösterir. / B, bir arka plan hizmetini belirtir. Sondaki etiket, işlemin öncelik atandığı genel kuralı gösterir. Genellikle adj = alanıyla eşleşmelidir; ancak adj = değeri, bazı durumlarda diğer hizmetler veya etkinliklerle etkin bağlamalar üzerindeki bağlayıcı bayraklar nedeniyle yukarı veya aşağı doğru ayarlanabilir.

Bağlama bayrakları olan bir hataya takıldıysanız, dumpsys satırı şöyle görünecektir:

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

Adj alanının değerinin nasıl yanlış bir şekilde "adj = bak" (boş arka plan işlemi) olarak ayarlandığına dikkat edin, bu da kabaca "lütfen beni şimdi sonlandırın, böylece bu anlamsız varoluşu sona erdirebilirim" anlamına gelir. Ayrıca, "adj" ayarını belirlemek için "yer hizmet kuralları kullanıldığını gösteren satırın sonundaki (fg-hizmet) bayrağına dikkat edin. Fg-hizmet kuralları kullanılmasına rağmen, bu işleme bir adj ayarı atandı "bak" ve çok uzun yaşamayacak. Açıkça söylemek gerekirse, bu bir hata.

Dolayısıyla amaç, sürecinizin her zaman "adj = prcp" (veya daha iyi) olmasını sağlamaktır. Ve bu hedefe ulaşmanın yöntemi, öncelik atamasında hatalardan kaçınmayı başarana kadar bağlama bayraklarını ince ayar yapmaktır.

İşte bildiğim hatalar. (1) HERHANGİ bir hizmet veya etkinlik Context.BIND_ABOVE_CLIENT kullanarak hizmete bağlanmışsa, bu bağlama artık etkin olmasa bile adj = ayarının "bak" değerine düşürülmesi riskini alırsınız. Bu, özellikle hizmetler arasında bağlantılarınız varsa geçerlidir. 4.2.1 kaynaklarında açık bir hata. (2) Bir hizmetten hizmete bağlama için kesinlikle BIND_ABOVE_CLIENT kullanmayın. Aktivite-hizmet bağlantıları için de kullanmayın. BIND_ABOVE_CLIENT davranışını uygulamak için kullanılan bayrak, bağlantı bazında değil, işlem bazında ayarlanmış gibi görünüyor, bu nedenle hizmetten hizmete etkin bir etkinlik olmasa bile hizmetten hizmete bağlanmalarıyla hataları tetikler bayrak kümesi ile bağlanma. Süreçte hizmetten hizmete bağlamaları olan birden fazla hizmet olduğunda öncelik belirlemede sorunlar var gibi görünmektedir. Hizmetten hizmete bağlamalarda Context.BIND_WAIVE_PRIORITY (API 14) kullanmak yardımcı görünmektedir. Context.BIND_IMPORTANT, bir Aktiviteden bir hizmete bağlanırken az çok iyi bir fikir gibi görünüyor. Bunu yapmak, Etkinlik ön plandayken, Etkinlik duraklatıldığında veya bittiğinde görünürde herhangi bir zarar vermeden işlem önceliğinizi bir kademe yükseltir.

Ancak genel olarak strateji, sysdump işleminizin doğru önceliği aldığını gösterene kadar bindService bayraklarınızı ayarlamaktır.

Benim amaçlarım için, Context.BIND_AUTO_CREATE kullanarak | Etkinlikten hizmete bağlamaları için Context.BIND_IMPORTANT ve Context.BIND_AUTO_CREATE | Hizmetten hizmete bağlamalar için Context.BIND_WAIVE_PRIORITY doğru olanı yapıyor gibi görünüyor. Kilometreniz farklı olabilir.

Uygulamam oldukça karmaşık: her biri bağımsız olarak ön plan hizmet durumlarını barındırabilen iki arka plan hizmeti ve ayrıca ön plan hizmet durumunu da alabilen üçüncüsü; hizmetlerden ikisi şartlı olarak birbirine bağlanır; üçüncü her zaman birinciye bağlanır. Ek olarak, Aktiviteler ayrı bir süreçte çalışır (animasyonu daha pürüzsüz hale getirir). Faaliyetleri ve Hizmetleri aynı süreçte çalıştırmak herhangi bir fark yaratmıyor gibi görünüyordu.

Atma işlemleri için kuralların uygulanması (ve sysdump dosyalarının içeriğini oluşturmak için kullanılan kaynak kod) çekirdek android dosyasında bulunabilir.

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.

İyi şans.

Not: İşte Android 5.0 için sysdump dizelerinin yorumu. Onlarla çalışmadım, o yüzden ne yaparsan yap onları. 4'ün "A" veya "S" olmasını ve 5'in "IF" veya "IB" olmasını ve 1'in mümkün olduğunca düşük olmasını istediğinize inanıyorum (muhtemelen 3'ün altında, çünkü yalnızca 3 ön plan hizmet süreci etkin durumda tutuluyor varsayılan yapılandırmada).

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 

4
@Robin Davies, küçük bir sorum var. bindService()Sürekli çalışan Hizmete ihtiyacım olursa gerçekten aramam gerekir mi? Sadece startForeground()servisi aramak yeterli değil mi? Sunucu ile iletişim için EventBus kullanıyorum.
ar-g

Hizmetin ilk etapta çalışmasını sağlamak için bir Etkinlikten Context.bindService'i çağırırsınız. Service.startService yöntemi, başlatılan bir hizmeti "ön plan" durumuna taşımak için hizmetteki kod tarafından çağrılır. EventBus kütüphanesinin, hizmeti başlatmak için bir noktada sizin adınıza Context.bindService'i çağırdığını varsayıyorum. Bir hizmeti başlatmanın başka bir yolu varsa, buna aşina değilim.
Robin Davies

3
Harika gönderi! Olumlu oy verildi. Konuyla ilgili olduğuna inandığım bu yoruma eklemek istediğim bir parça. Sürekli çalışan bir hizmet istiyorsanız, Robin'in bahsettiği gibi bir şekilde başlatmanız gerekir. StartService (Intent service) 'i bindService () yerine doğrudan etkinliğinizin içinden çağırmak mümkündür ve hizmetiniz başladıktan sonra startForeground () yöntemini çağırabilirsiniz. Bunu hizmet sınıfının onStartCommand () içinde çağırıyorum. Bildiğim kadarıyla bu, hizmetinizin bağlı olmamasını sağlamalı, ancak bekleyen kaynak sorunlarını çalıştırmaya devam etmelidir. Umarım bu birine yardımcı olur.
Dave

Harika iş!! Buna bir güncelleme eklemek istiyorum. Öncelikle adb'nin çıktı formatı biraz değişti (Ocak 2016). Bu işlemi iki cihazda test ettim LG Volt 4.4.2 ve Nexus 5x 6.0.1 her iki cihaz da hala hatadan etkileniyor. Sorunu yalnızca Bağlamı kullanarak yeniden oluşturabilirim. etkinlikten çıktıktan sonra cihaz. Diğer tüm bayraklar her iki android sürümünde de iyi çalışıyor gibi görünüyor.
user3259330

1
@Dave hey Dave, START_STICKY'e geri dönmenin yanı sıra tam olarak bu yöntemi kullanıyorum, ancak hizmetim cihaz boştayken bir saat kadar sonra ölüyor. Neler olabileceğine dair herhangi bir fikriniz var mı
Ruchir Baronia

7

Eğer "artık istemiyorum ..." diyorsa, bu işlemin şu anda startForeground () durumunda olan aktif bir hizmeti yoktur. Aramanızın gerçekten başarılı olduğundan emin olmak için kontrol edin - gönderilen bildirimi görüyorsunuz, o noktada günlükte herhangi bir şey hakkında şikayet eden mesaj yok, vb. Ayrıca bakmak için "adb shell dumpsys etkinlik hizmetleri" ni kullanın. hizmetinizin durumunu ve aslında ön plan olarak işaretlendiğinden emin olun. Ayrıca, doğru bir şekilde ön plandaysa, "adb shell dumpsys aktivitesi" çıktısında, işleminizin OOM ayarlamasını gösteren bölümde, işleminizin o hizmet nedeniyle şu anda ön plan düzeyinde olduğunu göreceksiniz.


Yardım için teşekkürler! Sorumu, bahsettiğiniz komutların çıktısıyla düzenledim. Hizmetin ön planda çalıştığını gösteriyor gibi görünüyorlar.
howettl

Teşhise yardımcı olabilecek bir kod bölümü var mı?
howettl

1
Kesinlikle ön plandayken öldürülmemeli ve eminim ki standart platformda Müzik gibi şeylerin olmadığını biliyorum. Sorunu yeniden oluşturmak için kodla bir hata dosyalamayı düşünün. Bakılması gereken bir şey, öldürülmesine izin verebilecek herhangi bir noktada ön plana girip çıkmadığınızdır.
hackbod

1
Devam eden bildirimi tekrar startForeground () 'u çağırmak yerine notify ()' ı çağırarak güncellemek onu ön plan durumundan çıkarabilir mi? Ayrıca önemliyse, bildirimde FLAG_ALERT_ONLY_ONCE özelliğini etkinleştirdim.
howettl

2
Bildirim yöneticisi aracılığıyla kesinlikle güncellemeyin. Bunu hizmet aracılığıyla yayınlıyorsunuz ve hizmet aracılığıyla güncellemeye devam etmelisiniz.
hackbod
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.