SQLの窓

2017年06月20日


Android での保存用テキストデータの扱いを okio で簡素化する

OkHttp はとても簡単にインターネットへのアクセスを提供してくれます。その Okhttp が依存しているストリームの処理が okio です。

当然 okio は一般的なテキストファイルのアクセスにも簡単に使えて便利なので、Android の内部で扱われる『位置付け』別に処理を実際に使用して整理してみました。

★ 全体のソースコード
build.gradle への記述 ▼ 2018/10/31 時点 api 'com.squareup.okio:okio:2.1.0' ( api は、依存関係に関して compile と同等らしいので / compile は 2018年の終わりに削除 ) ▼ 2017/06/20 時点 compile 'com.squareup.okio:okio:1.13.0' Runtime Permission の対応 ▼ 以下のテンプレートを使用しています Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート okio を使用した2つのメソッドを用意 okio には、BufferedSource と BufferedSink というクラスがあり、それぞれ 入力と出力を担当しています。一応サンプルとして OkioTest.java を読むと雰囲気がつかめると思いますが、欲しい機能はそれほど複雑では無いので以下の2つのメソッドだけでも十分使いこなせると思います。
// *************************
// テキストファイル一括読込
// "UTF-8" or "windows-31j"
// *************************
private String readTextAll(InputStream inputStream, String charset) throws Exception {
	String result;

	BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));
	result = bufferedSource.readString(Charset.forName(charset));

	bufferedSource.close();
	inputStream.close();

	return result;
}

// *************************
// ファイルコピー
// *************************
private void copy(InputStream inputStream, OutputStream outputStream) throws Exception {

	Source source = Okio.source(inputStream);
	BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
	bufferedSink.writeAll(source);

	bufferedSink.close();
	source.close();

}
assets に syain.csv ファイルを置いて他の場所にコピー(書込み) syain.csv は、Windows の一般的な SHIFT_JIS のテキストファイル(CSV) です。このデータを パッケージの領域と 外部ストレージにコピーして、後でテキストファイルの読み込みをテストします。 他にバリエーションとして、okio で直接パッケージの領域にテキストファイル(test.txt)を "UTF-8" として書き込んでいます(23〜30)。
1) UTF-8 を指定して書き込み

Charset charset = Charset.forName("UTF-8");
sink.writeString("ストレージ情報\n",charset);

2) 専用メソッドで UTF-8 として書き込み

sink.writeUtf8(Manifest.permission.CAMERA + "\n");

