【问题标题】:Create a better Download Service创建更好的下载服务
【发布时间】:2014-08-06 21:52:46
【问题描述】:

我是新的服务(和下载)...我有这个下载服务,可以下载 XML 提要并解析它,但需要一段时间。

我正在从 14 个不同的 URL 下载,该服务从一个 URL 下载,解析 xml,然后向活动发送一条消息,告诉该服务从下一个 URL 重新开始。

我确信有更好的方法来实现这一点,我正在寻求一些建议。这是下载服务的代码,如果您需要其他代码(如主活动中的句柄消息),请告诉我,我会放上去。

因为有人建议我需要一个特定的问题:除了接收来自此下载服务的响应并循环重新启动它直到下载所有 URL 之外,是否有更有效的方法从多个 URL 下载?

    package prs.psesto.rotorss;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;

public class DownloadService extends Service {

    // XML node keys
    private final String KEY_ITEM = "item"; // parent node
    private final String KEY_GUID = "guid";
    private final String KEY_LINK = "link";
    private final String KEY_TITLE = "title";
    private final String KEY_DESCRIPTION = "description";
    private final String KEY_UPDATED = "a10:updated";

    public static final String nameSpace = null;

    private static String[] sportArray = { "nfl", "mlb", "nba", "nhl", "bpl",
            "cfb", "gol", "nas" };

    private final int PLAYER_NEWS = 0;
    private final int ARTICLES = 1;

    private final String THREAD_PLAYER_NEWS = "THREAD_PLAYER_NEWS";
    private final String THREAD_ARTICLES = "THREAD_ARTICLES";

    public static enum DownloadType {
        PLAYER_NEWS, ARTICLES
    };

    /**
     * Looper associated with the HandlerThread.
     */
    private volatile Looper mServiceLooper;

    /**
     * Processes Messages sent to it from onStartCommnand() that
     */
    private volatile ServiceHandler mServiceHandler;

    /**
     * Hook method called when DownloadService is first launched by the Android
     * ActivityManager.
     */
    public void onCreate() {
        super.onCreate();
        Log.d("DownloadService", "onCreate");

        // Create and start a background HandlerThread since by
        // default a Service runs in the UI Thread, which we don't
        // want to block.
        HandlerThread thread = new HandlerThread("DownloadService");
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler.
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    /**
     * Hook method called each time a Started Service is sent an Intent via
     * startService().
     */
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("DownloadService", "onStartCommand");

        // Create a Message that will be sent to ServiceHandler to
        // retrieve animagebased on the URI in the Intent.
        Message message = mServiceHandler.makeDownloadMessage(intent, startId);

        // Send the Message to ServiceHandler to retrieve an image
        // based on contents of the Intent.
        mServiceHandler.sendMessage(message);

        // Don't restart the DownloadService automatically if its
        // process is killed while it's running.
        return Service.START_NOT_STICKY;
    }

    /**
     * Helper method that returns sport if download succeeded.
     */
    public static int getSportIndex(Message message) {
        Log.d("DownloadService", "getSportIndex");

        // Extract the data from Message, which is in the form
        // of a Bundle that can be passed across processes.
        Bundle data = message.getData();

        // Extract the pathname from the Bundle.
        int sport = data.getInt("SPORTCODE");
        Log.d("DownloadService", "sport = " + String.valueOf(sport));

        // Check to see if the download succeeded.
        if (message.arg1 == 9)
            return 9;
        else
            return sport;
    }

    /**
     * Helper method that returns sport if download succeeded.
     */
    public static int getTypeIndex(Message message) {
        Log.d("DownloadService", "getTypeIndex");

        // Extract the data from Message, which is in the form
        // of a Bundle that can be passed across processes.
        Bundle data = message.getData();

        // Extract the pathname from the Bundle.
        int type = data.getInt("TYPECODE");
        Log.d("DownloadService", "type = " + String.valueOf(type));

        // Check to see if the download succeeded.
        if (message.arg1 == 9)
            return 9;
        else
            return type;
    }

    /**
     * Factory method to make the desired Intent.
     */
    public static Intent makeIntent(Context context, int type, int sport,
            Handler downloadHandler) {
        Log.d("DownloadService", "makeIntent");
        // Create the Intent that's associated to the DownloadService
        // class.
        Intent intent = new Intent(context, DownloadService.class);

        // Pathname for the downloaded image.
        intent.putExtra("TYPECODE", type);
        intent.putExtra("SPORTCODE", sport);

        // Create and pass a Messenger as an "extra" so the
        // DownloadService can send back the pathname.
        intent.putExtra("MESSENGER", new Messenger(downloadHandler));
        return intent;
    }

