Android 服务丢失 ArrayList

发布于 2024-10-18 05:35:04 字数 4463 浏览 1 评论 0 原文

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

我是 RingPack。基本思想是在后台启动一个服务,负责为用户切换铃声。我遇到了丢失对 ArrayList 的引用的问题 是每当用户从 Activity 中选择一个包时就启动它。

我认为我可能误解了生命周期的工作原理。我的意图 ; 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(因为我将手动创建和销毁 Service),后者将它传递给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 卡是否已安装,因为铃声存储在 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() 。同样,我的用户不断收到此错误,但我自己无法重现它。任何帮助将不胜感激。我为代码墙道歉,但我很困惑。也许我没有正确使用服务的概念?

Edit: here's the source on PasteBin. I feel like I might need to just redesign the entire Service.. :(

I'm the developer of RingPack. The basic idea is that there is a Service launched in the background that takes care of switching the ringtone out for the user. I'm having issues with losing my reference to an ArrayList within the Service. I think I may be misunderstanding how the lifecycle works. My intent was for it to be started whenever the user selects a pack from the 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);

I tell the Service to set the Default Notification Tone to the first tone of the pack corresponding to "currentPackId".

When the user wants to turn off RingPack, the disabling is done like so:

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();

So the Service's onCreate looks like so:

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();}

Then it passes it off to onStartCommand, which returns START_NOT_STICKY (since I will be creating and destroying the Service manually), who passes it off to 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);
        }
    }
}}

The isSdOk() method just checks if the SD card is mounted, since the ringtones are stored on it. initControl() just saves the user's default ringtone, so that we can give it back when they disable us. The setPack() method looks like this:

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));}

The tones ArrayList is what I've been losing. This is where it is initialized. It holds the ids of all the enabled ringtones within a pack. The NullPointerException I've been seeing is in 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));}

They way I intended it work is for swapTone() to never be called if setPack() hasn't already. Again, my users keep getting this error, but I can't reproduce it myself. Any help would be greatly appreciated. I apologize for the code wall, but I am very confused. Perhaps I'm not using the concept of a Service correctly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

咿呀咿呀哟 2024-10-25 05:35:04

好吧,尽管有“代码墙”,您的列表仍然不完整(例如,您说您的问题出在 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 引导用户进入可以配置或关闭服务的 Activity。

Well, despite the "code wall", your listings are incomplete (e.g., you say your problem is with tones but never show where it is defined). I am going to take a guess that it is a data member of your Service.

In that case, it will be null if the service was stopped between PACK_SET and NEXT_TONE operations. This can easily occur, if Android stops the service because it has been running too long. Even with START_NOT_STICKY, if the next startService() call after Android stops the service is NEXT_TONE, not PACK_SET, you will have this problem.

IOW, your data model (the chosen ring pack) is not stored in a persistent location, but rather is held in RAM (tones). You have two choices:

  1. Try to figure out a way that you do not need to be an always-running service, and load the tones out of a persistent store (e.g., database) as needed. This would be ideal, as you are chewing up a bunch of RAM while not delivering value for that RAM every microsecond. For example, you could use AlarmManager with an IntentService.

  2. Use startForeground() to make it less likely that Android will stop your service. The trade-off is that you will need to place a Notification on the screen, so the user knows you are constantly running. You might make that Notification lead the user to the activity where they can configure or shut down the service.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文