※ sink は BufferedSink です
さらに、プリファレンスの書き込みも行います(36〜46)。
try {

	InputStream inputStream;
	AssetManager assetManager = MainActivity.this.getResources().getAssets();

	// ****************************************
	// syain.csv を assets から プライベートエリアへコピー
	// ****************************************
	inputStream = assetManager.open("syain.csv");
	copy(inputStream, openFileOutput("syain.csv", MODE_PRIVATE));

	// ****************************************
	// syain.csv を assets から 外部ストレージへコピー
	// ****************************************
	createDir();
	inputStream = assetManager.open("syain.csv");
	String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
	copy(inputStream, new FileOutputStream(csvPath));

	// ****************************************
	// プライベートなテキストファイルの作成と書き込み
	// ****************************************
	BufferedSink sink = Okio.buffer(Okio.sink(openFileOutput("test.txt", MODE_PRIVATE)));
	Charset charset = Charset.forName("UTF-8");
	sink.writeString("ストレージ情報\n",charset);
	sink.writeUtf8(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
	sink.writeUtf8(Manifest.permission.CAMERA + "\n");
	sink.writeUtf8(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
	sink.writeUtf8(PackageManager.PERMISSION_GRANTED + "\n");
	sink.close();

	// ****************************************
	// プライベートな小さな情報を保存する為の本来の方法
	// private_data.xml の作成
	// ****************************************
	SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
	SharedPreferences.Editor editor = data.edit();

	// 現在の年月日・時分秒を取得する為の準備
	Calendar cal = Calendar.getInstance();
	SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

	// データの書き込み
	editor.putString("now", sf.format(cal.getTime()));
	editor.putString("japan", "日本語");
	editor.commit();

} catch (Exception e) {
	e.printStackTrace();
}

外部ストレージにフォルダの作成

Runtime Permission の対象処理です

// *************************
// フォルダ作成
// *************************
private void createDir() {
	String imageDir = Environment.getExternalStorageDirectory().getPath() + "/textfiletest";
	Log.i("lightbox", imageDir );
	File file = new File(imageDir);
	// ディレクトリ初期作成
	if (!file.exists()) {
		if (file.mkdir() == false) {
			Log.i("lightbox", "ディレクトリを作成できませんでした");
			return;
		}
	}
}

画面にファイルを読込み(1)

まずは、パッケージ内の領域から読み込んで画面に表示します( openFileInput )

※ 画面定義


StringBuilder sb = new StringBuilder();

InputStream inputStream;
String text;

Log.i("lightbox", "読み込み1");
try {
	// ****************************************
	// プライベートなテキストファイルの読み込み
	// "UTF-8" or "windows-31j"
	// ****************************************
	inputStream = openFileInput("test.txt");
	text = readTextAll(inputStream,"UTF-8");

	sb.append(text);

	// ****************************************
	// プライベートなテキストファイルの読み込み
	// "UTF-8" or "windows-31j"
	// ****************************************
	inputStream = openFileInput("syain.csv");
	text = readTextAll(inputStream,"windows-31j");

	sb.append(text);

	EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
	editText.setText(sb.toString());
	TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
	textView.setText(sb.toString());

	// ****************************************
	// private_data.xml からデータの取得
	// ****************************************
	SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
	Log.i("lightbox", data.getString("now", "このデータはありません"));
	Log.i("lightbox", data.getString("japan", "このデータはありません"));

} catch (Exception e) {
	e.printStackTrace();
}
UTF-8 と SHIFT_JIS で保存してあるファイルの一括読み込みです。キャラクタセットに関する Oracle のドキュメントはこちらになります。

※ プリファレンスは Log.i で表示しています

画面にファイルを読込み(2)

外部ストレージより読み込みます( Runtime Permision の対象処理 )
new FileInputStream(path)

※ test.txt は、画面表示の都合で読み込んでいます
StringBuilder sb = new StringBuilder();

InputStream inputStream;
BufferedSource bufferedSource;
String text;

Log.i("lightbox", "読み込み2");
try {
	// ****************************************
	// プライベートなテキストファイルの読み込み
	// "UTF-8" or "windows-31j"
	// ****************************************
	inputStream = openFileInput("test.txt");
	text = readTextAll(inputStream,"UTF-8");

	sb.append(text);

	// ****************************************
	// 外部ストレージからテキストファイルの読み込み
	// "UTF-8" or "windows-31j"
	// ****************************************
	String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
	inputStream = new FileInputStream(csvPath);
	text = readTextAll(inputStream,"windows-31j");

	sb.append(text);

	EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
	editText.setText(sb.toString());
	TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
	textView.setText(sb.toString());


} catch (Exception e) {
	e.printStackTrace();
}


関連する記事 : 一般的な Java のテキスト読込み

Java : テキストファイルを読み込んで正規表現で置換



posted by lightbox at 2017-06-20 21:17 | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート

Runtime Permission の対応はしないとアプリが動作しないのでせざるを得ないですが、本来の処理とは無関係なのでかなり面倒な上にソースコードそのものが第三者への説明の邪魔になります。

そこで、ほぼ定型化して処理する為のテンプレートとして MainActivity への配置例と、そのために必要なクラスを用意しました。
手順1 ▼ Runtime Permission の処理は、以下から permission_170610.zip をダウンロードして下さい。 ※ CheckMyPermission の内容 手順2 ▼ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい ▼ エクスプローラから見た状態 手順3 使用したい Permission を AndroidManifest.xml に記述します
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lightbox.runtimepermissiontest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

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

</manifest>

手順4

MainActivity を以下のように記述して下さい
package com.example.lightbox.runtimepermissiontest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

	private CheckMyPermission checkMyPermission;

	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		// 全ての必要なパーミッションが許可された場合
		if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
			// onCreate で初期処理できるように、MainActivity をリスタート
			Intent intent = MainActivity.this.getIntent();
			MainActivity.this.finish();
			MainActivity.this.startActivity(intent);
		}
	}

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

		// 必要なパーミッションのリスト
		ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
		// AndroidManifest.xml に記述した Permission の一覧
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
		myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,101));
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_CALENDAR,102));
		// Runtime Permission 用のクラスのインスタンス
		checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
		if ( checkMyPermission.checkPermission() ) {
			// ここから通常の初期処理
			initSettings();
		}
	}

	private void initSettings() {

	}

}


手順5

実行します。すると、初回は許可ダイアログが指定した Permission と同数表示されて、onRequestPermissionsResult が実行されます。すると、Activity が再起動され、checkMyPermission.checkPermission() が true になって initSettings() を実行する事になります。

