如何监听连接电源在 Android 8 及以上版本的设备上。

9

我有一个简单的Android Kotlin应用程序,其部分功能是监听电源连接和断开并执行相应操作。

这是我的旧代码,在面向Oreo以下设备时完全正常工作。

AndroidManifest.xml

<receiver android:name=".ChargingUtil$PlugInReceiver">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
        <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
    </intent-filter>
</receiver>

ChargingUtil.kt

class ChargingUtil (context: Context){

    /*... Some other charging-related functions here ... */

    class PlugInReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            Log.d("thisistest", "Power was changed")
            // Here I do some logic with `intent.action`
        }
    }
}

最近的Android版本在实现广播方面做了一些更改,具体信息请参考此处文档
我尝试过的方法如下:
- 我按照这个文档中的说明实现了代码,但是它们的实现与我的当前代码相同(仅适用于Android 8以下版本)。 - 我还找到了这个问题,但是唯一的解决方案是定期检查电源是否连接,我认为这对我来说不太可行,因为我的应用需要即时知道充电状态何时发生变化。
所以,我的问题是:如何在电源连接/断开时调用函数?同时考虑到运行Android 8或更高版本的系统对清单声明的接收器施加的附加限制。
注意:我使用Kotlin,并希望避免使用已过时的包。
我在Android方面有点新手,如果有任何明显的解决方案我错过了,请提前谅解。先感谢您的帮助。
3个回答

8
你所提到的问题已有答案。
使用 ACTION_BOOT_COMPLETED 来在启动后启动前台服务。该服务应注册 ACTION_POWER_CONNECTED 广播。你也可以将该服务单独运行在另一个进程中。一旦电源连接/断开,你将在服务类中收到广播,在那里你可以运行所需的方法。
public class MyService extends Service {
    private String TAG = this.getClass().getSimpleName();

    @Override
    public void onCreate() {
        Log.d(TAG, "Inside onCreate() API");
        if (Build.VERSION.SDK_INT >= 26) {
            NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
            mBuilder.setSmallIcon(R.drawable.ic_launcher);
            mBuilder.setContentTitle("Notification Alert, Click Me!");
            mBuilder.setContentText("Hi, This is Android Notification Detail!");
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            // notificationID allows you to update the notification later on.
            mNotificationManager.notify(100, mBuilder.build());
            startForeground(100, mBuilder.mNotification);

            IntentFilter filter1=new IntentFilter();
            filter1.addAction(Intent.ACTION_POWER_CONNECTED);
            registerReceiver(myBroadcastReceiver,filter1);
        }
    }


    @Override
    public int onStartCommand(Intent resultIntent, int resultCode, int startId) {
        Log.d(TAG, "inside onStartCommand() API");
        return startId;
    }


    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "inside onDestroy() API");
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }

       BroadcastReceiver myBroadcastReceiver =new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                // call your method
            }
        };
}

您还可以使用 setRequiresCharging 将一个 JobScheduler 注册为 true。这将在电源状态改变时启动 JobScheduler 的作业。
ComponentName serviceComponent = new ComponentName(context, TestJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
builder.setMinimumLatency(1 * 1000); // wait at least
builder.setOverrideDeadline(3 * 1000); // maximum delay
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); 
builder.setRequiresDeviceIdle(true); 
builder.setRequiresCharging(true);
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());

1
问题是关于 Kotlin 的,但这是 Java,并且一些代码并不完全清楚它在做什么。另外 NotificationCompat.Builder 已经被弃用了。因此,我添加了另一个更简洁的 答案,使用 Kotlin 编写,以防有人需要。 - Lissy93
可能服务应该被设置为前台服务,否则它将在5秒钟后被杀死。 - hyena

6
从Android8.0(API 26及更高版本)开始,你不能使用静态接收器来接收大部分Android系统广播在这里阅读
BroadcastReceiver可以是静态接收器或动态接收器,具体取决于你如何注册它:
- 要静态地注册接收器,在AndroidManifest.xml文件中使用<receiver>元素。静态接收器也称为清单声明接收器。 - 要动态地注册接收器,请使用应用程序上下文或活动上下文。只要注册上下文有效(即相应的应用程序或活动正在运行),接收器就会接收广播。动态接收器也称为上下文注册接收器。
因此,你需要动态地注册你的接收器,前往AndroidManifest.xml并删除<receiver>标签,然后在你的活动中注册它。 private MyBatteryReceiver mReceiver = new MyBatterReceiver(); 然后为功率操作使用IntentFilter
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
filter.addAction(Intent.ACTION_POWER_CONNECTED)

您所剩下的就是注册并注销您的注册

this.registerReceiver(mReceiver, filter); // using activity context.
this.unregisterReceiver(mReceiver); // implement on onDestroy().

4

感谢 @Derek 的回答,再加上我做了一些修改,我让它能够正常工作了。在这里发布可用的解决方案,以便对其他人有所帮助。

PowerConnectionReciever.kt

import android.content.Intent
import android.content.BroadcastReceiver
import android.os.IBinder
import android.content.IntentFilter
import android.app.Service
import android.content.Context

class PowerConnectionService : Service() {

    private var connectionChangedReceiver: BroadcastReceiver = 
        object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // This block gets run whenever the power connection state is changed
            when {
                intent.action == Intent.ACTION_POWER_CONNECTED ->
                    powerWasConnected()
                intent.action == Intent.ACTION_POWER_DISCONNECTED ->
                    powerWasDisconnected()
            }
        }
    }

    override fun onCreate() {
        val connectionChangedIntent = IntentFilter()
        connectionChangedIntent.addAction(Intent.ACTION_POWER_CONNECTED)
        connectionChangedIntent.addAction(Intent.ACTION_POWER_DISCONNECTED)
        registerReceiver(connectionChangedReceiver, connectionChangedIntent)
    }

    override fun onStartCommand(
        resultIntent: Intent, resultCode: Int, startId: Int): Int {
        return startId
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(connectionChangedReceiver)
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    private fun powerWasConnected() {
        // Do whatever you need to do when the power is connected here
    }
    private fun powerWasDisconnected() {
        // And here, do whatever you like when the power is disconnected
    }
}

然后在我的MainActivity.kt文件中,添加了这个函数,它在onCreate钩子内被调用。

private fun startPowerConnectionListener() {
        val serviceComponent = ComponentName(this, PowerConnectionService::class.java)
        val builder = JobInfo.Builder(0, serviceComponent)
        builder.setMinimumLatency((200)) // wait time
        builder.setOverrideDeadline((200)) // maximum delay
        val jobScheduler = this.getSystemService(JobScheduler::class.java)
        jobScheduler.schedule(builder.build())
    }

我需要做的唯一其他更改是在AndroidMainifest.xml文件中。

添加了一个FOREGROUND_SERVICE权限作为一级项目:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

当然,还需要注册新的PowerConnectionService,这应该在相关的activity节点中完成。

<service
    android:name=".PowerConnectionService"
    android:permission="android.permission.BIND_JOB_SERVICE"
    android:exported="true">
</service>

2
在这里使用JobScheduler的意义是什么?为什么不直接从MainActivity启动服务?我没有看到你的服务被启动为ForegroundService,所以它可能会在一段时间后被杀死,或者我漏掉了什么吗? - Juke
正确。我认为既不需要前台服务也不需要作业调度程序。如果在连接电源时作业调度程序没有立即触发,那么就需要前台服务。 - user4057066
非常复杂,一个动态接收器似乎就足够了(请参见https://dev59.com/KbTma4cB1Zd3GeqP6GuQ#56540820) - woprandi

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