2011-07-11 77 views
64

更新我还没有找到真正解决问题的办法。我所想到的是一种在丢失连接时自动重新连接到以前的蓝牙设备的方法。这并不理想,但它似乎工作得很好。我很想听到关于这方面的更多建议。前台服务被杀害由Android

我在这个问题上有很多相同的问题:Service being killed while holding wake lock and after calling startForeground包括设备(华硕变压器),服务停止前的时间长度(30-45分钟),使用唤醒锁,使用startForeground(),以及在屏幕熄灭时应用程序处于打开状态时不会发生问题的事实。

我的应用程序与其他设备保持蓝牙连接并在两者之间发送数据,因此它必须始终处于活动状态以侦听数据。用户可以随意启动和停止服务,事实上,这是我实施启动或停止服务的唯一方式。一旦服务重新启动,与其他设备的蓝牙连接就会丢失。

根据链接问题的答案,startForeground()“降低了服务被杀死的可能性,但并不阻止它”。我明白,要成为这种情况,但是我看到很多没有这个问题的应用程序的例子(例如Tasker)。

如果服务无法运行直到用户停止,我的应用程序的实用性将大大降低。有什么办法可以避免这种?

我在logcat中看到,每当该服务被停止:

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 

编辑:我也应该注意到,这似乎并没有对此我连接到其他设备上发生:HTC传奇运行氰

编辑:这里是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} 

    [email protected] 

    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} 

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) 

这些似乎表明该服务在前台运行。

+0

看看这个答案 - 可能适合你http://stackoverflow.com/a/21157035/624109 – Muzikant

回答

164

Okey dokey。我经历过这个问题,并回到了这个问题。以下是如何继续。有错误。本文将介绍如何分析实施中的错误并解决问题。

总而言之,这里是应该如何工作的。运行服务将每隔30分钟进行一次常规扫描和终止。希望保持活动时间超过此时间的服务必须调用Service.startForeground,该通知会在通知栏上发出通知,以便用户知道您的服务已永久运行并可能吸收电池寿命。只有3个服务进程可以在任何给定的时间将自己命名为前台服务。如果有三个以上的前台服务,Android将提名最早的服务作为清理和终止的候选人。

不幸的是,在优先化前台服务方面,Android存在一些错误,这些错误是由各种服务绑定标志组合触发的。即使您已正确指定您的服务为前台服务,但无论如何,如果您的进程中的任何服务连接都使用了某些绑定标志组合,那么Android可能会终止您的服务。详情如下。

请注意,很少有服务需要前台服务。通常情况下,如果您持续有效或长时间运行的某种可打开和关闭的互联网连接或用户取消,则只需要成为前台服务。需要前台状态的服务示例:UPNP服务器,长时间运行的超大文件下载,通过wi-fi同步文件系统以及播放音乐。

如果您只是偶尔进行轮询或等待系统广播接收器或系统事件,那么最好是通过定时器唤醒您的服务,或者响应广播接收器,然后让服务完成一次。这就是服务的设计行为。如果你只需要保持活力,然后阅读。

勾选了众所周知的要求(例如调用Service.startForeground)后,下一个要查看的地方是您在Context.bindService调用中使用的标志。用于绑定的标志会以各种意想不到的方式影响目标服务进程的优先级。最特别的是,使用某些绑定标志可能会导致Android错误地将您的前台服务降级为常规服务。用于分配进程优先级的代码已经非常严重。值得注意的是,API 14+中的修订版在使用旧版绑定标志时会导致错误; 4.2.1中有明确的错误。

你的朋友在所有这一切都是sysdump工具,它可以用来找出活动管理器已经分配了你的服务过程的优先级,并发现它分配了不正确的优先级的情况。让您的服务启动并运行,然后发出从命令提示符下面的命令在主机上:

亚行外壳dumpsys活动过程> tmp.txt

用记事本(不是写字板/写)检查内容。

首先验证您是否成功地设法在前台状态下运行您的服务。 dumpsys文件的第一部分包含每个进程的ActivityManager属性的描述。寻找像对应于您的应用程序在dumpsys文件的第一部分如下行:

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

验证foregroundServices =真在下面的部分。不要担心隐藏和空的设置;他们描述了流程中的活动状态,似乎与其中的服务流程特别相关。如果foregroundService不是true,则需要调用Service.startForeground才能将其设置为true。

接下来需要看的是靠近文件末尾的部分,标题为“进程LRU列表(按oom_adj排序):”。通过此列表中的条目,您可以确定Android是否已将您的应用程序实际分类为前台服务。如果您的流程位于此列表的最底部,那么它是摘要式灭绝的主要候选对象。如果您的流程靠近列表的顶部,它几乎是坚不可摧的。

让我们来看看在这个表中的一行:

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

这是这样做了一切正确的前台服务的例子。这里的关键字段是“adj =”字段。这表明您的流程在完成所有事情后由ActivityManagerService分配的优先级。你希望它是“adj = prcp”(可见的前台服务);或“adj = vis”(具有活动的可见过程)或“前”(具有前景活动的过程)。如果它是“adj = svc”(服务进程)或“adj = svcb”(传统服务?)或“adj = bak”(空后台进程),那么你的进程可能是终止的候选者,并且将被终止即使没有任何回收记忆的压力,也不会少于每30分钟一次。线上的其余标志主要是Google工程师的诊断调试信息。终止决定是基于调整字段进行的。简而言之,/ FS表示前台服务;/FA表示具有活动的前台进程。/B表示后台服务。最后的标签显示了过程被赋予优先级的一般规则。通常它应该匹配adj =字段;但是在某些情况下,由于与其他服务或活动的活动绑定绑定标志,adj =值可以向上或向下调整。