2回目以降(全て許可した場合)は onCreate で initSettings() が常に実行される事になります。(許可ダイアログを再度表示したい場合は、アプリを削除して再度実行します)

許可ダイログ

  


関連する記事

『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』

Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )


posted by lightbox at 2017-06-20 19:35 | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

2017年06月10日


WindowBuilder(Swing) で、WEBカメラを使用して画像を保存して okhttp で WEBサーバへアップロードする



必要なライブラリ

アップロードには okhttp を使用します
( ライブラリとしては okio も必要です )

JFrame を新しく作成して WEBカメラの映像をリアルタイムに表示しています。ライブラリは、webcam-capture を使用していますが、他のライブラリとして bridj-0.6.2.jar と slf4j-api-1.7.2.jar が同梱されていましたが、slf4j はもっと新しいバージョンがあり、いずれにしても slf4j-nop-1.7.25.jar が必要だったので、現時点で最新版の slf4j-api-1.7.25.jar をダウンロードして使用しました。


( Failed to load class org.slf4j.impl.StaticLoggerBinder が出る場合 )

単純な環境ならば、Maven が簡単です。(一つの IP で多くのアクセスがある場合は問題が出ます)

配布元のサンプルコード

How to display image from webcam in Swing panel (basic)

JFrame にロードする場合は、自動的に open されるようです。

WindowBuildewr のソースコード

プロジェクトの作成は、通常プロジェクトに WindowBuilder の JFrame を作成したものです。WindowBuilder の導入は『Pleiades All in One(NEON) で、Windows アプリを作成する手順( WindowBuilder + Swing デザイナー or SWT デザイナー[JFace] )』を参照して下さい
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.WebcamResolution;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

public class Main extends JFrame {

	private JPanel contentPane;
	private Webcam webcam;

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Main frame = new Main();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public Main() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 559, 444);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		JButton btnNewButton = new JButton("New button");
		btnNewButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				File targetFile;

				// get image
				BufferedImage image = webcam.getImage();
				// save image to PNG file
				targetFile = new File("c:\\temp\\test.png");
				try {
					ImageIO.write(image, "PNG", targetFile);
				} catch (IOException e1) {
					// TODO 自動生成された catch ブロック
					e1.printStackTrace();
				}


				OkHttpClient client = new OkHttpClient();

				MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder();
				multipartBodyBuilder.setType(MultipartBody.FORM);
				multipartBodyBuilder.addFormDataPart(
					"target",
					targetFile.getName(),
					RequestBody.create(MediaType.parse("image/png"),targetFile)
				);
				RequestBody requestBody = multipartBodyBuilder.build();

				// 送信用のデータを作成
				Request.Builder requestBuilder = new Request.Builder();
				String url = "https://ドメイン/パス/file_upload.php";
				requestBuilder.url(url);
				requestBuilder.post(requestBody);
				Request request = requestBuilder.build();

				// 受信用のオブジェクトの準備
				Call call = client.newCall(request);
				String result = "";

				// 送信と受信
				try {

					Response response = call.execute();
					result = response.body().string();

				} catch (Exception ex) {
					ex.printStackTrace();
				}

				System.out.println(result);

			}
		});
		btnNewButton.setBounds(12, 10, 135, 21);
		contentPane.add(btnNewButton);

		loadWebCam();
	}

	private void loadWebCam(){

		webcam = Webcam.getDefault();
		webcam.setViewSize(WebcamResolution.VGA.getSize());

		WebcamPanel panel = new WebcamPanel(webcam);
		panel.setFPSDisplayed(true);
		panel.setDisplayDebugInfo(true);
		panel.setImageSizeDisplayed(true);
		panel.setMirrored(true);

		JFrame window = new JFrame("Webcam Panel");
		window.getContentPane().add(panel);
		window.setResizable(true);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.pack();
		window.setVisible(true);


	}
}


WEBサーバの PHP

アップロードの name は target で固定です。
<?php
session_cache_limiter('nocache');
session_start();

header( "Content-Type: application/json; charset=utf-8" );


if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {
 
	$upload = realpath ( '.' );
	$upload .= ( DIRECTORY_SEPARATOR . $_FILES['target']['name'] );
	if ( move_uploaded_file(
		$_FILES['target']['tmp_name'], $upload ) ) {
		$_POST['result']  = "アップロードに成功しました";
	}
	else {
		$_POST['result']  = "アップロードに失敗しました";
	}

}
else {
	$_POST['result']  = "POST メソッドを使用して下さい";
}

