【问题标题】:Android Service Losing an ArrayListAndroid 服务丢失 ArrayList
【发布时间】:2011-02-21 18:50:23
【问题描述】:

编辑:here's PasteBin 上的源代码。我觉得我可能需要重新设计整个服务.. :(

我是RingPack 的开发者。基本思想是在后台启动一个服务,负责为用户切换铃声。我在丢失对服务中的 ArrayList 的引用时遇到问题。我想我可能误解了生命周期是如何工作的。我的意图是在用户从 Activity 中选择一个包时启动它。

Intent i = new Intent(RingActivity.this, RingService.class); i.putExtra(RingService.ACTION, RingService.PACK_SET); i.putExtra(RingService.PASSED_PACK, currentPackId); RingActivity.this.startService(i);

我告诉服务将默认通知音设置为与“currentPackId”对应的包的第一个音。

当用户想要关闭 RingPack 时,禁用如下:

Intent i = new Intent(RingActivity.this, RingService.class); RingActivity.this.stopService(i);

Toast.makeText(RingActivity.this.getBaseContext(), RingActivity.this.getString(R.string.ringPackDisabled), Toast.LENGTH_SHORT).show();

所以 Service 的 onCreate 看起来像这样:

public void onCreate() {
db = new DbManager(this);
db.open();

vib = (Vibrator) getSystemService(VIBRATOR_SERVICE);

IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_TICK);
registerReceiver(tReceiver, intentFilter);
timeTick = 0;

//figure out the widget's status
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
widgetEnabled = prefs.getBoolean(WIDGET_ALIVE, false);

//save the ringtone for playing for the widget
Uri u = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
r = RingtoneManager.getRingtone(this.getBaseContext(), u);
playAfterSet = false;

super.onCreate();}

然后它将它传递给 onStartCommand,它返回 START_NOT_STICKY(因为我将手动创建和销毁服务),然后将它传递给 handleStart()。

@Override private void handleStart(Intent intent) {     
final Intent i = intent;
if (isSdOk()) {
    int action = i.getIntExtra(ACTION, -1);

    if (action != -1) {
        if (action == PACK_SET) {
            playAfterSet = true;

            Thread packSetThread = new Thread() {
                @Override
                public void run() {
                    int passedPackId = i.getIntExtra(PASSED_PACK, -1);
                    //we were passed the id
                    if (passedPackId != -1) {
                        checkPrefs();

                        if (!enabled)
                            initControl(passedPackId);
                        else
                            setPack(passedPackId);

                        packSetHandler.sendEmptyMessage(0);
                    }
                }
            };
            packSetThread.start();
        }
        else if (action == NEXT_TONE) {
            checkPrefs();
            swapTone();
        }
        else if (action == PLAY_TONE) {
            playCurrentTone();
        }
        else if (action == WIDGET_STATUS) {
            widgetEnabled = intent.getBooleanExtra(WIDGET_ALIVE, false);
            if (toneName != null)
                RingWidget.update(getBaseContext(), toneName);
        }
    }
}}

isSdOk() 方法只检查 SD 卡是否已安装,因为铃声存储在其中。 initControl() 只是保存用户的默认铃声,以便我们可以在他们禁用我们时将其归还。 setPack() 方法如下所示:

private void setPack(int packId) {
//save the current pack id in the prefs for restarting
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(RingService.this.getBaseContext());
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(SAVED_PACKID, packId);
editor.commit();

//get the info we need to work with this pack
//it's path on the SD
//build the tones ArrayList to work from
grabPath(packId);
if (tones == null)
    tones = new ArrayList<Integer>();
else
    tones.clear();
mapTones(packId);
currIndex = 0;
setNotificationTone(tones.get(currIndex));}

音调 ArrayList 是我一直在失去的。这是它被初始化的地方。它包含一个包中所有启用的铃声的 ID。我看到的 NullPointerException 在 swapTone() 中:

private void swapTone() {
//locked
if (lockPref)
    return;
//shuffle on
else if (shufflePref) {
    int randIndex = currIndex;

    while (randIndex == currIndex)
        randIndex = (int) Math.floor(Math.random() * tones.size());
    currIndex = randIndex;
}
//shuffle off
else {
    if (currIndex != (tones.size() - 1))
        currIndex++;
    else
        currIndex = 0;
}

setNotificationTone(tones.get(currIndex));}

我希望它起作用的方式是,如果 setPack() 尚未调用,则 swapTone() 永远不会被调用。同样,我的用户不断收到此错误,但我自己无法重现它。任何帮助将不胜感激。我为代码墙道歉,但我很困惑。也许我没有正确使用服务的概念?

【问题讨论】:

    标签: android nullpointerexception android-service android-lifecycle


    【解决方案1】:

    好吧,尽管有“代码墙”,但您的列表并不完整(例如,您说您的问题在于 tones,但从未显示它的定义位置)。我猜测它是您的Service 的数据成员。

    在这种情况下,如果服务在PACK_SETNEXT_TONE 操作之间停止,它将是null。如果 Android 由于运行时间过长而停止服务,则很容易发生这种情况。即使使用START_NOT_STICKY,如果Android 停止服务后的下一个startService() 调用是NEXT_TONE,而不是PACK_SET,就会出现这个问题。

    IOW,您的数据模型(选择的环包)并未存储在永久位置,而是保存在 RAM (tones) 中。你有两个选择:

    1. 尝试找出一种不需要始终运行的服务的方法,并根据需要从持久存储(例如数据库)中加载音调。这将是理想的,因为您正在咀嚼一堆 RAM,而不是每微秒为该 RAM 提供价值。例如,您可以将AlarmManagerIntentService 一起使用。

    2. 使用startForeground() 降低Android 停止服务的可能性。权衡是您需要在屏幕上放置一个Notification,以便用户知道您一直在运行。您可以让Notification 将用户引导到他们可以配置或关闭服务的活动。

    【讨论】:

    • 我的错误。是的,色调是一个全局定义。看pastebin代码,我只是放了全班。因此,如果我想将其保留为后台服务,我应该使其能够随时被杀死并重新启动。我的印象是它不会被杀死。
    • @Weston:对不起,当我穿过密码墙时我已经忘记了 pastebin... :-) “我应该让它能够随时被杀死并重新启动” - - 绝对地。 “我的印象是它不会被杀死”——绝对不会。用户可以使用任务杀手(Android 2.2 之前的版本)攻击您,用户可以通过“设置”应用停止服务,如果 Android 认为服务被泄露,它将关闭它。
    • 好吧,我会考虑到这一点重新设计。我的好奇心达到顶峰的是,在我的 onDestroy() 中,我将一个名为 ENABLED 的首选项保存为 false。任何启动服务的东西都会在调用 Context.startService() 之前检查这个首选项。因此,即使 Android 系统杀死了我的服务,onDestroy() 也应该被调用,这意味着除了用户之外的任何人都不应该再次启动它(当他们在正常上下文中选择一个包时)。那么 onDestroy() 实际上没有被调用吗?奇怪...
    • @Weston:“所以即使 Android 系统杀死了我的服务,onDestroy() 也应该被调用”——不一定。
    • 在你和 r/androiddev 之间,我明白了。谢谢!有答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-05
    • 2018-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多