【问题标题】:Android Service Creating new Instance of Singleton ClassAndroid服务创建单例类的新实例
【发布时间】:2014-08-28 19:46:02
【问题描述】:

我正在使用单例类来通过应用程序外的一些队列来保存一些数据。

我正在创建 Application 类的单例类实例 onCreate 方法。

 @Override
 public void onCreate() {
    super.onCreate();
    mInstance = this;
    mContext = getApplicationContext();
    Queue.getInstance(); // this is my singleton class instance
  }

在此之后,我将在我的活动中的这个单例类中添加数据

Queue.getInstance().addItem(qItem);
Log.d(Constants.TAG, "Added Item Queue Size: "+Queue.getInstance().getQueueList().size());

直到这一切正常。我可以访问我的ActivitiesListView Adapter 中的数据,但是当我启动服务并尝试访问服务的数据onCreate

Log.d(Constants.TAG, "Playing Item Queue Size: "+Queue.getInstance().getQueueList().size()+" Current Item No. "+Queue.getInstance().getCurrentPlayingItem());
String url = Queue.getInstance().getQueueItem(Queue.getInstance().getCurrentPlayingItem()).getLinkUrl();

我的单例实例变为空,我的单例创建新实例。这会导致我在服务内部丢失数据。

以下是我的错误流程。

  1. 开始应用程序创建实例 - 工作
  2. 从活动和适配器添加数据 - 工作
  3. 启动服务并访问数据 - 因为单例而无法正常工作 实例在服务内变为空

以下是我的单例类的代码

import java.util.ArrayList;

import com.taazi.utils.Constants;

import android.util.Log;

public class Queue {
    private ArrayList<QueueItem> mQueueList;
    static Queue mInstance;
    private int currentPlayingItem=0;

    private Queue(){
        mQueueList = new ArrayList<QueueItem>();
    }

    public static Queue getInstance(){
        if(mInstance == null){
            mInstance = new Queue();
            Log.d(Constants.TAG, "New Instance");
        }
        return mInstance;
    }

    public void addItem(QueueItem item){
        mQueueList.add(item);
    }

    public void removeItem(int position){
        mQueueList.remove(position);
    }

    public ArrayList<QueueItem> getQueueList(){
        return mQueueList;
    }

    public QueueItem getQueueItem(int position){
        return mQueueList.get(position);
    }

    public int getCurrentPlayingItem() {
        return currentPlayingItem;
    }

    public void setCurrentPlayingItem(int currentPlayingItem) {
        this.currentPlayingItem = currentPlayingItem;
    }


}

AudioPlayBackService.Java

package com.taazi.services;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.IBinder;
import android.util.Log;

import com.taazi.helper.NotificationHelperNew;
import com.taazi.models.Queue;
import com.taazi.models.QueueItem;
import com.taazi.utils.Constants;

public class AudioPlayBackService extends Service {

    /**
     * Called to go toggle between pausing and playing the music
     */
    public static final String TOGGLEPAUSE_ACTION = "com.taazi.services.togglepause";

    /**
     * Called to go to pause the playback
     */
    public static final String PAUSE_ACTION = "com.taazi.services.pause";

    /**
     * Called to go to stop the playback
     */
    public static final String STOP_ACTION = "com.taazi.services.stop";

    /**
     * Called to go to the previous track
     */
    public static final String PREVIOUS_ACTION = "com.taazi.services.previous";

    /**
     * Called to go to the next track
     */
    public static final String NEXT_ACTION = "com.taazi.services.next";

    /**
     * Used to build the notification
     */
    private NotificationHelperNew mNotificationHelper;

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

    MediaPlayer player;

    @Override
    public void onCreate() {
        super.onCreate();
     // Initialize the notification helper
        mNotificationHelper = new NotificationHelperNew(this);

        Log.d(Constants.TAG, "Playing Item Queue Size: "+Queue.getInstance().getQueueList().size()+" Current Item No. "+Queue.getInstance().getCurrentPlayingItem());
        String url = Queue.getInstance().getQueueItem(Queue.getInstance().getCurrentPlayingItem()).getLinkUrl();
        player = MediaPlayer.create(this, Uri.parse(url));
        player.setLooping(false); // Set looping
        updateNotification();
    }
    public int onStartCommand(Intent intent, int flags, int startId) {
        player.start();
        return 1;
    }

    public void onStart(Intent intent, int startId) {
        // TO DO
    }
    public IBinder onUnBind(Intent arg0) {
        // TO DO Auto-generated method
        return null;
    }

    public void onStop() {

    }
    public void onPause() {

    }
    @Override
    public void onDestroy() {
        mNotificationHelper.killNotification();
        player.stop();
        player.release();
    }

    @Override
    public void onLowMemory() {

    }