$_POST['files'] = $_FILES;

print json_encode($_POST, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
?>


関連する記事

PHP : MastodonOAuthPHP の HttpRequest.php の http_request を public に書き換えて、ファイルアップロード


メインの JFrame 内の JPanel に表示する



	private void loadWebCam(){

		webcam = Webcam.getDefault();
		webcam.setViewSize(WebcamResolution.VGA.getSize());

		WebcamPanel panel = new WebcamPanel(webcam);
		panel.setFPSDisplayed(true);
		panel.setDisplayDebugInfo(true);
		panel.setImageSizeDisplayed(true);
		panel.setMirrored(true);

		panelInFrame.add(panel);

	}

IP カメラを使用する場合

ライブラリのダウンロード(webcam-capture-driver-ipcam)

IP カメラは、WEBカメラを Gmax M-JPEG version を使用しています( exe ファイルを VirusTotal でチェックししています )
	// ***************************
	// JFrame に IP Cam
	// ***************************
	private void loadIpWebCam(){

		Webcam.setDriver(new IpCamDriver());

		try {
			IpCamDeviceRegistry.register(new IpCamDevice("lightbox", "http://192.168.1.4:8088/", IpCamMode.PUSH));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}

		JFrame window = new JFrame("Webcam Panel");
		window.getContentPane().add(new WebcamPanel(Webcam.getDefault()));
		window.setResizable(true);
		window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		window.pack();
		window.setVisible(true);
		
		webcam = Webcam.getDefault();

	}

	// ***************************
	// JFrame 内の JPanel に IP Cam
	// ***************************
	private void loadIpWebCam2(){

		Webcam.setDriver(new IpCamDriver());

		try {
			IpCamDeviceRegistry.register(new IpCamDevice("lightbox", "http://192.168.1.4:8088/", IpCamMode.PUSH));
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}

		panelInFrame.add(new WebcamPanel(Webcam.getDefault()));

		webcam = Webcam.getDefault();
	}



posted by lightbox at 2017-06-10 23:40 | java : WindowBuilder | このブログの読者になる | 更新情報をチェックする

Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )

Runtime Permission の処理は、『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』でテストしたものをダウンロードできます。


※ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい


MainActivity

画面の表示はこちらで行います。onCreate で Runtime Permission のチェックの準備を行って、onRequestPermissionsResult での受け取り処理もここで完結します。

MainActivity を継承したサブクラスの onCreate でチェックを行い、既に許可済みであれば通常の初期処理を行います。

ここでは、カメラの呼び出しを行うので、カメラの Permission は必要ありませんが、ExternalStorage に画像ファイルを作成するので、AndroidManifest.xml への記述と Runtime Permission の許可処理が必要です。

また、カメラ呼び出しで private 変数がクリアされる事を想定して(そういう仕様である上に、カメラが保存したパスを返さない事から保存せざるを得ない)、MainActivity では onSaveInstanceState と onRestoreInstanceState で変数の保存と復帰を行っています。

package com.example.lightbox.cameracalltest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private CheckMyPermission checkMyPermission;

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imageUri) {
        this.imagePath = imageUri;
    }

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

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);

    }

    // 全ての必要なパーミッションが既に許可されているかどうか
     protected boolean  checkMyPermission() {
        return checkMyPermission.checkPermission();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString("image_uri", imagePath);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        imagePath = savedInstanceState.getString("image_uri");
    }
}


CameraCallActivity
package com.example.lightbox.cameracalltest;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CameraCallActivity extends MainActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 画面表示は super 側
        Log.i("lightbox", "CameraCallActivity : onCreate");

        if ( CameraCallActivity.this.checkMyPermission() ) {
            // 通常処理
            initSettings();
        }
    }

    private void initSettings() {

        // ボタンイベントの登録
        Button button = (Button) CameraCallActivity.this.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリックされました");

                callCamera();
            }
        });

    }

    // カメラの呼び出し
    private void callCamera() {

        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);

        String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
        File file = new File(imageDir);
        // ディレクトリ初期作成
        if (!file.exists()) {
            if (file.mkdir() == false) {
                Log.i("lightbox", "ディレクトリを作成できませんでした");
                return;
            }
        }

        // ギャラリー用画像保存パス
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";
        Log.i("lightbox",  "imagePath : " + imagePath );
        // 画像ファイルの パス を保存
        CameraCallActivity.this.setImagePath(imagePath);
        file = new File( imagePath );

        // 保存してほしい URI を引き渡す
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));

        CameraCallActivity.this.startActivityForResult(intent, 101);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if ( requestCode == 101 ) {
            if ( resultCode == Activity.RESULT_OK ) {

                // 画像表示
                Bitmap bitmap = BitmapFactory.decodeFile(CameraCallActivity.this.getImagePath());
                ImageView imageView = (ImageView) CameraCallActivity.this.findViewById(R.id.imageView);
                imageView.setImageBitmap(bitmap);

                // ギャラリーに反映
                MediaScannerConnection.scanFile(
                        CameraCallActivity.this,
                        new String[] { CameraCallActivity.this.getImagePath() },
                        new String[] { "image/jpeg" },
                        null);
            }
        }
    }
}


