Android:HttpURLConnection 字符串数据的 ProgressBar

标签 android progress-bar httpurlconnection stringbuilder simpledateformat

我正在从 Web 服务获取 JSON 数据,并希望在下载数据时显示进度条。我见过的所有示例都像这样使用 StringBuilder:

//Set up the initial connection
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setDoOutput(true);
connection.setReadTimeout(10000);

connection.connect();

InputStream stream = connection.getInputStream();

//read the result from the server
reader  = new BufferedReader(new InputStreamReader(stream));
StringBuilder builder = new StringBuilder();
String line = "";
while ((line = reader.readLine()) != null) {
    builder.append(line + '\n');
}

result = builder.toString();

我通过将数据下载为字节数组,然后将字节数组转换为字符串来让 ProgressBar 工作,但我想知道是否有“更正确”的方法来执行此操作。由于我没有找到其他方法来做到这一点,下面的类也可以作为一个工作示例,看起来有点 hack,但它确实工作得很好。

package com.royaldigit.newsreader.services;

import android.os.AsyncTask;
import android.util.Log;

import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.royaldigit.newsreader.controller.commands.CommandInterface;
import com.royaldigit.newsreader.model.data.SearchResultDO;
import com.royaldigit.newsreader.model.data.SearchTermDO;

/**
 * Gets news results from Feedzilla based on the search term currently stored in model.searchTermDO
 * 
 * Sends progress update and returns results to the CommandInterface command reference:
 * * command.onProgressUpdate(progress);
 * * command.serviceComplete(results);
 *
 *
 */
public class FeedzillaSearchService {
    private static final String TAG = "FeedzillaSearchService";
    private static final String SERVICE_URI = "http://api.feedzilla.com/v1/categories/26/articles/search.json?q=";
    private static final int STREAM_DIVISIONS = 10;

    private CommandInterface command;
    private SearchTermDO currentSearchTermDO;
    private Integer maximumResults;
    private DownloadTask task;
    private ArrayList<SearchResultDO> results;
    public Boolean isCanceled = false;

    public void getData(CommandInterface cmd, SearchTermDO termDO, Integer maxResults){
        command = cmd;
        currentSearchTermDO = termDO;
        //Feedzilla only allows count to be 100 or less, anything over throws an error
        maximumResults = (maxResults > 100)? 100 : maxResults;
        results = new ArrayList<SearchResultDO>();
        task = new DownloadTask();
        task.execute();
    }

    public void cancel() {
        isCanceled = true;
        if(task != null) task.cancel(true);
    }

    /**
     * Handle GET request
     *
     */
    private class DownloadTask extends AsyncTask<Void, Integer, String> {
        @Override
        protected String doInBackground(Void...voids) {
            String result = "";
            if(currentSearchTermDO == null || currentSearchTermDO.term.equals("")) return result;

            BufferedReader reader = null;

            publishProgress(0);

            try {
                String path = SERVICE_URI + URLEncoder.encode(currentSearchTermDO.term, "UTF-8") + "&count=" + maximumResults;
                Log.d(TAG, "path = "+path);
                URL url = new URL(path);

                //Set up the initial connection
                HttpURLConnection connection = (HttpURLConnection)url.openConnection();
                connection.setRequestMethod("GET");
                connection.setDoOutput(true);
                connection.setReadTimeout(10000);

                connection.connect();

                int length = connection.getContentLength();
                InputStream stream = connection.getInputStream();
                byte[] data = new byte[length];
                int bufferSize = (int) Math.ceil(length / STREAM_DIVISIONS);
                int progress = 0;
                for(int i = 1; i < STREAM_DIVISIONS; i++){
                    int read = stream.read(data, progress, bufferSize);
                    progress += read;
                    publishProgress(i);
                }
                stream.read(data, progress, length - progress);
                publishProgress(STREAM_DIVISIONS);

                result = new String(data);

            } catch (Exception e) {
                Log.e(TAG, "Exception "+e.toString());
            } finally {
                if(reader != null){
                    try {
                        reader.close();
                    } catch(IOException ioe) {
                        ioe.printStackTrace();
                    }
                }
            }
            return result;
        }

        protected void onProgressUpdate(Integer... progress) {
            int currentProgress = progress[0] * 100/STREAM_DIVISIONS;
            if(!this.isCancelled()) command.onProgressUpdate(currentProgress);
        }

        @Override
        protected void onPostExecute(String result){
            if(!this.isCancelled()) downloadTaskComplete(result);
        }
    }

