Sibainu Relax Room

柴犬と過ごす

Android Java で CameraX の写真をキャプチャ5 and HTTP POST

今日は昼の散歩です。結構歩いたので柴犬も少し疲れ気味です。お前も少し疲れたろ、休めよと言っている顔している柴犬です。

概要

今日は2件目を記録します。

画像の FTP 転送をこれまでしてきましたが、画像を Base64 で文字列に変換して、HTTP の POST リクエストでWEBサーバーに投げてみることをしてみます。

汚いコードですが、何とかできましたので記録します。

1冊だけでは理解の助けにはならないので買い足しました。

WEBのみでは断片的で覚えにくいので最初に購入した Kotlin の本です。

コードの実行結果

実行してみたら何やら表示されました。(左の画像)

少しテキストビューを下へスクロールさせてみました。(右の画像)

サーバーをこのWEBのHPにしましたので、HPのソースが表示されました。HPは接続があれば規定のHPのソースをレスポンスで返すので正常な反応です。

サーバーが POST に対して応答・処理できるようにして、キーとそれに対応する値をいれてPOSTすればいいと思います。

POST関係新規関数

uploadJson

executorService.submit 関数の引数 backgroundReceiverオブジェクトの中にある call関数を起動します。

この場合は HTTP の POST をリクエストして戻り値を Future<String> オブジェクトが受け取ります。

copy

    @UiThread
    private void uploadJson(String jsonUri, Map<String, String> postParams) {
        //クラス JsonPostHttp の実体を作成
        JsonPostHttp backgroundReceiver = new JsonPostHttp(jsonUri, postParams);
        //executorService を作成
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        //executorService.submit の引数に JsonPostHttp の実体を渡します
        //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
        Future<String> future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            //future から値を取り出します
            result = future.get();
        }
        catch(ExecutionException | InterruptedException ex) {
            Log.w(TAG, "非同期処理結果の取得で例外発生: ", ex);
        }
        resultInfo(result);
    }

class JsonPostHttp

uploadJson関数にあるExecutorService にこのクラスの実体を渡して、バックグラウンドで call 関数呼び出します。call関数の中に POST の実行文を書いて非同期で実行します。

call関数で実行すると戻りがありますので、ここでは接続が成功した場合のレスポンスを戻り値にしています。

戻り値は、uploadJson関数にあるように Future<String> で受け取ります。

copy

    public class JsonPostHttp implements Callable<String> {
        private final String _jsonUrl;
        private Map<String,String> _postParams;
        //コンストラクタです
        public JsonPostHttp(String jsonUrl,
                            Map<String, String> postParams) {
            _jsonUrl = jsonUrl;
            _postParams = postParams;
            Log.d(TAG, "コンストラクタ : " + postParams.get("data"));
        }
        // 非同期内でセットした文字列を返す
        @WorkerThread
        @Override
        public String call() {
            Log.d(TAG, "call : " + _postParams.get("data"));
            ArrayList<String> infolist = new ArrayList<>();
            HttpURLConnection conn = null;
            try {
                //bodyData の作成
                Map<String, String> postParams = _postParams;
                Log.d(TAG, "try first : " + postParams.get("data"));
                StringBuilder postData = new StringBuilder();
                for(Map.Entry<String, String> entry: postParams.entrySet()){
                    if (!entry.getKey().equals("")) {
                        if (postData.length() != 0) {
                            postData.append("&");
                        }
                        postData.append(URLEncoder.encode(entry.getKey(), "utf-8"));
                        postData.append("=");
                        if (entry.getKey() == "data"){
                            //画像データはURL変換なしとしました
                            postData.append(entry.getValue());
                        }else{
                            postData.append(URLEncoder.encode(entry.getValue(), "utf-8"));
                        }
                    }
                }
                String bodyData = postData.toString();
                Log.d(TAG, "bodyData : " + bodyData);

                URL url = new URL(_jsonUrl);

                conn = (HttpURLConnection) url.openConnection();
                // 接続に使ってもよい時間を設定。
                conn.setConnectTimeout(1000);
                // データ取得に使ってもよい時間。
                conn.setReadTimeout(2000);

                conn.setRequestMethod("POST");

                conn.setDoOutput(true);

                conn.setRequestProperty("Content-Type",
                                        "application/json; charset=utf-8");
                conn.setUseCaches(false);

                OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(),
                                                                StandardCharsets.UTF_8);
                out.write(bodyData);
                out.flush();
                out.close();

                conn.connect();

                String info = "";
                int status = conn.getResponseCode();
                if (status == HttpURLConnection.HTTP_OK) {
                    //コネクト成功
                    InputStream is = conn.getInputStream();
                    info = isToString(is);
                    infolist.add(info);
                } else {
                    infolist.add("サーバーに接続できませんでした");
                    Log.d(TAG, Integer.valueOf(status).toString());
                }

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (conn != null) conn.disconnect();
            }
            return join("\n",infolist);
        }
    }

