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 を機能させましたが、これを行うための「より正しい」方法があるかどうか疑問に思っています。これを行う他の方法が見つからなかったので、次のクラスも実用的な例として役立ちます。少しハックのようですが、うまく機能します。

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

更新: これはニコライからの提案を使用したクラスの完成版です。結局、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

コンテンツの長さはバイト単位で報告されるため、他に方法はありません。を使用したい場合は、StringReader読み取った各行の長さを取得し、読み取った合計バイト数を計算して同じことを達成できます。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 に答える