持有唤醒锁并调用startForeground后服务被杀死

4
我遇到了一个问题,即使我持有唤醒锁并调用了startForeground,我的服务仍然会被杀死。当这种情况发生时,平板电脑(ASUS Transformer TF101)会在没有调用onDestroy的情况下停止服务。没有其他应用程序可见,并且日志显示没有异常(没有“内存不足”等消息)。在被杀死后,服务会立即重新启动。
我正在开发的应用是一个聊天客户端,需要保持持续连接,它还是基于插件的,因此我的应用程序是这样开发的:客户端-HostService-多个子“服务”。
主服务是黏性的,持有唤醒锁并调用startForeground(并显示通知),子服务不是黏性的,不持有唤醒锁并且是后台服务。
如果客户端本身打开,则不会出现问题,但我要实现的模型是用户可以使用设备并保持连接(接收消息等),而无需始终打开客户端。
有人能解释为什么服务会以这种方式被杀死吗?如果可以防止发生这种情况,那么怎么做呢?由于聊天客户端显示用户登录和退出,而服务的死亡会导致所有打开的连接都中断,这会使聊天客户端“弹跳”。目前,它似乎在每15到45分钟之间发生一次。
此外,如果有人知道如何在不持有唤醒锁的情况下持续保持套接字连接开放状态,我很想听听!下面是主服务源代码的修剪测试版本。
public class HostService extends Service
{
    PowerManager m_powerManager = null;
    PowerManager.WakeLock m_wakeLock = null;

    @Override
    public IBinder onBind( Intent intent )
    {
        return m_serviceImplementation;
    }

    @Override
    public void onCreate()
    {
        super.onCreate();       
    }

    @Override
    public void onDestroy()
    {
        if( m_wakeLock != null )
        {
            m_wakeLock.release();
            m_wakeLock = null;
        }

        stopForeground( true );

        super.onDestroy();
    }

    @Override
    public int onStartCommand( Intent intent, int flags, int startId )
    {
        // Display a notification about us starting. We put an icon in the
        // status bar.
        Notification notification = createNotification();

        startForeground( R.string.service_running, notification );

        if( m_powerManager == null )
        {
            m_powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
        }

        if( m_wakeLock == null )
        {
            m_wakeLock = m_powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Keep background services running");
            m_wakeLock.acquire();
        }

        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    /**
     * Create a notification to show the service is running
     */
    private Notification createNotification()
    {
        CharSequence text = getText( R.string.service_running );
        CharSequence title = getText( R.string.app_name );

        // The PendingIntent to launch our activity if the user selects this
        // notification
        PendingIntent contentIntent = PendingIntent.getActivity( this, 0, new Intent(this, MainChat.class) , 0 );

        Notification notification = new Notification( android.R.drawable.sym_action_chat, title, System.currentTimeMillis() );  
        notification.setLatestEventInfo( this, title, text, contentIntent );

        return notification;
    }

    private final IMessageInterface.Stub m_serviceImplementation = new IMessageInterface.Stub()
    {
        ...
    };
}

Android清单文件(相关部分):

<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="11" />

<service android:name="com.mydomain.chatClient.server.HostService" android:exported="true" android:enabled="true" android:process=":remote"/>

<uses-permission android:name="android.permission.WAKE_LOCK" />
1个回答

10

即使我持有wake lock并调用了startForeground,我的服务仍然被杀死了。

startForeground()可以降低服务被杀死的可能性,但并不能完全防止。

我正在开发的应用是一个聊天客户端,需要保持持续连接。它还是基于插件的,因此我的应用程序结构如下:客户端- HostService- 多个子“服务”。

我建议你去掉其中一层。即使操作系统不关闭你的应用,许多用户也会这样做(例如任务管理器、设置中的运行服务),认为你运行了太多服务。

只要客户端本身打开了,问题就不会出现,但我想实现的模型是用户可以使用设备并保持连接(接收消息等),而无需始终打开客户端。

我建议你将其作为可选项。你可能认为这很酷,但一些用户会抱怨耗费他们的电池。

有人能解释一下为什么以这种方式杀死服务,并且如果有的话,如何防止它发生?

我建议首先去掉android:process=":remote"。你不需要它,也不想要它。用它可能会加速Android关闭你的服务的兴趣。你绝对会因为它而伤害用户,因为你在没有好理由的情况下浪费了RAM。

如果您已将插件实现为单独的应用程序,请摆脱这些插件。在这种情况下,每个插件都将在自己的进程中运行,浪费更多的RAM。此外,当前的实现会存在缺陷,因为您将被困在将服务命名为com.mydomain.chatClient.server.HostService的情况下,直到最后,因为您没有使用<intent-filter>来区分“服务在内部命名”的关注点和“其他单独安装的应用程序调用该服务时所使用的名称”。而如果您没有将插件实现为单独的应用程序,那么我无法看到它们作为单独服务的价值,而不是将它们全部合并到一个服务中。

另外,如果有人知道一种保持套接字连接持续打开而不需要在整个连接持续时间内持有唤醒锁的方法,请告诉我!

如果套接字使用的是无线数据而非WiFi,则不需要始终使用WakeLock。套接字将保持打开状态,该套接字上的传入数据包将唤醒您的代码。在那时,您需要获取足够长的WakeLock以便在数据到达时进行任何需要的操作,然后释放WakeLock

但如果使用的是WiFi,则这个技巧行不通,因此需要一个WakeLock(以及可能是WifiLock)。


1
@icStatic:还要注意的是,SystemPanel Lite并不是非常可靠。例如,我刚刚随意安装了它,并运行了一个使用startForeground()的书籍示例。虽然“设置”应用程序显示我的服务正在运行,但SystemPanel Lite根本没有显示它,在多次刷新后仍是如此。 - CommonsWare
你不需要持有wakelock来保持套接字的开放。这是我制作的一个例子 https://github.com/schwiz/android-websocket-example - Nathan Schwermann
@CommonsWare 但是除非有数据传输,否则不会持有wakelocks,连接将保持打开状态而不需要wakelock。一旦第一条信息通过线路传输,我就会持有wakelock以确保我获取整个消息。使用这种技术,我已经能够在零电池影响下保持连接超过12小时。 - Nathan Schwermann
@schwiz:这意味着你正在使用移动数据,而不是WiFi。否则,仅WiFi无线电就会产生非零的电池影响。如果你没有电池影响,那么你没有使用WiFi。 - CommonsWare
@CommonsWare 没问题,我的示例会在 WiFi 断开并且蜂窝连接重新启动时自动重新连接。此外,当您切换基站时,蜂窝连接也会被短暂地断开。因此,当设备进入睡眠状态并关闭 WiFi 时,我认为这种断开连接是微不足道的,因为我知道蜂窝连接很快就会重新连接。这对我的目的来说很好,但我想在其他情况下可能会非常错误。 - Nathan Schwermann
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接