    /**
     * @class ServiceHandler
     *
     * @brief An inner class that inherits from Handler and uses its
     *        handleMessage() hook method to process Messages sent to it from
     *        onStartCommnand() that indicate which url to download.
     */
    private final class ServiceHandler extends Handler {
        /**
         * Class constructor initializes the Looper.
         * 
         * @param Looper
         *            The Looper that we borrow from HandlerThread.
         */
        public ServiceHandler(Looper looper) {
            super(looper);
            log("public ServiceHandler(Looper looper)");

        }

        /**
         * A factory method that creates a Message to return to the
         * DownloadActivity with the downloaded sport and type.
         */
        private Message makeReplyMessage(int type, int sport, int result) {
            log("makeReplyMessage");

            Message message = Message.obtain();

            Bundle data = new Bundle();

            // Pathname for the downloaded image.
            data.putInt("TYPECODE", type);
            data.putInt("SPORTCODE", sport);
            message.arg1 = result;

            message.setData(data);
            return message;
        }

        /**
         * A factory method that creates a Message that contains information on
         * how to stop the Service.
         */
        private Message makeDownloadMessage(Intent intent, int startId) {
            log("makeDownloadMessage");

            Message message = Message.obtain();
            // Include Intent & startId in Message to indicate which URI
            // to retrieve and which request is being stopped when
            // download completes.
            message.obj = intent;
            message.arg1 = startId;
            return message;
        }

        /**
         * Retrieves the download response and sport/type and replies to the
         * DownloadActivity via the Messenger sent with the Intent.
         */
        private void downloadAndReply(Intent intent) {
            log("downloadAndReply");

            // Download the requested image.
            int sport = intent.getExtras().getInt("SPORTCODE");
            int type = intent.getExtras().getInt("TYPECODE");

            int result = downloadAndParse(type, sport);

            // Extract the Messenger.
            Messenger messenger = (Messenger) intent.getExtras().get(
                    "MESSENGER");

            // Send the pathname via the messenger.
            sendResult(messenger, type, sport, result);
        }

        /**
         * Send the result back to the DownloadActivity via the messenger.
         */
        private void sendResult(Messenger messenger, int type, int sport,
                int result) {
            log("sendResult");

            // Call factory method to create Message.
            Message message = makeReplyMessage(type, sport, result);

            try {
                // Send pathname to back to the DownloadActivity.
                messenger.send(message);
            } catch (RemoteException e) {
                Log.e(getClass().getName(), "Exception while sending.", e);
            }
        }

        public int downloadAndParse(int typeIndex, int sportIndex) {
            log("downloadAndParse");

            long id = 0;
            String link = null;
            int linkType = ARTICLES;
            String title = null;
            String description = null;
            String updated = null;

            String sport = sportArray[sportIndex];

            try {
                InputStream stream = null;

                try {
                    stream = downloadUrl(getUrl(getDlType(typeIndex), sport));

                    XmlPullParserFactory factory = XmlPullParserFactory
                            .newInstance();
                    factory.setNamespaceAware(false);
                    XmlPullParser xpp = factory.newPullParser();
                    xpp.setInput(stream, null);
                    boolean insideItem = false;

                    // Returns the type of current event: START_TAG,
                    // END_TAG,
                    // etc..
                    int eventType = xpp.getEventType();
                    while (eventType != XmlPullParser.END_DOCUMENT) {
                        if (eventType == XmlPullParser.START_TAG) {

                            if (xpp.getName().equalsIgnoreCase(KEY_ITEM)) {
                                insideItem = true;
                            } else if (xpp.getName().equalsIgnoreCase(KEY_GUID)) {
                                if (insideItem) {
                                    id = Long.valueOf(String.valueOf(
                                            xpp.nextText()).replace(" ", ""));
                                }
                            } else if (xpp.getName().equalsIgnoreCase(KEY_LINK)) {
                                if (insideItem) {
                                    link = xpp.nextText();

                                    if (link.contains("player")) {
                                        linkType = PLAYER_NEWS;
                                    }
                                }
                            } else if (xpp.getName()
                                    .equalsIgnoreCase(KEY_TITLE)) {
                                if (insideItem) {
                                    title = xpp.nextText();
                                }
                            } else if (xpp.getName().equalsIgnoreCase(
                                    KEY_DESCRIPTION)) {
                                if (insideItem) {
                                    description = xpp.nextText();
                                }
                            } else if (xpp.getName().equalsIgnoreCase(
                                    KEY_UPDATED)) {
                                if (insideItem) {
                                    updated = xpp.nextText();
                                }
                            }

                        } else if (eventType == XmlPullParser.END_TAG
                                && xpp.getName().equalsIgnoreCase("item")) {
                            insideItem = false;
                        }

                        eventType = xpp.next(); // / move to next element
                        RotoItem rItem = DatabaseManager.sInstance.newRotoItem(
                                id, sport, link, linkType, title, description,
                                updated);

                        DatabaseManager.sInstance.addRotoItem(rItem);
                    }

                    // Makes sure that the InputStream is closed after the
                    // class
                    // is
                    // finished using it.
                } finally {

                    if (stream != null) {
                        stream.close();
                    }
                }
            } catch (IOException e) {
                Log.e("DownloadXml - performDownload", "Connection Error");
                Log.e("IOException", e.toString());
                return 9;
            } catch (XmlPullParserException e) {
                Log.e("DownloadXml - performDownload", "Error in data set");
                Log.e("XmlPullParserException", e.toString());
                return 9;
            }
            Log.d("DownloadData", "sportIndex = " + String.valueOf(sportIndex));

            return sportIndex;

        }

