Androidアプリで画像をアップロードしたいぞぉ!ということで、1つ前の記事でphpをhttpサーバーで動くようにしたので、次は、Androidアプリを作成します。
が、その前にAndroidアプリで画像を受け取るphpファイルをWindowsに作っておきます。tomcatをC:\tomcatにインストールしている場合は、 C:\tomcat\htdocs\index.phpというファイルに
<?php $data = file_get_contents("php://input"); $fp = fopen("C:/temp/data.jpg", 'wb'); fwrite($fp, $data); fclose($fp); echo "Image Upload Finish.\n"; ?>
を作ります。でアップロードしたファイルは C:/temp/data.jpg に作るので、C:\tempフォルダーを作っておきます。ここで気が付いたと思いますが、アップロードした画像は全部C:/temp/data.jpgに置かれてしまうので、古い画像は上書きされてしまいます(使えねぇ!!)
2つほど前に記事で作成したボタン付きのSampleAppを改造します。
MainActivityを以下のように改造
"http://192.168.1.21/index.php"のところは、サーバーになるWindows10のPCのローカルアドレスに変更すること。確認するには、Windowsキーを押す→設定→ネットワークとインターネット→プロパティ→IPv4 アドレス
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: ") sb.append(cursor.getString(cursor.getColumnIndex( MediaStore.Images.Media.DATA))); 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.21/index.php", bmp)) } 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() } } } }
Android Studioでapp→manifests→AndroidManifest.xmlをダブルクリックして開く。 下の方に
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /\> <uses-permission android:name="android.permission.INTERNET" /\>
の2行を追加。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.hatenablog.kuukaix.simpleapp"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.SimpleApp"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET" /> </manifest>
最後にMainAcitivityのところで右クリックしてNew→Kotolin Class/File を選んで、ファイル名にPostBmpAsyncHttpRequestと入力。
中身は
package com.hatenablog.kuukaix.simpleapp import android.app.Activity import android.graphics.Bitmap import android.os.AsyncTask import java.io.* import java.net.HttpURLConnection import java.net.URL class Param(var uri: String, var bmp: Bitmap) 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()) 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 = string } }
実行(エミュレータでは画像ファイルがないので正しく動きません。画像が入っている実機で動作させます)すると、画面は
で、同じです。「ボタン」を押すと、「例外が発生、ストレージPermissionを許可していますか?」が出ます。 ストレージへアクセス許可しないといけません。Androidの方で「設定」→「アプリ」→「SimpleApp」→「許可」→「ストレージ」のスライドを動かして許可する。permission.INTERNETも追加したのに出ないのは、初めから許可されているからです。(でもAndroidManifest.xmlには書かないといけません)
もう一度アプリを起動して、「ボタン」を押すとMediaStorageに入っている画像データの一覧が出ます。このときhttpのPost機能を使って、画像データをサーバーに転送しています。
Windowsに戻って、C:\temp\data.jpgを見ると最後にアップロードしたファイルが見えます。