AndroidManifest.xml

実行するアクティビティは、CameraCallActivity なので、修正が必要です( 12行目 )
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameracalltest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".CameraCallActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>



画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameracalltest.MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher" />
</LinearLayout>




posted by lightbox at 2017-06-10 15:08 | 2017 Android Studio | このブログの読者になる | 更新情報をチェックする

2017年06月09日


JSON 配列を WEB より読み出して jQuery で動的にテーブルを作成して表示する


デモページ

関連する記事デモで実際に使用している自家製 API です

PHP : SQLインジェクション対策付きの、MySQL のデータを JSON で返す自家製 API テスト用のテンプレート3パターン

https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php
https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php?pretty=no : 整形なし
https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php?escape=no : \uXXXX にエスケープ

▼ TABLE 作成部分はほぼ同じです

FileReader で、ローカルの CSV を読み込んで(shift_jis)、jQuery でテーブルを作成して表示する

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<style>
body {
	padding: 10px 0 0 20px;
}
@media screen and ( max-width:479px )
{
	body {
		padding: 0px;
	}
}
#table_wrap {
	width:900px;

}
#syain  td {
	padding: 5px;
	border: solid #000000 1px;
	white-space: nowrap;
}
#syain  th {
	background-color: #e0e0e0;
	font-weight: bold;
	padding: 5px;
	border: solid #000000 1px;
	white-space: nowrap;
}
</style>
<script>
$(function(){

	$("#btn").on( "click", function(){
	
		console.log("クリックされました");
		$.ajax({
			url: "https://lightbox.sakura.ne.jp/demo/json/syain_api_bind.php",
			cache: false,
			data: { "name" : $("#cond").val() }
		})
		.done(function( data, textStatus ){
			console.log( "status:" + textStatus );
			console.log( "data:" + JSON.stringify(data, null, "    ") );

			// テーブル作成処理
			loadData( data );
			
		})
		// 失敗
		.fail(function(jqXHR, textStatus, errorThrown ){
			console.log( "status:" + textStatus );
			console.log( "errorThrown:" + errorThrown );
		})
		// 常に実行
		.always(function() {
			console.log("常に実行");
		})
		;		
	
	});

});


function loadData( data ) {

	console.log("処理開始");

	// テーブル表示リセット
	$("#syain tr,#syain th").remove();

	// タイトル専用
	var title_row;
	var title_col_data;
	// データ用
	var row_data;
	var col_data;

	// 行のループ  ( <tr></tr> )
	$.each(data, function( index, value ) {
	
		// 初回はタイトル作成
		if ( index == 0 ) {
			$.each( value, function( key, val ) {
				title_col_data = $("<th></th>").appendTo( "#syain" );
				title_col_data.text( key );
			});
		}

		// テーブルに行を追加	
		row_data = $("<tr></tr>").appendTo( "#syain" );
	
		// 列のループ ( <td></td> )
		$.each( value, function( key, val ) {
		
			// 行に列を追加
			col_data = $("<td></td>").appendTo( row_data );
			col_data.text( val );

		});
	
	});

}
</script>
<h3>社員一覧</h3>

<input id="cond" type="text">
<input id="btn" type="button" value="検索">

<div class="table-responsive">
<table id="syain"></table>
</div>



twitter-bootstrap は、スマホでテーブルが横スクロールする効果で使用しています( class="table-responsive" )。


posted by lightbox at 2017-06-09 22:04 | jQuery | このブログの読者になる | 更新情報をチェックする

PHP : SQLインジェクション対策付きの、MySQL のデータを JSON で返す自家製 API テスト用のテンプレート3パターン

特記事項