    /**
     * Updates the notification, considering the current play and activity state
     */
    private void updateNotification() {
        QueueItem item =  Queue.getInstance().getQueueItem(Queue.getInstance().getCurrentPlayingItem());
            mNotificationHelper.buildNotification("", item.getArtist(),
                    item.getTitle(), (long)50, null, true);
    }

}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.taazi.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="20" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
    <uses-permission android:name="android.permission.GET_TASKS" />


    <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />

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

    <application
        android:name="com.taazi.app.AppController"
        android:allowBackup="true"
        android:hardwareAccelerated="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.Apptheme" >
        <activity
            android:name="com.taazi.activities.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <!-- Music service -->
        <service
            android:name="com.taazi.services.AudioPlayBackService"
            android:label="@string/app_name"
            android:process=":main" />

    </application>

</manifest>

【问题讨论】:

  • 您是否在单独的进程中运行该服务?在单独的进程中运行它会导致这种情况(打破单例模式)
  • 也包括你的服务清单
  • 添加您的服务 onCreate() 方法和清单文件的代码。
  • @waqaslam 添加了更多代码。
  • @TheLostMind 我正在使用startService(playBackServiceIntent); 启动服务。

标签: java android singleton android-service


【解决方案1】:

好的,这是线程安全的版本。

    // package, imports ...

    public class Queue {

        // Make 100% sure your QueueItem class is immutable! Otherwise it will not be thread safe!
        // Also, it makes more sense (more readable) to put your QueueItem together with your Queue class 
        public static class QueueItem {
            //...
        }

        // changed the below line - was: private ArrayList<String> mQueueList; 
        // (search for programming against an interface rather than an implementation)
        private List<QueueItem> mQueueList; 
        static Queue mInstance;
        private int currentPlayingItem=0;

        private Queue() {
            // See javadoc on the synchronizedList() method. 
            mQueueList = Collections.synchronizedList(new ArrayList<QueueItem>());
        }

        // Added synchronized keyword below 
        public synchronized static Queue getInstance(){
            if(mInstance == null){
                mInstance = new Queue();
                Log.d(Constants.TAG, "New Instance");
            }
            return mInstance;
        }

        // Thread safe as it is (provided that QueueItem is immutable)
        public void addItem(QueueItem item){
            mQueueList.add(item);
        }

        // Thread safe as it is
        public void removeItem(int position){
            mQueueList.remove(position);
        }

        // This method is actually inherently flawed, remove this method - you should never need 
        // mQueueList - if you do need this method then your code is structured wrong.
        // changed the below line - was: public ArrayList<QueueItem> getQueueList(){
        /*public List<QueueItem> getQueueList(){
            return mQueueList;
        }*/

        // Thread safe as it is (provided that QueueItem is immutable) 
        public QueueItem getQueueItem(int position){
            return mQueueList.get(position);
        }

        // Made this method synchronized (and thus thread safe) - very unlikely to cause performance issue.
        public synchronized int getCurrentPlayingItem() {
            return currentPlayingItem;
        }

        // Made this method synchronized (and thus thread safe) - very unlikely to cause performance issue.
        public synchronized void setCurrentPlayingItem(int currentPlayingItem) {
            this.currentPlayingItem = currentPlayingItem;
        }


    }

【讨论】:

  • 再想一想,removeItem(int position) 并不是严格线程安全的。将方法的签名更改为 removeItem(QueueItem item) 并将方法的实现更改为 mQueueList.remove(item); 以使其成为线程安全的。
  • 哦,谢谢。 :) 我删除了getQueueList(),因为它是获取大小所必需的。我做了一个新功能public int getQueueSize(){ return mQueueList.size(); }
  • 好的。 removeItem(QueueItem item) 也这样做。
  • @Ahmed getQueueSize() 是一个更好的主意! :)
【解决方案2】:

您正在另一个进程中运行您的 Service,这与您的 Application 上下文不同,这就是队列在新进程中由 null 显示的原因。

从清单中的Service 中删除以下内容即可:

android:process=":main"

此外,我建议您在服务中使用HandlerThread 来卸载一些繁重的操作。

【讨论】:

  • 还要注意Queue 的实现不是线程安全的。
  • @Floris 请您分享一些安全的实施示例吗?我需要一个在整个应用程序中可用的队列。
  • @AhmedNawaz 您可以使用Collections.synchronizedList() 使您的列表线程安全。或使用Handler 及其消息队列从列表中添加/删除项目。
  • 好的,我会给它一个 bash,但同时你可以按原样使用它,因为线程不安全的代码只会产生间歇性的问题。另请仔细阅读docs.oracle.com/javase/tutorial/essential/concurrency 上的 Oracle 文档
  • @waqaslam 让我了解这两个。我会试试这个。因为在同一进程上运行服务导致我的应用程序挂起。
猜你喜欢
  • 2019-06-21
  • 1970-01-01
  • 2021-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多