如果你绊倒的错误有约束力的标志,dumpsys线将是这样的:

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

注意如何ADJ字段的值被错误地设置为“ADJ =李明博”(空后台进程),这个过程大致意味着“请现在终止我,以便我可以结束这个毫无意义的存在”,以便进行过程清理。还要注意行尾的(fg-service)标志,它表示“使用地面服务规则来确定”adj“设置。尽管使用了fg服务规则,但这个过程被分配了一个adj设置“bak”,它不会活很长时间,很明显,这是一个bug。

所以我们的目标是确保你的进程总是得到“adj = prcp”(或更好)。 (1)如果有任何服务或活动使用Context.BIND_ABOVE_CLIENT绑定到服务上,则可以通过调整绑定标志来实现,即使该绑定不再活动,您仍有可能将adj =设置降级为“bak”,尤其是如果您也有服务之间的绑定,则为true。 4.2.1源代码中的一个明确错误。 (2)绝对不会使用BIND_ABOVE_CLIENT进行服务到服务的绑定。不要将它用于活动到服务的连接。用于实现BIND_ABOVE_CLIENT行为的标志似乎是以每个进程为基础设置的,而不是基于每个连接的基础,因此即使没有活动对服务的活动,它也会触发具有服务到服务绑定的错误绑定与标志集。在流程中存在多个服务的情况下,使用服务到服务的绑定似乎也存在确定优先级的问题。在服务到服务绑定上使用Context.BIND_WAIVE_PRIORITY(API 14)似乎有所帮助。从活动绑定到服务时,Context.BIND_IMPORTANT似乎是一个或多或少的好主意。当活动处于前台时,这样做会使进程优先级提高一个档次,而在活动暂停或完成时不会造成任何明显的伤害。

但总的来说,策略是调整您的bindService标志,直到sysdump指示您的进程已收到正确的优先级。

对我而言,使用Context.BIND_AUTO_CREATE |语境。活动对服务绑定的BIND_IMPORTANT和Context.BIND_AUTO_CREATE |服务到服务绑定的Context.BIND_WAIVE_PRIORITY似乎是正确的。你的里程可能不同。

我的应用程序是相当复杂的:两个后台服务,每一个都可以独立地保持前台服务状态,再加上三分之一也可以采取前台服务状态;两个服务有条件地相互绑定;第三,总是与第一个绑定。另外,Activites在单独的过程中运行(使动画更平滑)。在同一过程中运行活动和服务似乎没有任何区别。

的用于清除过程,(和源代码用于生成sysdump文件的内容)的规则执行可以在核Android文件

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

盂兰盆机会找到。

PS:这里是为Android 5.0 sysdump字符串的解释。我没有和他们一起工作,所以他们会做什么。我相信你希望4个是'A'或'S',5个是'IF'或'IB',1要尽可能低(可能低于3,因为只有3个前台服务进程保持活动在默认配置中)。

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 
+1

@罗宾戴维斯,我有一个小问题。如果我需要不断运行服务,我真的需要调用'bindService()'吗?仅仅调用'startForeground()'服务是不够的?用于使用EventBus与服务器通信。 –

+0

您可以从Activity中调用Context.bindService,以便首先让服务运行。 Service.startService方法由服务中的代码调用,以便将已启动的服务移动到“前台”状态。我会假设EventBus库在某个时候代表您调用Context.bindService以启动服务。如果还有另一种开始服务的方式,我并不熟悉它。 –

+1

好帖子! Upvoted。我想补充一点,我认为这是相关的。如果你想要一个持续运行的服务,正如罗宾提到的,你需要以某种方式启动它。可以直接在您的活动中调用startService(Intent服务)而不是bindService(),然后一旦启动服务,您可以调用startForeground()方法。我在服务类的onStartCommand()中调用它。据我所知,这应该使您的服务不受限制,但在资源问题中保持运行。希望这有助于某人。 – Dave

5

如果它说“不再需要...”,那么该进程没有活动的服务,它当前处于startForeground()状态。检查以确保您的调用实际上是成功的 - 您看到通知已发布,日志中没有任何消息抱怨任何事情等。还可以使用“adb shell dumpsys活动服务”查看您的服务状态,并确保它实际上标记为前景。另外,如果前景正确,那么在“adb shell dumpsys活动”的输出中,您将在显示进程的OOM adj的部分中看到,由于该服务,您的进程当前处于前台级别。

+0

感谢您的帮助!我用你提到的命令的输出编辑了我的问题。他们似乎表明该服务正在前台运行。 – howettl

+0

是否有一段代码可以发布,可能有助于诊断? – howettl

+1

它绝对不应该在前台被杀,而且我确实知道标准平台中的音乐等东西不是。考虑用代码提交一个错误来重现问题。有一点需要注意的是,如果你在任何可能导致死亡的地点前进或后退, – hackbod

相关问题