1つ前の記事で画像をアップロードできたものの、同じファイルを上書きしてしまう(使えねぇ!!)なので、ファイル名もアップロードするデータに追加して、PHPサーバー側で取り出して、別々のファイルに保存するように改造をします。
まず、Androidアプリから画像を受け取るphpファイル(Windowsにあるよ)を改造します。tomcatをC:\tomcatにインストールしている場合は、 C:\tomcat\htdocs\index.phpというファイルにします。
<?php $data = file_get_contents("php://input"); $bytes = strlen($data); $name_part = substr($data, 0, 80); $name_part_str = ""; foreach (str_split($name_part) as $chr) { if ($chr != "\0") { $name_part_str .= $chr; } } $image = substr($data, 80, $bytes - 80); $fname = "C:/temp/" . $name_part_str . ".jpg"; $fp = fopen($fname, 'wb'); fwrite($fp, $image); fclose($fp); echo "Image Upload Finish.\n"; ?>
受け取ったデータをsubstr()で先頭の80byteを$name_partに取り出します。$dataはbyte配列なのに、なぜかsubstr()で切れて、結果は文字列になります(ややこしい)。このままファイル名に使えると便利なのですが、null文字が含まれていて、エラーになってしまいます。
str_split()で文字列をbyte配列に戻して(ややこしい)foreachで1つずつ取り出して$chrに保存。null文字を除く文字を$name_part_str .= $chr;でくっつけます。文字結合は+=じゃなくて.=なのです。
画像の本体は、substr($data, 80, $bytes - 80); で80byte目から、最後までを$imageに取り出します。
ファイル名は、$fname = "C:/temp/" . $name_part_str . ".jpg"; で作ります。文字結合は + じゃなくて . なのです。
後は、1つ前の記事と同じで、fopen()でファイルを開いてfwrite()で画像データを保存します。
続いてandroidアプリを改造。前回は送信するデータは画像だけでしたが、データの先頭80byteにファイル名を入れます。ファイル名は、ディレクトリと拡張子を除いた部分 "/storage/emulated/なんたらかんたら/hoge.png"だったら"hoge"のところだけ取り出します。では、PostBmpAsyncHttpRequest.kt の全ソースコード
package com.hatenablog.kuukaix.simpleapp import android.app.Activity import android.graphics.Bitmap import android.os.AsyncTask import android.view.View import android.widget.TextView import java.io.* import java.net.HttpURLConnection import java.net.URL class Param(var uri: String, var bmp: Bitmap, var name: String) class PostBmpAsyncHttpRequest(private val mActivity: Activity) : AsyncTask<Param?, Void?, String>() { override fun doInBackground(vararg params: Param?): String { val param: Param = params[0]!! var connection: HttpURLConnection? = null val sb = StringBuilder() try { // 画像をjpeg形式でstreamに保存 val jpg = ByteArrayOutputStream() param.bmp.compress(Bitmap.CompressFormat.JPEG, 100, jpg) val url = URL(param.uri) connection = url.openConnection() as HttpURLConnection connection.setConnectTimeout(3000) //接続タイムアウトを設定する。 connection.setReadTimeout(3000) //レスポンスデータ読み取りタイムアウトを設定する。 connection.setRequestMethod("POST") //HTTPのメソッドをPOSTに設定する。 //ヘッダーを設定する connection.setRequestProperty("User-Agent", "Android") connection.setRequestProperty("Content-Type", "application/octet-stream") connection.setDoInput(true) //リクエストのボディ送信を許可する connection.setDoOutput(true) //レスポンスのボディ受信を許可する connection.setUseCaches(false) //キャッシュを使用しない connection.connect() // データを投げる val out: OutputStream = BufferedOutputStream(connection.getOutputStream()) val filename = basename(param.name) out.write(filename.toByteArray()) out.write(ByteArray(80 - filename.length)) out.write(jpg.toByteArray()) out.flush() // データを受け取る val istream: InputStream = connection.getInputStream() val reader = BufferedReader(InputStreamReader(istream, "UTF-8")) var line: String? = "" while (reader.readLine().also { line = it } != null) sb.append(line) istream.close() } catch (e: IOException) { e.printStackTrace() } finally { connection!!.disconnect() } return sb.toString() } public override fun onPostExecute(string: String) { // 戻り値をViewにセット val textView = mActivity.findViewById<View>(R.id.text_view) as TextView textView.text = "" + textView.text + "\n" + string } private fun basename(path: String): String { val file = File(path) return file.nameWithoutExtension } }
前回から改造したのは
class Param(var uri: String, var bmp: Bitmap, var name: String)
MainActivityからデータを受け取る部分。nameでファイル名を受け取ります。
そしてデータを送信する部分
// データを投げる val out: OutputStream = BufferedOutputStream(connection.getOutputStream()) val filename = basename(param.name) out.write(filename.toByteArray()) out.write(ByteArray(80 - filename.length)) out.write(jpg.toByteArray()) out.flush() //と private fun basename(path: String): String { val file = File(path) return file.nameWithoutExtension }
basename()でファイルを拡張子を除く部分を取り出して、bytearrayにして、out.write()に出力します。 で、これだと80byteにならないので、残りの80 - filename.lengthの長さをnull文字で埋めます。 ByteArray(数)で、数の分だけnull文字が作れます。最後にjpg本体を書き込みます。
今回は、
public override fun onPostExecute(string: String) { // 戻り値をViewにセット val textView = mActivity.findViewById<View>(R.id.text_view) as TextView textView.text = "" + textView.text + "\n" + string }
も追加します。Windowsサーバー側で何らかのエラーが返ると、それをAndroidの画面に出すためです。kotlinでは文字の結合は "."ではなくて"+"なのよね(ややこしい)
で、最後にMainActivityからファイル名をPostBmpAsyncHttpRequest()に渡すよう改造します。
package com.hatenablog.kuukaix.simpleapp import android.content.ContentUris import android.graphics.Bitmap import android.os.Bundle import android.provider.MediaStore import android.widget.Button import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<Button>(R.id.button).setOnClickListener { // ボタンが押されたら実行される Toast.makeText(this, "button clicked", Toast.LENGTH_LONG).show() //レコードの取得 try { val contentResolver = contentResolver val query = contentResolver.query( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null ) query?.use { cursor -> val str = String.format( "MediaStore.Images = %s\n\n", cursor.count) val sb = StringBuilder(str) while (cursor.moveToNext()) { sb.append("ID: ") sb.append(cursor.getString(cursor.getColumnIndex( MediaStore.Images.Media._ID))); val id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)) sb.append("\n") sb.append("Title: ") sb.append(cursor.getString(cursor.getColumnIndex( MediaStore.Images.Media.TITLE))) sb.append("\n") sb.append("Path: ") val path_name = cursor.getString(cursor.getColumnIndex( MediaStore.Images.Media.DATA)) sb.append(path_name) sb.append("\n") sb.append("Uri: ") val uri = ContentUris.withAppendedId( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id) sb.append(uri) sb.append("\n\n") try { val bmp: Bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri) PostBmpAsyncHttpRequest(this).execute(Param("http://192.168.1.12/index.php", bmp, path_name)) } catch (e: Exception) { e.printStackTrace() Toast.makeText(this, "なんかエラー", Toast.LENGTH_SHORT).show() } } val textView = findViewById<TextView>(R.id.text_view) textView.setText(sb); } } catch (e: Exception) { e.printStackTrace() Toast.makeText(this, "例外が発生、ストレージPermissionを許可していますか?", Toast.LENGTH_SHORT).show() } } } }
val path_name = cursor.getString(cursor.getColumnIndex(
MediaStore.Images.Media.DATA))
でファイルパスを取り出して、
PostBmpAsyncHttpRequest(this).execute(Param("http://192.168.1.12/index.php", bmp, path_name))
Param()の第3引数にpath_nameを追加して、PostBmpAsyncHttpRequest()に渡します。
これで完成。
Androidアプリを実行してボタンを押すとC:\tempにjpgファイルが作られます。なお、エミュレータでは画像ファイルがないので、画像ファイルの入っている実機androidで試してください。