        public DownloadType getDlType(int typeIndex) {
            if (typeIndex == PLAYER_NEWS) {
                return DownloadType.PLAYER_NEWS;
            } else {
                return DownloadType.ARTICLES;
            }
        }

        public String getUrl(DownloadType downloadType, String sport) {

            if (downloadType == DownloadType.PLAYER_NEWS) {
                return "http://www.rotoworld.com/rss/feed.aspx?sport=" + sport
                        + "&ftype=news&count=12&format=rss";
            } else {
                return "http://www.rotoworld.com/rss/feed.aspx?sport=" + sport
                        + "&ftype=article&count=12&format=rss";
            }
        }

        // Given a string representation of a URL, sets up a connection and gets
        // an input stream.
        private InputStream downloadUrl(String urlString) throws IOException {

            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(10000 /* milliseconds */);
            conn.setConnectTimeout(15000 /* milliseconds */);
            conn.setRequestMethod("GET");
            conn.setDoInput(true);
            // Starts the query
            conn.connect();
            return conn.getInputStream();
        }

        public void handleMessage(Message message) {
            log("handleMessage");

            // Download the designated image and reply to the
            // DownloadActivity via the Messenger sent with the
            // Intent.
            downloadAndReply((Intent) message.obj);

            // Stop the Service using the startId, so it doesn't stop
            // in the middle of handling another download request.
            stopSelf(message.arg1);
        }
    }

    /**
     * Hook method called back to shutdown the Looper.
     */
    public void onDestroy() {
        log("onDestroy");

        mServiceLooper.quit();
    }

    /**
     * This hook method is a no-op since we're a Start Service.
     */
    public IBinder onBind(Intent arg0) {
        log("IBinder");
        return null;
    }

    public void log(String message) {
        Log.d("DownloadService", message);
    }
}

【问题讨论】:

  • Stack Overflow 用于编程问题。您的具体问题是什么?
  • 除了接收来自该下载服务的响应并重新启动直到所有 url 下载完毕之外,还有更有效的方法从多个 URL 下载吗?

标签: android android-service androidhttpclient


【解决方案1】:

除了接收来自该下载服务的响应并重新启动直到所有 url 下载完毕之外,还有更有效的方法从多个 URL 下载吗?

使用ThreadPoolExecutor 并并行运行几个线程。转储你的Messenger-和-HandlerThread 的东西,让onStartCommand() 把每项工作交给ThreadPoolExecutor。使用打包的事件总线(例如,greenrobot 的 EventBus,LocalBroadcastManager)让您的 UI 层了解已完成的工作。

ThreadPoolExecutor 逻辑将是相当标准的 Java。唯一的 Android-isms 将是 onStartCommand() 是工作的接收者并使用事件总线来传递结果。

【讨论】:

  • 我还没有阅读线程池执行程序。我知道我可以创建的线程数没有硬性限制,但是我应该保持在推荐的数量以下吗?
  • @WizardKnight:我会把它保持在 ~4 开始并从那里开始实验。
  • 我会这样做的。非常感谢
  • 这比我想象的要容易,GreenRobot 的 EventBus 是一个绝妙的建议。我已经在使用 greendao,所以我很惊讶我忽略了他们的其他产品。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-05
  • 2011-12-05
  • 2021-07-15
相关资源
最近更新 更多