Sibainu Relax Room

柴犬と過ごす

Android Java で CameraX の写真をキャプチャ1

なにか分からない文字がいっぱいあるな。なにしてんだという顔している柴犬です。

概要

言語Kotlin で CameraX を扱った例は WEB に記事がたくさんありますが、JAVA での記事は多くないので記録します。

最後 FTP で宅内ネットワークにある NAS にアップロードしたいのですが、クラス FtpAccess のcall関数のところでエラーになり上手くいきません。

クラス FtpAccess のコントラクターは「FtpAccess コントラクタ」のログがありますので処理できているようですが、call 関数が起動しません。

今後解決したら記録することにします。

1冊のみでは理解に苦しいところがあるので更に新しく本を購入しました。

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

AndroidManifest.xml

カメラ関係、ネット関係、ストレイジ関係と permission が必要です。

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.Cameraxjavanas"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

build.gradle(:app)

カメラ関係、FTP関係、ライフサイクル関係の依存関係を入れます。

copy

plugins {
    id 'com.android.application'
}

android {
    namespace 'org.sibainu.relax.room.cameraxjavanas'
    compileSdk 34

    defaultConfig {
        applicationId "org.sibainu.relax.room.cameraxjavanas"
        minSdk 26
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation files('libs/commons-net-3.10.0.jar')
    implementation files('libs/jcifs-ng-2.1.7.jar')
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

    def camerax_version = "1.1.0-beta01"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.camera:camera-video:${camerax_version}"

    implementation "androidx.camera:camera-view:${camerax_version}"
    implementation "androidx.camera:camera-extensions:${camerax_version}"

    def lifecycle_version = "2.7.0"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycle_version}")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:${lifecycle_version}")
}

activity_main.xml

今回は、ボタン、テキストビュー、プレビューの3つのみです。

copy

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

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bt_shutter"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/bt_shutter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="4dp"
        android:text="@string/bt_shutter"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/tvinfo"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/tvinfo"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="264dp"
        android:layout_marginBottom="16dp"
        android:text="info"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

シンプルな表示です。

res/values/strings.xml

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>
</resources>

MainActivity.java

ここは結構労力が必要でした。

protected void onCreate

copy

package org.sibainu.relax.room.cameraxjavanas;

import static java.lang.String.join;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
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 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 com.google.common.util.concurrent.ListenableFuture;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;

import java.io.File;
import java.io.FileInputStream;
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;

/**
* Represents a custom camera activity. This activity allows the user to capture images using the device's camera.
*/
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 final int REQUEST_CODE_PERMISSIONS = 100;

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

        cameraExecutor = Executors.newSingleThreadExecutor();

        if(ActivityCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
            ActivityCompat.requestPermissions(MainActivity.this, permissions, REQUEST_CODE_PERMISSIONS);
           return;
        }

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

        startCamera();
    }

private void startCamera

copy

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

        cameraProviderFuture.addListener(() -> {
            try {
                // アプリケーションのプロセス内のカメラのライフサイクルをバインド
                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();

                // バックカメラをデフォルト
                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

copy

    @UiThread
    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");

        // 共有ストレージに保存するパス
        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");

        // 画面遷移 Intentオブジェクトを生成。
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Log.d(TAG,"intent");

        // Extra情報としてimageuriを設定。
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageuri);

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

        TextView tv = findViewById(R.id.tvinfo);
        tv.setText("アウトプットオプション");

        // 撮影後にトリガーされる画像キャプチャのリスナーを設定する
        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());
                //Uri uri = outputFileResults.getSavedUri();
                try {
                    //InputStream ips = resolver.openInputStream(uri);

                    String[] projection = {MediaStore.MediaColumns.DATA};
                    //アプリケーションのデータベースに接続して DATA を検索し格納されている path を取得
                    Cursor cursor =
                            getContentResolver().query(
                                    imageuri, projection, null, null, null
                            );

                    if (cursor != null) {
                        String path = "";
                        //最初のレコードにカーソルを移します
                        if (cursor.moveToFirst()) {
                            //カラムは一つだけなのでインデックスは 0
                            path = cursor.getString(0).toString();
                        }
                        cursor.close();
                        Log.d(TAG, path);
                        //File file = new File(path);

                        String filelist = "";
                        try {
                            //InputStream ips = new FileInputStream(file);
                            Log.d(TAG, "try");
                            filelist = new FtpAccess(
                                    "□□□□□□",
                                    "□□□□□□□□□□□□",
                                    "192.168.□□.□□",
                                    "photo/□□□□□□/",
                                    name + ".jpg",
                                    path).toString();
                        } catch (Exception e) {
                            Log.d(TAG, "画像ファイルエラー");
                        }
                        TextView tv = findViewById(R.id.tvinfo);
                        tv.setText(filelist);
                    }

                } catch (Exception ex) {
                    Log.d(TAG, "アップロードエラー :", ex);
                }
            }
        });
    }

class clickListener / protected void onDestroy

copy

    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 onDestroy() {
        super.onDestroy();
        cameraExecutor.shutdown();
    }

class FtpAccess

copy

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

                //多くの FTP サーバーはデフォルトで BINARY になっています
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

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

                //ディレクトリ移動
                ftpClient.changeWorkingDirectory(_ftpDirectory);
                Log.d(TAG, _ftpDirectory);
                //
                String[] filenames;
                filenames = ftpClient.listNames();

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

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

MainActivity.java全体

copy

package org.sibainu.relax.room.cameraxjavanas;

import static java.lang.String.join;
import android.Manifest;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
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 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 com.google.common.util.concurrent.ListenableFuture;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;

