2

ファイル サーバーに Base64 でエンコードされた画像文字列があります。エンコードされた文字列には、一般的な最新のブラウザーでサポートするためのプレフィックス(例: "data:image/png;base64") があります (これは、JavaScript のCanvas.toDataURL() メソッドを介して取得されます)。クライアントは画像のリクエストをサーバーに送信します。サーバーは画像を検証し、Base64 でエンコードされた文字列のストリームを返します。

クライアントが Web クライアントの場合、を Base64 でエンコードされた文字列に<img>設定することで、画像をタグ内にそのまま表示できます。srcただし、クライアントが Android クライアントの場合は、文字列をプレフィックスなしでビットマップにデコードする必要があります。ただし、これはかなり簡単に行うことができます。

問題: コードを単純化して車輪の再発明を避けるために、Android クライアント用の画像ライブラリを使用して、画像の読み込み、表示、およびキャッシュを処理しています (正確にはFacebook のFresco ライブラリ)。ただし、Base64 デコードをサポートするライブラリはないようです (ケーキを食べて食べたいです)。私が思いついた解決策は、クライアントにストリーミングされているときにサーバー上の Base64 文字列をデコードすることです。

試み:

S3Object obj = s3Client.getObject(new GetObjectRequest(bucketName, keyName));
Base64.Decoder decoder = Base64.getDecoder();
//decodes the stream as it is being read
InputStream stream = decoder.wrap(obj.getObjectContent());
try{
    return new StreamingOutput(){
        @Override
        public void write(OutputStream output) throws IOException, WebApplicationException{
            int nextByte = 0;
            while((nextByte = stream.read()) != -1){
                output.write(nextByte);
            }
            output.flush();
            output.close();
            stream.close();
        }
    };
}catch(Exception e){
    e.printStackTrace();
} 

残念ながら、Fresco ライブラリにはまだ画像の表示に問題があります (スタック トレースなし!)。ストリームをデコードするときにサーバーに問題がないように見えるので (スタック トレースもありません)、接頭辞に問題があるに違いないと思います。それは私にジレンマを残します。

質問:ストリーム全体をサーバーに保存および編集せずに、クライアントに送信されるストリームから Base64 プレフィックスを削除するにはどうすればよいですか? これは可能ですか?

4

2 に答える 2

0

サーバーにストリーム全体を保存および編集せずに、クライアントに送信されるストリームから Base64 プレフィックスを削除するにはどうすればよいですか?

ストリームをクライアントに送信する際にプレフィックスを削除するのは、かなり複雑な作業です。サーバーに文字列全体を保存してもかまわない場合は、次のように簡単に実行できます。

BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
    br = new BufferedReader(new InputStreamReader(stream));
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
    String result = sb.toString();
    //comma is the charater which seperates the prefix and the Base64 String
    int i = result.indexOf(",");
    result = result.substring(i + 1);
    //Now, that we have just the Base64 encoded String, we can decode it
    Base64.Decoder decoder = Base64.getDecoder();
    byte[] decoded = decoder.decode(result);
    //Now, just write each byte from the byte array to the output stream
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

しかし、ストリーム全体をサーバーに保存しないでより効率的にするには、はるかに複雑なタスクが作成されます。Base64.Decoder.wrap() メソッドを使用することもできますが、問題は、デコードできない値に達した場合に IOException をスローすることです (バイトをデコードできない場合はどうなりますか?)。残念ながら、Base64 でエンコードされていないため、Base64 プレフィックスはデコードできません。そのため、IOException がスローされます。

この問題を回避するには、 を使用して、指定された適切な でInputStreamReaderを読み取る必要があります。次に、InputStream の read() メソッド呼び出しから受け取った s を sにキャストする必要があります。適切な文字数に達したら、それを Base64 プレフィックスのイントロ (「データ」) と比較する必要があります。一致する場合、ストリームにプレフィックスが含まれていることがわかっているので、プレフィックスの終了文字 (コンマ: ",") に到達するまで読み続けます。最後に、プレフィックスの後のバイトのストリーミングを開始できます。例:InputStreamCharsetintchar

S3Object obj = s3Client.getObject(new GetObjectRequest(bucketName, keyName));
Base64.Decoder decoder = Base64.getDecoder();
InputStream stream = obj.getObjectContent();
InputStreamReader reader = new InputStreamReader(stream);
try{
    return new StreamingOutput(){
        @Override
        public void write(OutputStream output) throws IOException, WebApplicationException{
            //for checking if string has base64 prefix
            char[] pre = new char[4]; //"data" has at most four bytes on a UTF-8 encoding
            boolean containsPre = false;
            int count = 0;
            int nextByte = 0;
            while((nextByte = stream.read()) != -1){
                if(count < pre.length){
                    pre[count] = (char) nextByte;
                    count++;
                }else if(count == pre.length){
                    //determine whether has prefix or not and act accordingly
                    count++;
                    containsPre = (Arrays.toString(pre).toLowerCase().equals("data")) ? true : false;
                    if(!containsPre){
                        //doesn't have Base64 prefix so write all the bytes until this point
                        for(int i = 0; i < pre.length; i++){
                            output.write((int) pre[i]);
                        }
                        output.write(nextByte);
                    }
                }else if(containsPre && count < 25){
                    //the comma character (,) is considered the end of the Base64 prefix
                    //so look for the comma, but be realistic, if we don't find it at about 25 characters
                    //we can assume the String is not encoded correctly
                    containsPre = (Character.toString((char) nextByte).equals(",")) ? false : true;
                    count++;
                }else{
                    output.write(nextByte);
                }
            }
            output.flush();
            output.close();
            stream.close();
        }
    };
}catch(Exception e){
    e.printStackTrace();
    return null;
}

これはサーバー上で行うには少し重い作業のように思えるので、クライアント側でデコードする方が良い選択だと思います。残念ながら、ほとんどの Android クライアント側ライブラリは Base64 デコード (特にプレフィックス付き) をサポートしていません。ただし、@tyronen が指摘したように、文字列が既に取得されている場合、Fresco はそれをサポートします。ただし、これにより、画像読み込みライブラリを使用する主な理由の 1 つがなくなります。

Android クライアント側のデコード

クライアント側のアプリケーションでデコードするのはとても簡単です。最初に InputStream から文字列を取得します。

BufferedReader br = null;
StringBuilder sb = new StringBuilder();
String line;
try {
    br = new BufferedReader(new InputStreamReader(stream));
    while ((line = br.readLine()) != null) {
        sb.append(line);
    }
    return sb.toString();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

次に、Android の Base64 クラスを使用して String をデコードします。

int i = result.indexOf(",");
result = result.substring(i + 1);
byte[] decodedString = Base64.decode(result, Base64.DEFAULT);
Bitmap bitMap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);

Fresco ライブラリは、多くの委譲を使用しているため、更新が難しいようです。そこで、Picasso 画像読み込みライブラリの使用に移り、Base64 デコード機能を備えた独自のフォークを作成しました。

于 2015-08-25T20:35:34.820 に答える