1) ブラウザの JavaScript からドメイン違いでも呼び出せるように、Access-Control-Allow-Origin を使用
2) pretty=no で、整形しません。escape=no で、日本語はそのまま返します(デバッグ優先)

real_escape_string で SQLインジェクション対策

SQL の実行に query メソッドを使用します。その為、SQL は単純に文字列で作成するので、real_escape_string で条件内の値を $_GET より $get に変換して使用します。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// SQL インジェクション用
	foreach( $_GET as $key => $value ) {
		// $_GET を $get で代替
		$get[$key] = $mysqli->real_escape_string( $value );
	}
	// json encode オプション
	if ( $get["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $get["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like '%{$get["name"]}%'";
	}

	$result = $mysqli->query($query);
	if ( $result === false ) {
		// SQL 失敗
		$json = array( "error" => "$mysqli->error : {$query}" );
	}
	else {
		// DB 読み出し成功
		$json = $result->fetch_all( MYSQLI_ASSOC );
	}
}

print json_encode( $json, $flg );

?>


bind_param で SQLインジェクション対策

prepare メソッドを使用すると、bind_param メソッドを使用して入力値から SQL を作成する事ができます。そして、結果として get_result メソッド を使用する事によって mysqli_result を取得できるので、その後は パターン(1) と同じになります
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// MySQL ネイティブドライバ限定(PHP 5.3.0 以降)
			$result = $stmt->get_result();
			if ( $result === false ) {
				$json = array( "error3" => "$mysqli->error" );
			}
			else {
				// DB 読み出し成功
				$json = $result->fetch_all( MYSQLI_ASSOC );
			}
		}
	}
}

print json_encode( $json, $flg );

?>


get_result メソッド を使え無い場合の方法

get_result メソッドは、MySQL ネイティブドライバ限定(PHP 5.3.0 以降)なので、代替方法です。$stmt->store_result() => $stmt->bind_result => $stmt->fetch という流れになります。
<?php
error_reporting( E_ALL & ~E_NOTICE );

session_cache_limiter('nocache');
session_start();

// 取得対象列
$list = "社員コード,氏名,フリガナ,所属,性別,作成日,更新日,給与,手当,管理者";
// json encode オプション
$flg = 0;

// JavaScript ajax 用
header( "Access-Control-Allow-Origin: *" );
header( "Content-Type: application/json; charset=utf-8" );

// DB 接続
$mysqli = new mysqli('サーバ', 'ユーザ', 'パスワード', 'データベース');
if ( $mysqli->connect_error ) {
	// 接続失敗
	$json = array( "error" => "Connect Error ({$mysqli->connect_errno}) {$mysqli->connect_error}" );
}
else {
	// json encode オプション
	if ( $_GET["pretty"] != "no" ) {
		$flg = $flg | JSON_PRETTY_PRINT;
	}
	if ( $_GET["escape"] != "no" ) {
		$flg = $flg | JSON_UNESCAPED_UNICODE;
	}

	$mysqli->set_charset("utf8");

	// ~E_NOTICE なので、単純化
	if ( $_GET["name"] == "" ) {
		$query = "select {$list} from 社員マスタ";
	}
	else {
		$query = "select {$list} from 社員マスタ";
		// 条件オプション
		$query .= " where 氏名 like ?";
	}

	// SQL 準備
	$stmt = $mysqli->prepare($query);
	if ( $stmt === false ) {
		// SQL 失敗
		$json = array( "error1" => "$mysqli->error : {$query}" );
	}
	else {
		// バインドは変数で
		$param = "%{$_GET["name"]}%";

		// パラメータの数が一致しない場合の warning を出さない為の @ 抑制
		@$stmt->bind_param('s', $param );
		if ( false === $stmt->execute() ) {
			$json = array( "error2" => "$mysqli->error" );
		}
		else {
			// get_result() を使わない方法
			$stmt->store_result();
			$row_data = new StdClass;
			$stmt->bind_result(
				$row_data->{"社員コード"},
				$row_data->{"氏名"},
				$row_data->{"フリガナ"},
				$row_data->{"所属"},
				$row_data->{"性別"},
				$row_data->{"作成日"},
				$row_data->{"更新日"},
				$row_data->{"給与"},
				$row_data->{"手当"},
				$row_data->{"管理者"}
			);
			$json = array();
			while ($stmt->fetch()) {
				$json[] = $row_data;
			}
		}
	}
}

print json_encode( $json, $flg );

?>




posted by lightbox at 2017-06-09 20:43 | PHP + データベース | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり