3

我正在从 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 建议的课程的完成版本。毕竟我最终使用了 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);
        }
    }
}
4

2 回答 2

2

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

于 2012-05-30T02:15:55.093 回答
1

我有类似的问题。我尝试了 Jeremy C 的解决方案,但它不准确,因为标题中的“Content-Length”值可能与真实数据有很大不同。

我的解决方案是:

  1. 从服务器发送 HTTP 标头 (PHP):

    $string = json_encode($data, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT);
    header("X-Size: ".strlen($string)); //for example with name: "X-Size"
    print($string);
    
  2. 在从流中读取之前,从 HTTP 标头中读取 contentLength 变量的正确值“X-size”:

    protected String doInBackground(URL... urls) {
        if (General.DEBUG) Log.i(TAG, "WebAsyncTask(doInBackground)");
    
        String result = "";
        BufferedReader reader = null;
    
        try {
            HttpURLConnection conn = (HttpURLConnection) urls[0].openConnection();
            conn.setConnectTimeout(General.TIMEOUT_CONNECTION);
            conn.setReadTimeout(General.TIMEOUT_SOCKET);
            conn.setRequestMethod("GET");
            conn.connect();
    
            if (General.DEBUG) Log.i(TAG, "X-Size: "+conn.getHeaderField("X-Size"));    
            if (General.DEBUG) Log.i(TAG, "getHeaderField: "+conn.getHeaderFields());   
    
            if(conn.getResponseCode() != General.HTTP_STATUS_200)
                return General.ERR_HTTP;
    
            int contentLength = -1;
            try {
                contentLength = Integer.parseInt(conn.getHeaderField("X-Size"));
            } catch (Exception e){
                e.printStackTrace();
            }
    
            InputStream stream = conn.getInputStream();
            reader  = new BufferedReader(new InputStreamReader(stream));
            StringBuilder builder = new StringBuilder();
    
            //Pokud delku zname:
            if(contentLength > -1){
                byte[] data = new byte[16]; //TODO
                int totalRead = 0;
                int bytesRead = 0;
    
                while ((bytesRead = stream.read(data)) > 0){
    
                    Thread.sleep(100);  //DEBUG TODO
    
                    try {
                        builder.append(new String(data, 0, bytesRead, "UTF-8"));
                    } catch (UnsupportedEncodingException e) {
                        Log.i(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.i(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 (SocketException | SocketTimeoutException e){
            if (General.DEBUG) Log.i(TAG, "SocketException or SocketTimeoutException");
            e.printStackTrace();
            return General.HTTP_TIMEOUT;
        } catch (Exception e){
            e.printStackTrace();
            return General.ERR_HTTP;
        } finally {
            if (reader != null) {
                try {
                  reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        return result;
    }
    
于 2015-05-19T20:15:13.423 回答