isToString

InputStream を文字列に変換する関数です。

copy

    private String isToString(InputStream is) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        StringBuffer sb = new StringBuffer();
        char[] b = new char[1024];
        int line;
        while(0 <= (line = reader.read(b))) {
            sb.append(b, 0, line);
        }
        return sb.toString();
    }

requestJsonBody

WEBに接続してユザー名、パスワード、格キーに対応する値を持つハッシュテーブルを作成します。

requestImageBody との違いは、本関数は JSON オブジェクトをハッシュテーブル・配列で表現したオブジェクトを引数としますが、requestImageBody は InputStream を引数とします。

copy

    private Map<String, String> requestJsonBody(String jsonusername,
                                                String jsonuserid,
                                                String jsonaccess,
                                                String jsonkey,
                                                Map<String, Object> jsondata) {
        Map<String, String> postmap = new HashMap<>();
        postmap.put("username", jsonusername);
        postmap.put("userid", jsonuserid);
        postmap.put("access", jsonaccess);
        postmap.put("key", jsonkey);
        JSONObject json = new JSONObject(jsondata);
        String jsonstr = json.toString();
        postmap.put("data", jsonstr);
        return postmap;
    }

requestImageBody

WEBに接続してユザー名、パスワード、格キーに対応する値を持つハッシュテーブルを作成します。

requestJsonBody との違いは、本関数は InputStream を引数としますが、requestJsonBody はJSON オブジェクトをハッシュテーブル・配列で表現したオブジェクトを引数にします。

copy

    @UiThread
    private Map<String, String> requestImageBody(String jsonusername,
                                                 String jsonuserid,
                                                 String jsonaccess,
                                                 String jsonkey,
                                                 InputStream ips) {
        Map<String, String> postmap = new HashMap<>();
        postmap.put("username", jsonusername);
        postmap.put("userid", jsonuserid);
        postmap.put("access", jsonaccess);
        postmap.put("key", jsonkey);
        Log.d(TAG, "jsonkey");
        String imgstr = base64Encode(ips);
        Log.d(TAG, "requestImageBody : " + imgstr);
        postmap.put("data", imgstr);
        return postmap;
    }

base64Encode

キャプチャー画像の Uri がキャプチャー時に取得できますので、その Uri を使えば簡単に InputStream を取得できますので、それを引数にして base64 エンコードします。

