4

Androidデバイスから.NetWebサービスにいくつかの大きなファイルをアップロードしようとしています。このWebサービスは、これらのファイルをPOSTパラメーターとして受け入れ、ファイルをBase64でエンコードされた文字列として送信する必要があるように設定されています。

私はChristiand'Heureuseによってこのライブラリを使用して、ファイルをBase64文字列に変換し、文字列のサイズをバイト単位で計算して以前に送信することができましたが、以前に使用した方法では、ファイル全体をメモリにロードする必要がありました。大きなファイルを処理するときにメモリ不足エラーが発生していましたが、これは予期しないことではありませんでした。

私はファイルをチャンクでBase64に変換し、変換中に接続を介して(データ出力ストリームオブジェクトを使用して)このデータをストリーミングしようとしているので、ファイル全体を一度にメモリにロードする必要はありません。ファイルを変換する前に、リクエストのContent-Lengthのサイズを正確に計算できないようです(通常は約10バイトのようです)。イライラすることに、ときどき機能します。

また、これが機能する場合、サーバーが次のエラーメッセージ「Base64文字配列のサイズが無効です」を返すこともわかりました。これは文字のパディングの問題だと思いますが、コードの問題がうまくいくことはわかりません。この問題に関するアドバイスをいただければ幸いです。

これは、リクエストを生成してデータをストリーミングするコードです。

    try
{


    HttpURLConnection connection = null;

    DataOutputStream outputStream = null;

    DataInputStream inputStream = null;

    //This is the path to the file
    String pathToOurFile = Environment
            .getExternalStorageDirectory().getPath()
            + "/path/to/the/file.zip";

    String urlServer = "https://www.someserver.com/somewebservice/";

    int bytesRead, bytesAvailable, bufferSize;

    byte[] buffer;

    int maxBufferSize = 456;


    //The parameters of the POST request - File Data is the file in question as a Base64 String
    String params = "Username=foo&Password=bar&FileData=";

    File sizeCheck = new File(pathToOurFile);

    Integer zipSize = (int) sizeCheck.length();

    Integer paddingRequired = ((zipSize * 8) / 6) % 3;

    Integer base64ZipSize = ((zipSize * 8) / 6)
            + ((zipSize * 8) / 6) % 3;

    Integer paramLength = params.getBytes().length;

    //Code to work out the number of lines required, assuming we create a new
    //line every 76 characters - this is used t work out the number of
    //extra bytes required for new line characters
    Integer numberOfLines = base64ZipSize / 76;

    Log.i(TAG, "numberOfLines: " + numberOfLines);

    Integer newLineLength = System.getProperty("line.separator")
            .getBytes().length;

    //This works out the total length of the Content
    Integer totalLength = paramLength + base64ZipSize
            + (numberOfLines * newLineLength) + paddingRequired;

    Log.i(TAG, "total Length: " + totalLength);

    FileInputStream fileInputStream = new FileInputStream(new File(
            pathToOurFile));

    URL url = new URL(urlServer);

    connection = (HttpURLConnection) url.openConnection();

    connection.setDoInput(true);

    connection.setDoOutput(true);

    connection.setUseCaches(false);

    connection.setRequestMethod("POST");

    connection.setRequestProperty("Connection", "Keep-Alive");

    connection.setRequestProperty("Content-Type",
            "application/x-www-form-urlencoded;");

    connection.setRequestProperty("Content-Length", ""
            + totalLength); // number of bytes

    outputStream = new DataOutputStream(
            connection.getOutputStream());

    //Write out the parameters to the data output stream
    outputStream.writeBytes(params);

    bytesAvailable = fileInputStream.available();

    bufferSize = Math.min(bytesAvailable, maxBufferSize);

    buffer = new byte[bufferSize];

    bytesRead = fileInputStream.read(buffer, 0, bufferSize);

    Integer totalSent = paramLength;

    Integer enLen = 0;


    //Convert the file to Base64 and Stream the result to the
    //Data output stream
    while (bytesRead > 0)
    {
        String convetedBase64 = Base64Coder.encodeLines(buffer);
        convetedBase64 = convetedBase64.replace("=", "");

        if (totalSent >= (totalLength - 616))
        {
            Log.i(TAG, "about to send last chunk of data");
            convetedBase64 = convetedBase64.substring(0,
                    convetedBase64.length() - 1);
        }

        Log.i(TAG,
                "next data chunk to send: "
                        + convetedBase64.getBytes().length);
        Log.i(TAG, "'" + convetedBase64 + "'");

        enLen = enLen + convetedBase64.length();
        outputStream.writeBytes(convetedBase64);

        totalSent = totalSent + convetedBase64.getBytes().length;

        Log.i(TAG, "total sent " + totalSent);
        Log.i(TAG, "actual size: " + outputStream.size());

        bytesAvailable = fileInputStream.available();
        bufferSize = Math.min(bytesAvailable, maxBufferSize);
        buffer = new byte[bufferSize];
        bytesRead = fileInputStream.read(buffer, 0, bufferSize); // read
                                                                    // into
                                                                    // the
                                                                    // buffer
    }

    Log.i(TAG, "enLen: " + enLen);
    Log.i(TAG, "paddingRequired: " + paddingRequired);

    for (int x = 0; x < paddingRequired; x++)
    {
        outputStream.writeBytes("=");
    }

    InputStream is2 = connection.getInputStream();
    String output = IOUtils.toString(is2);
    Log.i(TAG, "Got server response: " + output);
    fileInputStream.close();
    outputStream.flush();
    outputStream.close();
}
catch (Exception ex)
{
    Log.e(TAG, "caught an exception:" + ex.getMessage());
}

誰かがこれを引き起こしている可能性のある私のコードのエラーを指摘したり、ファイルを変換してアップロードするためのより良い方法を提案したりできれば、私は非常に感謝しています。

4

1 に答える 1

1

すっごく...誰かがこの質問につまずいた場合に備えて、私はこの問題のいくつかの解決策を見つけることができました。ここに残しておきます。

1つ目は、データを一時ファイルに書き出すことでした。これにより、変換後のサイズをバイト単位で取得できました。最初は良いアイデアのように見えましたが、非効率的で、他の方法を見つけた後はばかげているように見えました。

もう1つの方法は、コンテンツの長さを指定しないことです(これができるとは知りませんでした!)。残念ながら、Androidはまだアップロードに十分なメモリを割り当てようとしたため、問題が発生しました。

ChunkedStreamingMode Androidを使用する接続を指定すると、Androidが適切に再生され、アップロードがバッファリングされ、使用されるRAMが節約されます(2.2の奇妙なバグは別として)。

このためのコードは次のとおりです。

httppost.setDoInput(true);
httppost.setDoOutput(true);
httppost.setRequestMethod("POST");
httppost.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httppost.setChunkedStreamingMode(0); //This specifies the size of the chunks - 0 uses the system default

DataOutputStream dos = new DataOutputStream(httppost.getOutputStream());
dos.writeBytes(content); //This code write out the rest of the post body first, for example username or password

try
{
    BufferedInputStream dis = new BufferedInputStream(new FileInputStream("path/to/some/file"), 2048);

    byte[] in = new byte[512];

    while (dis.read(in)) > 0)
    {
        dos.write(Base64.encode(in, Base64.URL_SAFE)); //This writes out the Base64 data
                                                       //I used the web safe base64 to save URL encoding it again
                                                       //However your server must support this
        dos.write(newLine); //this write out a newline character
    }

dos.flush();
dis.close();
dos.close();

これがお役に立てば幸いです!!!

于 2012-11-13T20:21:38.463 に答える