Android在设备处于睡眠模式时获取信号强度(PhoneStateListener)

5

我有一个问题,经过一些搜索,我没有找到任何积极的解决方案。经过研究,我认为我的问题没有实现,但这个问题可能是我的最后机会。

我需要什么?

有一个应用程序可以获取移动网络信号强度信息。我使用PhoneStateListener来实现。当然,它工作得很好,但当我的设备进入睡眠模式时,监听器就不起作用了:

https://code.google.com/p/android/issues/detail?id=10931 https://code.google.com/p/android/issues/detail?id=7592

WakeLock只能在设备超时关闭的情况下解决问题。如果我按下电源键,我的设备也会进入睡眠模式。我们无法覆盖电源按钮操作。

我的目标是在设备启用时始终获取信号强度。不管是什么模式,它都应该一直收集数据。

问题:

有什么想法吗?如何实现?是否有方法可以做到这一点,或者可能有一些黑客技巧?欢迎提供所有解决方案。如果您有一些有用的经验,请分享。

感谢大家的帮助!!!我希望这个主题能够完整地解决这个问题。


1
当我按下电源按钮时,我的设备也会进入睡眠模式。我们无法覆盖电源按钮的操作。但是 WakeLock 不受电源按钮的影响。 - CommonsWare
为什么不使用服务? - An-droid
它在睡眠模式下也无法工作。 - Ilya Demidov
4个回答

9

闹钟管理器是一种不错的选择 - 问题在于需要在闹钟管理器接收器返回后保持手机唤醒状态。

  • setup an alarm (notice you should also register an "On Boot completed" receiver to set up the alarm after a reboot - your alarms do not survive a reboot) :

    Intent monitoringIntent = new Intent(context, YourReceiver.class);
    monitoringIntent.setAction("your action");
    PendingIntent pi = PendingIntent.getBroadcast(context, NOT_USED,
                             monitoringIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    AlarmManager am = (AlarmManager) 
                       context.getSystemService(Context.ALARM_SERVICE);
    // here is the alarm set up
    am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    SystemClock.elapsedRealtime() + INITIAL_DELAY,
                    INTERVAL_BETWEEN_ALARMS, pi);
    
  • receive it - the receiver holds a WakeLock in its onReceive() which never fails :

    public abstract class YourReceiver extends BroadcastReceiver {
    
        @Override
        final public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if ("your action".equals(action)) {
                // monitoring - got broadcast from ALARM
                try {
                        d("SS : " + new Signal().getSignalStrength(context));
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }
                // Actu8ally the lines above will ANR
                // I did it with WakefulIntentService :
                // WakefulIntentService.sendWakefulWork(
                // context, YourWakefulService.class);
                // Will be posting it asap
            } else {
                w("Received bogus intent : " + intent);
                return;
            }
        }
    }
    

    If you are lucky (yourRetrieveSignal() is fast enough) this will work, otherwise you will need a (Wakeful)IntentService pattern in your receiver.
    The WakefulIntentService will take care of the wake lock (if you want to avoid a dependency have a look here) - EDIT : keep in mind you can't define listeners in an intent service - see here.

如果接收器在您的设备上ANR,您需要尝试使用WakefulIntentService模式。在任何情况下,您都可以使用此链接:这里
实际上,这是最困难的部分:
class Signal {

    static volatile CountDownLatch latch; //volatile is an overkill quite probably
    static int asu;
    private final static String TAG = Signal.class.getName();

    int getSignalStrength(Context ctx) throws InterruptedException {
        Intent i = new Intent(TAG + ".SIGNAL_ACTION", Uri.EMPTY, ctx,
                SignalListenerService.class);
        latch = new CountDownLatch(1);
        asu = -1;
        ctx.startService(i);
        Log.d(TAG, "I wait");
        latch.await();
        ctx.stopService(i);
        return asu;
    }
}

在哪里:

public class SignalListenerService extends Service {

    private TelephonyManager Tel;
    private SignalListener listener;
    private final static String TAG = SignalListenerService.class.getName();

    private static class SignalListener extends PhoneStateListener {

        private volatile CountDownLatch latch;

        private SignalListener(CountDownLatch la) {
            Log.w(this.getClass().getName(), "CSTOR");
            this.latch = la;
        }

        @Override
        public void onSignalStrengthChanged(int asu) {
            Signal.asu = asu;
            latch.countDown();
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.w(TAG, "Received : " + intent.getAction());
        Tel = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        listener = new SignalListener(Signal.latch);
        @SuppressWarnings("deprecation")
        final int listenSs = PhoneStateListener.LISTEN_SIGNAL_STRENGTH;
        Tel.listen(listener, listenSs);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.w(TAG, "onDestroy");
        Tel.listen(listener, PhoneStateListener.LISTEN_NONE);
        super.onDestroy();
    }

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

这是可行的代码(尽管不是最优雅的 - 欢迎评论/更正)。不要忘记在清单中注册您的服务并获取权限。
编辑 2013.07.23:我没有使用onReceive - 如果您使用它,它会ANR - 如果您在onReceive中使用WakefulIntentService,并在其中调用SignalListenerService,则此代码有效。


1
如果你只支持Android 2.1及以上版本,你需要使用PhoneStateListener.LISTEN_SIGNAL_STRENGTHS而不是PhoneStateListener.LISTEN_SIGNAL_STRENGTH - ChuongPham

1
根据我对PhoneStateListener的理解,当应用程序CPU处于睡眠模式时,您无法执行此操作。您可以让设备保持唤醒状态,但这会损坏电池寿命。或者,您可以使用闹钟(请参见AlarmManager)在间隔期内唤醒设备,以便收集数据(仍然会影响电池寿命)。 这里可以找到一些使用AlarmManager的示例

我该如何让设备保持唤醒状态?在按下硬件电源按钮后,我的设备总是进入睡眠模式。或者我可以通过某种方式实现这一点吗?主要问题就在于电源按钮的情况。 - Ilya Demidov
那么看起来你想要使用AlarmManager。:) AlarmManager将允许您每X分钟/秒唤醒CPU并执行一段代码。 - AndersNS
如果您使用AlarmManager,仍会影响电池寿命。我想,如果您要在应用程序中实现此功能,您需要向用户解释他们的电池可能会有少许耗电。 - ChuongPham

1

0

Android问题10931的可能解决方法之一是在屏幕关闭后向“电话”进程发送android.intent.action.SCREEN_ON意图。

  1. 创建并注册 BroadcastReceiver 监听通知,当屏幕关闭时触发

    start(Context context) {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        context.registerReceiver(mScreenReceiver, filter);
    }
    
    final BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(final Context context, final Intent intent) {
            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                Log.v(LOGTAG, "屏幕已关闭。运行解决方法");
                new Thread(mReportScreenIsOnRunnable).start();
            }
        }
    };
    
  2. 仅向手机进程发送 SCREEN_ON 意图。

    public final Runnable mReportScreenIsOnRunnable = new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Runtime.getRuntime().exec(new String[] { "su", "-c",
                        "am broadcast -a android.intent.action.SCREEN_ON com.android.phone" });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };
    

收到此意图后,手机进程将恢复发送移动位置更新。

需要root权限。

这个解决方案有点hacky、危险,并且不能在所有手机上运行。它可能会导致更高的功耗,但比保持屏幕开启不会多太多。


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