Android 服务在应用程序关闭后无法保持活动状态

Android Service not staying alive after app closes

我想要一个后台服务,它会在应用程序关闭后保持活动状态,并且在应用程序启动时我可以再次绑定。

为了测试,每次绑定到服务时,计数器都会增加。


所以理论上应用程序应该启动,我将创建服务,然后绑定到它 -> 计数器应该向上移动。

然后我关闭应用程序并再次按下绑定按钮,它应该记录一个“1”并再次向上移动计数器。

但事实并非如此…… 每次我重新启动应用程序并绑定到它时,它都会显示 0 ...


这是我当前的测试 - 服务 - class:

package com.programm.testapp;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class TestService extends Service {

    /*
     * Service Binder
     */
    private final IBinder iBinder = new TestService.LocalConnectionService();

    public class LocalConnectionService extends Binder {
        public TestService getService(){
            return TestService.this;
        }
    }

    /*
     * Test var
     * It should increase every time the app is started.
     */
    private int test;

    @Override
    public IBinder onBind(Intent intent) {
        Log.d("mDEBUG", "Test: " + test);
        test++;

        return iBinder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("mDEBUG", "Service: Start Command");

        return START_STICKY;
    }
}

这是我当前的测试 - Activity:

package com.programm.testapp;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private TestService service;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button createButton = findViewById(R.id.button_create_service);
        createButton.setOnClickListener(this::createService);

        Button destroyButton = findViewById(R.id.button_destroy_service);
        destroyButton.setOnClickListener(this::destroyService);



        Button bindButton = findViewById(R.id.button_bind_service);
        bindButton.setOnClickListener(this::bindService);

        Button unbindButton = findViewById(R.id.button_unbind_service);
        unbindButton.setOnClickListener(this::unbindService);


    }

    private void createService(View v){
        Intent intent = new Intent(this.getBaseContext(), TestService.class);
        startService(intent);
    }

    private void destroyService(View v){
        Intent intent = new Intent(this.getBaseContext(), TestService.class);
        stopService(intent);
    }

    private void bindService(View v){
        Intent intent = new Intent(this.getBaseContext(), TestService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
    }

    private void unbindService(View v){
        unbindService(serviceConnection);
    }


    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("mDEBUG", "Connection: on service connected");

            MainActivity.this.service = ((TestService.LocalConnectionService) service).getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d("mDEBUG", "Connection: on service disconnected");
        }
    };
}

这是我的 AndroidManifest.xml - 文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.programm.testapp">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".TestService"
            android:enabled="true"
            android:exported="false"></service>
    </application>
</manifest>

这是我...

后的输出
  1. 按下创建服务 - 按钮
  2. 按下绑定服务 - 按钮
  3. 按下解除绑定服务 - 按钮
  4. 关闭应用程序并重新启动
  5. 按下绑定服务 - 按钮

:

.../com.programm.testapp D/mDEBUG: Service: Start Command
.../com.programm.testapp D/mDEBUG: Test: 0
.../com.programm.testapp D/mDEBUG: Connection: on service connected
.../com.programm.testapp D/mDEBUG: Service: Start Command
.../com.programm.testapp D/mDEBUG: Test: 0
.../com.programm.testapp D/mDEBUG: Connection: on service connected

顺便说一下,当我关闭应用程序时调用了第二个 "Service: Start Command" ...在我注意到一些新日志之后,还有构造函数和服务的 "onCreate" 方法 - class 将被调用。

这正常吗?


编辑:

当我只最小化应用程序而不是通过 Activity 关闭它时 - 菜单行为正是我想要的!!!


编辑 2:

前台服务暂时完成这项工作... 我找不到任何其他解决方案

如果您主动关闭该应用程序(通过从 Android activity 列表中关闭它),Android 很可能会终止您的服务。您可以在您的应用程序中看到这一点 Logcat。唯一真正解决这个问题的方法是前台服务。

此外,onBind 不会 在您每次绑定到该服务时被调用。来自 Android documentation

You can connect multiple clients to a service simultaneously. However, the system caches the IBinder service communication channel. In other words, the system calls the service's onBind() method to generate the IBinder only when the first client binds. The system then delivers that same IBinder to all additional clients that bind to that same service, without calling onBind() again.

其次,仅仅调用了onStartCommand并不意味着重新创建了服务。在服务生命周期内可以多次调用。例如,每次调用 startService 时,都会执行 onStartCommand,但不一定会重新创建服务。

此外,您似乎在关闭 activity 时没有解除绑定服务。这会使您的 activity 泄漏 ServiceConnection 并使您的应用程序崩溃。它可以解释为什么您每次关闭并重新启动应用程序时都会看到重新创建的服务。

尝试在 activity 的 onPause 方法中添加解除绑定:

@Override
void onPause() {
    super.onPause()
    unbindService(this.serviceConnectino)
}

工作配置如下所示。它使用专用服务函数而不是 onBind:

实现递增计数器

MyBoundService.kt

package com.test

import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.util.Log

class MyBoundService : Service() {

    abstract class MyBinder: Binder() {
        abstract fun getService(): MyBoundService
    }

    val iBinder: MyBinder = object: MyBinder() {
        override fun getService(): MyBoundService {
            return this@MyBoundService
        }
    }

    private var counter = 0

    fun increment() {
        counter ++
        Log.i("MyBoundService", "Counter: ${counter}")
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Log.i("MyBoundService", "startCommand");
        return super.onStartCommand(intent, flags, startId)
    }

    override fun onBind(p0: Intent?): IBinder? {
        counter++
        Log.i("MyBoundService", "Bound: ${counter}")
        return iBinder
    }

    override fun onUnbind(intent: Intent?): Boolean {
        Log.i("MyBoundService", "Unbound")
        return super.onUnbind(intent)
    }
}

MainActivity.kt

package com.test

import android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import com.test.MyBoundService

class MainActivity : AppCompatActivity() {


    private val serviceConnection: ServiceConnection = object: ServiceConnection {
        override fun onServiceDisconnected(p0: ComponentName?) {
            Log.i("MainActivity", "Service disconnected")
        }

        override fun onServiceConnected(p0: ComponentName?, p1: IBinder?) {
            Log.i("MainActivity", "Service connected")
            p1?.let {
                (p1 as MyBoundService.MyBinder).getService().increment()
            }
        }

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_create.setOnClickListener {
            val i = Intent(this@MainActivity, MyBoundService::class.java)
            startService(i)
        }

        btn_bind.setOnClickListener {
            val i = Intent(this@MainActivity, MyBoundService::class.java)
            bindService(i, serviceConnection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onPause() {
        super.onPause()
        unbindService(serviceConnection)
    }
}