copy

    public String base64Encode(InputStream ips) {
        String encodedStr = "";
        try {
            byte[] byteVal = new byte[1];
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (ips.read(byteVal) > 0) {
                byteArrayOutputStream.write(byteVal);
            }

            byteArrayOutputStream.close();
            byte[] b = byteArrayOutputStream.toByteArray();

            String cc = Integer.valueOf(b.length).toString();
            Log.d(TAG, cc);

            // Base64へのエンコード
            //encodedStr = Base64.getUrlEncoder().withoutPadding().encodeToString(b);
            encodedStr = Base64.getEncoder().encodeToString(b);            

            Log.d(TAG, "base64Encode : " + encodedStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encodedStr;
    }

resultInfo

copy

    @UiThread
    private void resultInfo(String result) {
        tv.setText(result);
    }

takePhoto/onImageSaved

copy

                    @Override
                    public void onImageSaved(ImageCapture.OutputFileResults
                                                     outputFileResults) {

                        CharSequence msg = "Photo capture succeeded: " +
                                        outputFileResults.getSavedUri();
                        Toast.makeText(MainActivity.this,
                                        msg,
                                        Toast.LENGTH_SHORT).show();
                        Log.d(TAG, msg.toString());

                        Uri uri = Uri.parse(outputFileResults.getSavedUri().toString());
                        String filelist = "";
                        try {
                            Log.d(TAG, "begin try");
                            InputStream ips = resolver.openInputStream(uri);
                            Log.d(TAG, "InputStream");

                            if (id == R.id.bt_shutter) {
                                //画像のアップロードの実行
                                Log.d(TAG,"ftpupload");
                                uploadNas(FTPUSERNAME,
                                        FTPPASSWORD,
                                        FTPSERVER,
                                        FTPDIRECTORY,
                                        name + ".jpg",
                                        ips);

                            } else if (id == R.id.bt_post) {
                                //POSTリクエストの実行(画像の文字エンコード)
                                Log.d(TAG,"postBody");
                                Map<String, String> postBody =
                                        requestImageBody(JSONNAME,
                                                JSONID,
                                                JSONACCESS,
                                                JSONKEY,
                                                ips);
                                Log.d(TAG, "front : " + postBody.get("data"));
                                uploadJson(JSONSERVER,postBody);
                            }
                            ips.close();

                        } catch (Exception e) {
                            Log.d(TAG, "画像ファイルエラー");
                        }
                    }

インポート・定数関係

copy

import static java.lang.String.join;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.commons.codec.net.URLCodec;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {
    -----略-----
    private final String JSONSERVER = "https://www.sibainu.org/";
    private final String JSONNAME = "jsonusername";
    private final String JSONID = "jsonid";
    private final String JSONACCESS = "jsonaccess";
    private final String JSONKEY = "jsonkey";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    -----略-----
        //ボタンを2つにしましたので修正
        clickListener ls = new clickListener();

        bt = findViewById(R.id.bt_shutter);
        bt.setOnClickListener(ls);

        bt = findViewById(R.id.bt_post);
        bt.setOnClickListener(ls);

        tv = findViewById(R.id.tvinfo);
    }

MainActivity 以下全コード

私がコピペで直に使えるように全コードを記録しています。これは本ブログを私のコード記録帳にしているためです。

それ故、処置の不十分・間違い・汚いが多々ありますので気が付いても素通りでお願いします。

copy

package org.sibainu.relax.room.cameracjavanas_call1;

import static java.lang.String.join;
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraSelector;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
import androidx.camera.core.Preview;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.camera.view.PreviewView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.commons.codec.net.URLCodec;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {

    private final static String TAG = "camerax";
    private final static String JSON_TAG = "jason_process";
    private static String FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS";
    private ImageCapture imagecapture;
    private Uri imageuri;
    private ExecutorService cameraExecutor;
    private Button bt;
    private TextView tv;
    private final int REQUEST_CODE_PERMISSIONS = 100;
    private final String[] REQUIRED_PERMISSIONS = {Manifest.permission.CAMERA,
                                   Manifest.permission.WRITE_EXTERNAL_STORAGE};
    private final String FTPUSERNAME = "□□□□□□";
    private final String  FTPPASSWORD = "□□□□□□□□□□□□";
    private final String FTPSERVER = "192.168.□□.□□";
    private final String FTPDIRECTORY = "photo/□□□□□□□□□□□□/";
    private final String JSONSERVER = "https://www.sibainu.org/";
    private final String JSONNAME = "jsonusername";
    private final String JSONID = "jsonid";
    private final String JSONACCESS = "jsonaccess";
    private final String JSONKEY = "jsonkey";
    private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        cameraExecutor = Executors.newSingleThreadExecutor();
        cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        if (allPermissionsGranted()) {
            startCamera();
        } else {
            ActivityCompat.requestPermissions(
                    this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
            );
        }

        clickListener ls = new clickListener();

        bt = findViewById(R.id.bt_shutter);
        bt.setOnClickListener(ls);

        bt = findViewById(R.id.bt_post);
        bt.setOnClickListener(ls);

        tv = findViewById(R.id.tvinfo);
    }

    private boolean allPermissionsGranted() {
        for (String requiredPermission : REQUIRED_PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(
                    this, requiredPermission
            ) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

    private void startCamera() {
        ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);

        cameraProviderFuture.addListener(() -> {
            try {
                // カメラのライフサイクルをアプリのライフサイクルに紐づけ
                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

                // プレビューの設定
                PreviewView previewView = findViewById(R.id.viewFinder);
                Preview preview = new Preview.Builder().build();
                preview.setSurfaceProvider(previewView.getSurfaceProvider());

                // イメージキャプチャーを受け取るオブジェクト
                imagecapture = new ImageCapture.Builder().build();

                // 背面カメラをデフォルト
                CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

                // 既存の廃棄
                cameraProvider.unbindAll();

                // カメラをビュー・イメージキャプチャーに紐づけ
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imagecapture);

            } catch (ExecutionException | InterruptedException e) {
                Log.e(TAG, e.getLocalizedMessage(), e);
            }
        }, ContextCompat.getMainExecutor(this));
    }

    private void takePhoto(int id) {

        if (imagecapture == null) {
            return;
        }
        Log.d(TAG, "imagecapture");

        // タイムスタンプされた名前を作るため時間を「yyyyMMddHHmmssSSS」形式にフォーマット
        SimpleDateFormat dateFormat = new SimpleDateFormat(FILENAME_FORMAT, Locale.JAPAN);

        // 現在の日時を取得。
        Date now = new Date(System.currentTimeMillis());

        // 取得した日時データを設定したフォーマットに整形した文字列を生成。
        String nowStr = dateFormat.format(now);

        // ストレージに格納する画像のファイル名を生成。
        // ファイル名の一意を確保するためにミリ秒のタイムスタンプの値を利用。
        String name = "Photo_" + nowStr;
        Log.d(TAG,name);

        // ContentValuesオブジェクトを生成。
        ContentValues values = new ContentValues();

        // 画像ファイルのタイトル名を設定。
        values.put(MediaStore.Images.Media.TITLE, name);

        // 画像ファイルの表示名を設定。
        values.put(MediaStore.Images.Media.DISPLAY_NAME, name);

        // 画像ファイルの種類を設定。
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");

        // 共有ストレージに保存するパス
        // P  Constant Value: 28
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image");
        }
        Log.d(TAG,"SDK_INT");

        // ContentResolverオブジェクトを生成。
        ContentResolver resolver = getContentResolver();
        Log.d(TAG,"resolver");

        // ContentResolverを使ってURIオブジェクトを生成。
        imageuri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        Log.d(TAG,"imageuri");

        // file + metadata 出力オプションオブジェクトの作成
        ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(
                resolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                values).build();

        // これがないとキャプチャーでエラーになります
        values.clear();

        Log.d(TAG,"outputOptions");

        // 撮影後にトリガーされる画像キャプチャのリスナーを設定する
        imagecapture.takePicture(
                outputOptions,
                ContextCompat.getMainExecutor(this),
                new ImageCapture.OnImageSavedCallback() {
                    @Override
                    public void onError(ImageCaptureException error) {
                        Log.e(TAG, "Photo capture failed: " + error.toString(), error);
                    }
                    @Override
                    public void onImageSaved(ImageCapture.OutputFileResults
                                                     outputFileResults) {

                        CharSequence msg = "Photo capture succeeded: " +
                                        outputFileResults.getSavedUri();
                        Toast.makeText(MainActivity.this,
                                        msg,
                                        Toast.LENGTH_SHORT).show();
                        Log.d(TAG, msg.toString());

                        Uri uri = Uri.parse(outputFileResults.getSavedUri().toString());
                        String filelist = "";
                        try {
                            Log.d(TAG, "begin try");
                            InputStream ips = resolver.openInputStream(uri);
                            Log.d(TAG, "InputStream");

                            if (id == R.id.bt_shutter) {
                                //画像のアップロードの実行
                                Log.d(TAG,"ftpupload");
                                uploadNas(FTPUSERNAME,
                                        FTPPASSWORD,
                                        FTPSERVER,
                                        FTPDIRECTORY,
                                        name + ".jpg",
                                        ips);

                            } else if (id == R.id.bt_post) {
                                //POSTリクエストの実行(画像の文字エンコード)
                                Log.d(TAG,"postBody");
                                Map<String, String> postBody =
                                        requestImageBody(JSONNAME,
                                                JSONID,
                                                JSONACCESS,
                                                JSONKEY,
                                                ips);
                                Log.d(TAG, "front : " + postBody.get("data"));
                                uploadJson(JSONSERVER,postBody);
                            }
                            ips.close();

                        } catch (Exception e) {
                            Log.d(TAG, "画像ファイルエラー");
                        }
                    }
                });
    }

    private class clickListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            int id = view.getId();
            if (id == R.id.bt_shutter) {
                Log.d(TAG,"onClick");
                tv.setText("処理を開始します");
                takePhoto(id);
            } else if (id == R.id.bt_post) {
                Log.d(TAG,"onClick");
                tv.setText("処理を開始します");
                takePhoto(id);
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        startCamera();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        cameraExecutor.shutdown();
    }

    // takePhoto() から呼び出すようにしています
    @UiThread
    private void uploadNas(String ftpUsername,
                             String ftpPassword,
                             String ftpServer,
                             String ftpDirectory,
                             String filename,
                             InputStream ips) {
        //クラス JsonPostHttp の実体を作成
        FtpAccess backgroundReceiver = new FtpAccess(
                                                ftpUsername,
                                                ftpPassword,
                                                ftpServer,
                                                ftpDirectory,
                                                filename,
                                                ips);
        //executorService を作成
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        //executorService.submit の引数に JsonPostHttp の実体を渡します
        //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
        Future<String> future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            //future から値を取り出します
            result = future.get();
        }
        catch(ExecutionException | InterruptedException ex) {
            Log.w(TAG, "非同期処理結果の取得で例外発生: ", ex);
        }
        resultInfo(result);
    }

    @UiThread
    private void resultInfo(String result) {
        tv.setText(result);
    }

    // uploadNas() から呼び出すようにしています
    public class FtpAccess implements Callable<String> {
        private final String _ftpUsername;
        private final String _ftpPassword;
        private final String _ftpServer;
        private final String _ftpDirectory;
        private final String _filename;
        private final InputStream _ips;
        //コンストラクタです
        public FtpAccess(String ftpUsername,
                         String ftpPassword,
                         String ftpServer,
                         String ftpDirectory,
                         String filename,
                         InputStream ips){
            _ftpUsername = ftpUsername;
            _ftpPassword = ftpPassword;
            _ftpServer = ftpServer;
            _ftpDirectory = ftpDirectory;
            _filename = filename;
            _ips = ips;
            Log.d(TAG, "FtpAccess コンストラクタ");
        }
        // 非同期内でセットした文字列を返す
        @WorkerThread
        @Override
        public String call() {
            Log.d(TAG, "call start");
            //ファイルの一覧表を返します
            ArrayList<String> infolist = new ArrayList<>();
            FTPClient ftpClient = new FTPClient();
            try {
                //デフォルト ポートでリモート ホストに接続され、システムに割り当てられたポートで現在のホストから発信されるソケットを開きます
                ftpClient.connect(_ftpServer);
                //指定されたユーザーとパスワードを使用して FTP サーバーにログインします
                ftpClient.login(_ftpUsername, _ftpPassword);
                //データ転送を行うために接続するデータ ポートを開くようにサーバーに指示されます
                ftpClient.enterLocalPassiveMode();

                //多くの FTP サーバーはデフォルトで BINARY になっています
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
                //文字化け対策
                ftpClient.setControlEncoding("CP932");

                Log.d("ServerFileAccess", ftpClient.getStatus());

                //ディレクトリ移動
                ftpClient.changeWorkingDirectory(_ftpDirectory);
                //ファイル一覧の取得
                String[] filenames;
                filenames = ftpClient.listNames();

                for (String filename : filenames) {
                    Log.d("ServerFileAccess", filename);
                    infolist.add(filename);
                }

                //NASにファイルをアップロード
                try {
                    // staoreFile関数の呼び出しでアップロードする。
                    ftpClient.storeFile(_filename, _ips);
                    // ログ出力
                    Log.d(TAG, "Upload - " + _filename);
                    infolist.add("Upload - " + _filename);
                } catch(Exception e){
                    e.printStackTrace();
                    infolist.add(e.toString());
                }
            } catch (Exception e) {
                e.printStackTrace();
                infolist.add(e.toString());
            } finally {
                Log.d("ServerFileAccess", "finally");
                infolist.add("処理を終了します");
                try {
                    ftpClient.logout();
                    ftpClient.disconnect();
                } catch (Exception e) {
                    Log.d(TAG, "FTP開放エラー :", e);
                    infolist.add(e.toString());
                }
            }

            return join("\n",infolist);
        }
    }

    @UiThread
    private void uploadJson(String jsonUri, Map<String, String> postParams) {
        //クラス JsonPostHttp の実体を作成
        JsonPostHttp backgroundReceiver = new JsonPostHttp(jsonUri, postParams);
        //executorService を作成
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        //executorService.submit の引数に JsonPostHttp の実体を渡します
        //バックグラウンドで非同期処理が行われ戻り値が future に格納されます
        Future<String> future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            //future から値を取り出します
            result = future.get();
        }
        catch(ExecutionException | InterruptedException ex) {
            Log.w(TAG, "非同期処理結果の取得で例外発生: ", ex);
        }
        resultInfo(result);
    }
    public class JsonPostHttp implements Callable<String> {
        private final String _jsonUrl;
        private Map<String,String> _postParams;
        //コンストラクタです
        public JsonPostHttp(String jsonUrl,
                            Map<String, String> postParams) {
            _jsonUrl = jsonUrl;
            _postParams = postParams;
            Log.d(TAG, "コンストラクタ : " + postParams.get("data"));
        }
        // 非同期内でセットした文字列を返す
        @WorkerThread
        @Override
        public String call() {
            Log.d(TAG, "call : " + _postParams.get("data"));
            ArrayList<String> infolist = new ArrayList<>();
            HttpURLConnection conn = null;
            try {
                //bodyData の作成
                Map<String, String> postParams = _postParams;
                Log.d(TAG, "try first : " + postParams.get("data"));
                StringBuilder postData = new StringBuilder();
                for(Map.Entry<String, String> entry: postParams.entrySet()){
                    if (!entry.getKey().equals("")) {
                        if (postData.length() != 0) {
                            postData.append("&");
                        }
                        postData.append(URLEncoder.encode(entry.getKey(), "utf-8"));
                        postData.append("=");
                        if (entry.getKey() == "data"){
                            //画像データはURL変換なしとしました
                            postData.append(entry.getValue());
                        }else{
                            postData.append(URLEncoder.encode(entry.getValue(), "utf-8"));
                        }
                    }
                }
                String bodyData = postData.toString();
                Log.d(TAG, "bodyData : " + bodyData);

                URL url = new URL(_jsonUrl);

                conn = (HttpURLConnection) url.openConnection();
                // 接続に使ってもよい時間を設定。
                conn.setConnectTimeout(1000);
                // データ取得に使ってもよい時間。
                conn.setReadTimeout(2000);

                conn.setRequestMethod("POST");

                conn.setDoOutput(true);

                conn.setRequestProperty("Content-Type",
                                        "application/json; charset=utf-8");
                conn.setUseCaches(false);

                OutputStreamWriter out = new OutputStreamWriter(conn.getOutputStream(),
                                                                StandardCharsets.UTF_8);
                out.write(bodyData);
                out.flush();
                out.close();

                conn.connect();

                String info = "";
                int status = conn.getResponseCode();
                if (status == HttpURLConnection.HTTP_OK) {
                    //コネクト成功
                    InputStream is = conn.getInputStream();
                    info = isToString(is);
                    is.close();
                    infolist.add(info);
                } else {
                    infolist.add("サーバーに接続できませんでした");
                    Log.d(TAG, Integer.valueOf(status).toString());
                }

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (conn != null) conn.disconnect();
            }
            return join("\n",infolist);
        }
    }

    private String isToString(InputStream is) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        StringBuffer sb = new StringBuffer();
        char[] b = new char[1024];
        int line;
        while(0 <= (line = reader.read(b))) {
            sb.append(b, 0, line);
        }
        return sb.toString();
    }

    private Map<String, String> requestJsonBody(String jsonusername,
                                                String jsonuserid,
                                                String jsonaccess,
                                                String jsonkey,
                                                Map<String, Object> jsondata) {
        Map<String, String> postmap = new HashMap<>();
        postmap.put("username", jsonusername);
        postmap.put("userid", jsonuserid);
        postmap.put("access", jsonaccess);
        postmap.put("key", jsonkey);
        JSONObject json = new JSONObject(jsondata);
        String jsonstr = json.toString();
        postmap.put("data", jsonstr);
        return postmap;
    }

    @UiThread
    private Map<String, String> requestImageBody(String jsonusername,
                                                 String jsonuserid,
                                                 String jsonaccess,
                                                 String jsonkey,
                                                 InputStream ips) {
        Map<String, String> postmap = new HashMap<>();
        postmap.put("username", jsonusername);
        postmap.put("userid", jsonuserid);
        postmap.put("access", jsonaccess);
        postmap.put("key", jsonkey);
        Log.d(TAG, "jsonkey");
        String imgstr = base64Encode(ips);
        Log.d(TAG, "requestImageBody : " + imgstr);
        postmap.put("data", imgstr);
        return postmap;
    }

    public String base64Encode(InputStream ips) {
        tv.setText("base64Encode");
        String encodedStr = "";
        try {
            byte[] byteVal = new byte[1];
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            while (ips.read(byteVal) > 0) {
                byteArrayOutputStream.write(byteVal);
            }

            byteArrayOutputStream.close();
            byte[] b = byteArrayOutputStream.toByteArray();

            String cc = Integer.valueOf(b.length).toString();
            Log.d(TAG, cc);

            // Base64へのエンコード
            //encodedStr = Base64.getUrlEncoder().withoutPadding().encodeToString(b);
            encodedStr = Base64.getEncoder().encodeToString(b);
 
            Log.d(TAG, "base64Encode : " + encodedStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encodedStr;
    }

}

activity_main.xml

横固定にしましたので、レイアウトを変更しPOSTボタンを追加しました。

copy

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/guideline"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <Button
        android:id="@+id/bt_shutter"
        android:layout_width="100dp"
        android:layout_height="48dp"
        android:layout_marginTop="5dp"
        android:layout_marginEnd="5dp"
        android:text="@string/bt_shutter"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/bt_post"
        android:layout_width="100dp"
        android:layout_height="48dp"
        android:layout_marginEnd="5dp"
        android:text="@string/bt_post"
        app:layout_constraintBottom_toTopOf="@+id/scrollView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toBottomOf="@+id/bt_shutter" />

    <ScrollView
        android:id="@+id/scrollView2"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="@+id/guideline"
        app:layout_constraintTop_toTopOf="@+id/guideline2"
        app:layout_constraintVertical_bias="1.0">

        <TextView
            android:id="@+id/tvinfo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="TextView" />
    </ScrollView>

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.85" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.20" />

</androidx.constraintlayout.widget.ConstraintLayout>

res/values/strings

POSTボタン関係を追加しました。

copy

<resources>
    <string name="app_name">CameraXApp</string>
    <string name="take_photo">Take Photo</string>
    <string name="start_capture">Start Capture</string>
    <string name="stop_capture">Stop Capture</string>
    <string name="bt_shutter">Shutter</string>
    <string name="bt_post">Post</string>
</resources>

AndroidManifest.xml

モニターを横固定にしました。

copy

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature android:name="android.hardware.camera.any" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Cameracjavanas_call1"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="landscape"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

今回はここまでとします。