import java.io.File;
import java.io.FileInputStream;
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;

/**
* Represents a custom camera activity. This activity allows the user to capture images using the device's camera.
*/
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 final int REQUEST_CODE_PERMISSIONS = 100;

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

        cameraExecutor = Executors.newSingleThreadExecutor();

        if(ActivityCompat.checkSelfPermission(MainActivity.this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){
            String[] permissions = {Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE};
            ActivityCompat.requestPermissions(MainActivity.this, permissions, REQUEST_CODE_PERMISSIONS);
           return;
        }

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

        startCamera();
    }

    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));
    }

    @UiThread
    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");

        // 共有ストレージに保存するパス
        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");

        // 画面遷移 Intentオブジェクトを生成。
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Log.d(TAG,"intent");

        // Extra情報としてimageuriを設定。
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageuri);

        // file + metadata 出力オプション・オブジェクトの作成
        ImageCapture.OutputFileOptions outputOptions = new ImageCapture.OutputFileOptions.Builder(resolver,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                values).build();
        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());
                //Uri uri = outputFileResults.getSavedUri();
                try {
                    //InputStream ips = resolver.openInputStream(uri);

                    String[] projection = {MediaStore.MediaColumns.DATA};
                    //アプリケーションのデータベースに接続して DATA を検索し格納されている path を取得
                    Cursor cursor =
                            getContentResolver().query(
                                    imageuri, projection, null, null, null
                            );

                    if (cursor != null) {
                        String path = "";
                        //最初のレコードにカーソルを移します
                        if (cursor.moveToFirst()) {
                            //カラムは一つだけなのでインデックスは 0
                            path = cursor.getString(0).toString();
                        }
                        cursor.close();
                        Log.d(TAG, path);
                        //File file = new File(path);

                        String filelist = "";
                        try {
                            //InputStream ips = new FileInputStream(file);
                            Log.d(TAG, "try");
                            filelist = new FtpAccess(
                                    "□□□□□□",
                                    "□□□□□□□□□□□□",
                                    "192.168.□□.□□",
                                    "photo/□□□□□□/",
                                    name + ".jpg",
                                    path).toString();
                        } catch (Exception e) {
                            Log.d(TAG, "画像ファイルエラー");
                        }
                        TextView tv = findViewById(R.id.tvinfo);
                        tv.setText(filelist);
                    }

                } catch (Exception ex) {
                    Log.d(TAG, "アップロードエラー :", ex);
                }
            }
        });
    }

    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 onDestroy() {
        super.onDestroy();
        cameraExecutor.shutdown();
    }

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

                //多くの FTP サーバーはデフォルトで BINARY になっています
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

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

                //ディレクトリ移動
                ftpClient.changeWorkingDirectory(_ftpDirectory);
                Log.d(TAG, _ftpDirectory);
                //
                String[] filenames;
                filenames = ftpClient.listNames();

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

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

AsyncTask 使う方法(未確認)

非推奨な AsyncTask ですが、こちらも試してみようと思います。近々確認してみます。

copy

    public void FtpAccess() {
        Map<String, String> ftpmap = new HashMap<>();
        ftpmap.put("ftpUsername","□□□□□□");
        ftpmap.put("ftpPassword","□□□□□□□□□□□□");
        ftpmap.put("ftpServer","192.168.□□.□□");
        ftpmap.put("ftpDirectory","photo/□□□□□□/");
        ftpmap.put("filename", name + ".jpg");
        ftpmap.put("path",path);

        new FtpAsyncTask(){
            @Override
            protected void onPostExecute(String response) {
                TextView tv = findViewById(R.id.tvinfo);
                tv.setText(response);
            }
        }.execute(new FtpTaskParams(ftpmap));
    }


    //--------------------------------
    public class FtpTaskParams {
        Map<String, String> _ftpData;

        public FtpTaskParams(Map<String, String> ftpData) {
            this._ftpData = ftpData;
        }
    }


    //--------------------------------
    public class FtpAsyncTask extends AsyncTask<FtpTaskParams, String, String> {
        @Override
        protected String doInBackground(FtpTaskParams... params) {
            Map<String, String> ftpData = params[0]._ftpData;
            final String _ftpUsername = ftpData.get("ftpUsername");
            final String _ftpPassword = ftpData.get("ftpPassword");
            final String _ftpServer = ftpData.get("ftpServer");
            final String _ftpDirectory = ftpData.get("ftpDirectory");
            final String _filename = ftpData.get("filename");
            final String _path = ftpData.get("path");

            Log.d(TAG, "call start");
            //ファイルの一覧表を返します
            ArrayList<String> infolist = new ArrayList<>();
            FTPClient ftpClient = new FTPClient();

            try {
                Log.d(TAG, _ftpServer);
                //デフォルト ポートでリモート ホストに接続され、システムに割り当てられたポートで現在のホストから発信されるソケットを開きます
                ftpClient.connect(_ftpServer);
                Log.d(TAG, _ftpServer);
                //指定されたユーザーとパスワードを使用して FTP サーバーにログインします
                ftpClient.login(_ftpUsername, _ftpPassword);
                Log.d(TAG, _ftpPassword);
                //データ転送を行うために接続するデータ ポートを開くようにサーバーに指示されます
                ftpClient.enterLocalPassiveMode();

                //多くの FTP サーバーはデフォルトで BINARY になっています
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE);

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

                //ディレクトリ移動
                ftpClient.changeWorkingDirectory(_ftpDirectory);
                Log.d(TAG, _ftpDirectory);
                //
                String[] filenames;
                filenames = ftpClient.listNames();

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

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

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