    /**
     * 
     * @param data
     */
    private void downloadTaskComplete(Object data){
        if(!isCanceled){
            try {
                Log.d(TAG, data.toString());
                JSONObject obj = new JSONObject(data.toString());

                JSONArray array = obj.getJSONArray("articles");

                for(int i = 0; i < array.length(); i++){
                    SearchResultDO dataObj = new SearchResultDO();
                    dataObj.title       = array.getJSONObject(i).getString("title");
                    dataObj.url         = array.getJSONObject(i).getString("url");
                    dataObj.snippet     = array.getJSONObject(i).getString("summary");
                    dataObj.source      = array.getJSONObject(i).getString("source");
                    dataObj.date        = array.getJSONObject(i).getString("publish_date");
                    dataObj.termId      = currentSearchTermDO.id;

                    //Reformat date
                    SimpleDateFormat format1 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
                    try {
                        Date date = format1.parse(dataObj.date);
                        SimpleDateFormat format2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        dataObj.date = format2.format(date);
                    } catch(ParseException pe) {
                        Log.e(TAG, pe.getMessage());
                    }

                    results.add(dataObj);
                }
                command.serviceComplete(results);
            } catch(JSONException e){
                Log.e(TAG, e.toString());
                command.serviceComplete(results);
            }   
        }
    }
}

更新:这是使用 Nikolay 的建议完成的类(class)版本。毕竟我最终使用了 StringBuilder。以前的版本会中断,因为有时 connection.getContentLength() 会返回 -1。对于这种情况,此版本会优雅地降级。对这个实现进行了大量测试,它似乎是万无一失的。

package com.royaldigit.newsreader.services;

import android.os.AsyncTask;
import android.util.Log;

import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.royaldigit.newsreader.controller.commands.CommandInterface;
import com.royaldigit.newsreader.model.data.SearchResultDO;
import com.royaldigit.newsreader.model.data.SearchTermDO;

/**
 * Gets news results from Feedzilla based on the search term currently stored in model.searchTermDO
 * 
 * Sends progress update and returns results to the CommandInterface command reference:
 * * command.onProgressUpdate(progress);
 * * command.serviceComplete(results);
 *
 */
public class FeedzillaSearchService implements SearchServiceInterface {
    private static final String TAG = "FeedzillaSearchService";
    private static final String SERVICE_URI = "http://api.feedzilla.com/v1/categories/26/articles/search.json?q=";

    private CommandInterface command;
    private SearchTermDO currentSearchTermDO;
    private Integer maximumResults;
    private DownloadTask task;
    private ArrayList<SearchResultDO> results;
    private Boolean isCanceled = false;

    public void getData(CommandInterface cmd, SearchTermDO termDO, Integer maxResults){
        command = cmd;
        currentSearchTermDO = termDO;
        //Feedzilla only allows count to be 100 or less, anything over throws an error
        maximumResults = (maxResults > 100)? 100 : maxResults;
        results = new ArrayList<SearchResultDO>();
        task = new DownloadTask();
        task.execute();
    }

    public void cancel() {
        isCanceled = true;
        if(task != null) task.cancel(true);
    }

