Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

Android Java で CameraX の写真をキャプチャ4 と フチベニベンケイ

柴犬は昨日顔が出ましたので、今日は近くの公園の桜の花の様子を柴犬といっしょに見てきました。
大分蕾が大きくなりましたが昨日からの寒さで開花が

概要

キャプチャーができない原因がわかったので、@UiThread、@WorkerThread を使ったコードを作成してみました。

前回のものと同等に動作しますので記録することにします。

AsyncTask は API 30から非推奨になっているようなので、使うならこちらの方がいいと思います。

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

フチベニベンケイ

コードに入る前に去年の年末から様子を見ていたフチベニベンケイに変化がありましたので記録します。

緑3番のフチベニベンケイの当初からあった葉っぱが取れました。

挿してから新しくでた芽の葉っぱは健康のようなので大きくなっていくと思われます。

観察しているとゆっくりですが変化があるので面白いですし、癒される感じがします。

takePhoto

imagecapture.takePicture の onImageSaved だけが変更する必要がありますが、takePhoto の全コードを載せています。

copy

    private void takePhoto() {

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

        // Create time stamped name and MediaStore entry.
        SimpleDateFormat dateFormat = new SimpleDateFormat(FILENAME_FORMAT, Locale.JAPAN);

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

        // 取得した日時データを「yyyyMMddHHmmss」形式に整形した文字列を生成。
        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");
                            //アップロードの実行
                            uploadNas(FTPUSERNAME,
                                    FTPPASSWORD,
                                    FTPSERVER,
                                    FTPDIRECTORY,
                                    name + ".jpg",
                                    ips);
                        } catch (Exception e) {
                            Log.d(TAG, "画像ファイルエラー");
                        }
                    }
                });
    }

uploadNas/resultInfo/
FtpAccess

AsyncTask 関係はすべて取り除き、こちらを使います。

copy

    // takePhoto() から呼び出すようにしています
    @UiThread
    private void uploadNas(String ftpUsername,
                             String ftpPassword,
                             String ftpServer,
                             String ftpDirectory,
                             String filename,
                             InputStream ips) {
        FtpAccess backgroundReceiver = new FtpAccess(
                                                ftpUsername,
                                                ftpPassword,
                                                ftpServer,
                                                ftpDirectory,
                                                filename,
                                                ips);
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            result = future.get();
        }
        catch(ExecutionException ex) {
            Log.w(TAG, "非同期処理結果の取得で例外発生: ", ex);
        }
        catch(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);
        }
    }
}

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.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.util.concurrent.ListenableFuture;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
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 static String TAG = "camerax";
    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 = {android.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 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
            );
        }

        bt = findViewById(R.id.bt_shutter);
        clickListener ls = new clickListener();
        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 {
                // Used to bind the lifecycle of cameras to the lifecycle owner
                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

                // Preview
                PreviewView previewView = findViewById(R.id.viewFinder);
                Preview preview = new Preview.Builder().build();
                preview.setSurfaceProvider(previewView.getSurfaceProvider());
                imagecapture = new ImageCapture.Builder().build();

                // Select back camera as a default
                CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;

                // Unbind use cases before rebinding
                cameraProvider.unbindAll();

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imagecapture);

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

    private void takePhoto() {

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

        // Create time stamped name and MediaStore entry.
        SimpleDateFormat dateFormat = new SimpleDateFormat(FILENAME_FORMAT, Locale.JAPAN);

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

        // 取得した日時データを「yyyyMMddHHmmss」形式に整形した文字列を生成。
        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");
                            //アップロードの実行
                            uploadNas(FTPUSERNAME,
                                    FTPPASSWORD,
                                    FTPSERVER,
                                    FTPDIRECTORY,
                                    name + ".jpg",
                                    ips);
                        } 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");
                takePhoto();
            }
        }
    }

    @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) {
        FtpAccess backgroundReceiver = new FtpAccess(
                                                ftpUsername,
                                                ftpPassword,
                                                ftpServer,
                                                ftpDirectory,
                                                filename,
                                                ips);
        ExecutorService executorService  = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(backgroundReceiver);
        String result = "";
        try {
            result = future.get();
        }
        catch(ExecutionException ex) {
            Log.w(TAG, "非同期処理結果の取得で例外発生: ", ex);
        }
        catch(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);
        }
    }
}

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