    /**
     * Handle GET request
     *
     */
    private class DownloadTask extends AsyncTask<Void, Integer, String> {
        @Override
        protected String doInBackground(Void...voids) {
            String result = "";
            if(currentSearchTermDO == null || currentSearchTermDO.term.equals("")) return result;

            BufferedReader reader = null;

            publishProgress(0);

            try {
                String path = SERVICE_URI + URLEncoder.encode(currentSearchTermDO.term, "UTF-8") + "&count=" + maximumResults;
                Log.d(TAG, "path = "+path);
                URL url = new URL(path);

                //Set up the initial connection
                HttpURLConnection connection = (HttpURLConnection)url.openConnection();
                connection.setRequestMethod("GET");
                connection.setDoOutput(true);
                connection.setReadTimeout(20000);
                connection.connect();

                //connection.getContentType() should return something like "application/json; charset=utf-8"
                String[] values = connection.getContentType().toString().split(";");
                String charset = "";
                for (String value : values) {
                    value = value.trim();
                    if (value.toLowerCase().startsWith("charset=")) {
                        charset = value.substring("charset=".length());
                        break;
                    }
                }
                //Set default value if charset not set
                if(charset.equals("")) charset = "utf-8";

                int contentLength = connection.getContentLength();
                InputStream stream = connection.getInputStream();
                reader  = new BufferedReader(new InputStreamReader(stream));
                StringBuilder builder = new StringBuilder();
                /**
                 * connection.getContentLength() can return -1 on some connections.
                 * If we have the content length calculate progress, else just set progress to 100 and build the string all at once.
                 * 
                 */
                if(contentLength>-1){
                    //Odd byte array sizes don't always work, tried 512, 1024, 2048; 1024 is the magic number because it seems to work best.
                    byte[] data = new byte[1024];
                    int totalRead = 0;
                    int bytesRead = 0;
                    while ((bytesRead = stream.read(data)) > 0) {
                        try {
                            builder.append(new String(data, 0, bytesRead, charset));
                        } catch (UnsupportedEncodingException e) {
                            Log.e(TAG, "Invalid charset: " + e.getMessage());
                            //Append without charset (uses system's default charset)
                            builder.append(new String(data, 0, bytesRead));
                        }
                        totalRead += bytesRead;
                        int progress = (int) (totalRead * (100/(double) contentLength));
                        //Log.d(TAG, "length = " + contentLength + " bytesRead = " + bytesRead + " totalRead = " + totalRead + " progress = " + progress);
                        publishProgress(progress);
                    }
                } else {
                    String line = "";
                    while ((line = reader.readLine()) != null) {
                        builder.append(line + '\n');
                        publishProgress(100);
                    }
                }
                result = builder.toString();

            } catch (Exception e) {
                Log.e(TAG, "Exception "+e.toString());
            } finally {
                if(reader != null){
                    try {
                        reader.close();
                    } catch(IOException ioe) {
                        ioe.printStackTrace();
                    }
                }
            }
            return result;
        }

        protected void onProgressUpdate(Integer... progress) {
            if(!this.isCancelled()) command.onProgressUpdate(progress[0]);
        }

        @Override
        protected void onPostExecute(String result){
            if(!this.isCancelled()) downloadTaskComplete(result);
        }
    }

    /**
     * 
     * @param data
     */
    private void downloadTaskComplete(Object data){
        if(!isCanceled){
            try {
                Log.d(TAG, data.toString());
                JSONObject obj = new JSONObject(data.toString());

                JSONArray array = obj.getJSONArray("articles");

                for(int i = 0; i < array.length(); i++){
                    SearchResultDO dataObj = new SearchResultDO();
                    dataObj.title       = array.getJSONObject(i).getString("title");
                    dataObj.url         = array.getJSONObject(i).getString("url");
                    dataObj.snippet     = array.getJSONObject(i).getString("summary");
                    dataObj.source      = array.getJSONObject(i).getString("source");
                    dataObj.date        = array.getJSONObject(i).getString("publish_date");
                    dataObj.termId      = currentSearchTermDO.id;

                    //Reformat date
                    SimpleDateFormat format1 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z");
                    try {
                        Date date = format1.parse(dataObj.date);
                        SimpleDateFormat format2 = new SimpleDateFormat(SearchResultDO.DATE_FORMAT_STRING);
                        dataObj.date = format2.format(date);
                    } catch(ParseException pe) {
                        Log.e(TAG, pe.getMessage());
                    }

                    results.add(dataObj);
                }
            } catch(JSONException e){
                Log.e(TAG, e.toString());
            }
            command.serviceComplete(results);
        }
    }
}

最佳答案

好吧,由于内容长度是以字节为单位报告的,所以真的没有其他办法。如果您想使用 StringReader,您可以获取您读取的每一行的长度并计算读取的总 字节数 以实现相同的目的。此外,常规习惯用法是检查 read() 的返回值,以检查您是否已到达流的末尾。如果出于某种原因,内容长度错误,您的代码可能会读取比可用数据更多/更少的数据。最后,将字节 blob 转换为字符串时,您应该明确指定编码。在处理 HTTP 时,您可以从“Content-Type” header 的“charset”参数中获取它。

关于Android:HttpURLConnection 字符串数据的 ProgressBar,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10808893/

相关文章:

java - 使用 Java 线程的异步调用

android - 将Android Studio更新到版本4.1之后原因:重复输入:META-INF/new_core_debug.kotlin_module发生此错误?如何解决

android - HttpURLConnection getResponseCode 未返回

android - 将 Android 构建发送到 codenameOne Buildserver 时出错

android - LoganSquare 解析 Android 库 : feedback, 基准,优点,缺点

wpf - 使用WPF中的TaskBarItemInfo作为Win 7任务栏中的进度条

java - 在 onclick 方法中实现进度条时出现问题

javascript - 在长时间的 Ajax 调用中显示进度

Java - 连接到亚马逊

java - HttpURLConnection 获取连接