Android

AndroidでPDFViewerを作成する 第5回

Posted 9月 2 2014 by 須藤 雄一  ,

本連載の趣旨

Androidのアプリを作るぞ!と一念発起したはいいものの…開発環境は作ってHelloWorldを出してみたけど次に何をすればいいかわからない。1冊本を読んでみたけど、実際の開発はどのように進めていくのか想像がつかない。などなど。
初級者から中級者へステップアップするために必要な「何かアプリを作ってみる」ということに焦点を当てた連載となっています。
脱初心者を目指す連載となっていますので、ごくごく基本的な説明は割愛しています。また、Javaのプログラムに関しても「一冊本を読んだことがある」程度の読者を想定していますので、基本的な話は割愛していきますのでご了承ください。
題材は、「本棚を模したPDFViewer」です。

前回の宿題

第4回の最後で宿題となっていた「二つ目のImageViewにClickイベントを設定する」の解答例を紹介します。

変更するのは「MainActivity.java」ファイルのみです。

public class MainActivity extends ActionBarActivity implements OnClickListener {

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

		ImageView imageView1 = (ImageView) findViewById(R.id.imageView1);
		imageView1.setOnClickListener(this);

		/* ここから追加 */
		ImageView imageView2 = (ImageView) findViewById(R.id.imageView2);
		imageView2.setOnClickListener(this);
		/* ここまで */
	}

	@Override
	public void onClick(View v) {
		// onClickの引数を使って条件分岐
		// 「imageView1」をクリックした場合
		if(v.getId() == R.id.imageView1){
			String path = "/mnt/sdcard/android.pdf"; // ここだけ異なる
			Uri uri = Uri.parse(path);
			Intent intent = new Intent(this, MuPDFActivity.class);
			intent.setAction(Intent.ACTION_VIEW);
			intent.setData(uri);
			startActivity(intent);

		// 「imageView2」をクリックした場合
		}else if(v.getId() == R.id.imageView2){
			String path = "/mnt/sdcard/html5.pdf"; // ここだけ異なる
			Uri uri = Uri.parse(path);
			Intent intent = new Intent(this, MuPDFActivity.class);
			intent.setAction(Intent.ACTION_VIEW);
			intent.setData(uri);
			startActivity(intent);
		}
	}
}

まず、12、13行目を追加します。これは、二つ目のImageView(R.id.imageView2)に対してClickリスナーの設定を行っています。これを行わないとクリックしてもイベントを検知することができません。

次に、onClickメソッドの中を修正します。
onClickメソッドはViewをクリックした際に呼び出されるコールバックメソッドで、引数のViewにはユーザーが実際にクリックしたViewが格納されてきます。そのため、このViewのリソースIDとR.id.xxxを比較することでどのView(この場合はImageView)がクリックされたかがわかります。
今回の場合は、クリックしたImageViewに応じて表示するPDFファイルを切替えればいいですので、変数pathに代入している値を切替えます。

では、実行して動きを確認してみましょう。
プレゼンテーション1

今後の課題

  • 本棚に表示するサムネイルを動的にプログラムで生成する
  • PDFデータの保存方法を検討する

ここに新たに以下の課題を追加します。
これらは現時点でわかっているアプリケーションの機能用件となります。

  • 新しいPDFデータはサーバーからダウンロードできるようにする。(アプリ内に保存しない)
    アプリ内に書籍データを保存してしまうと、新しい書籍を販売したいときには更新したアプリをPlayストアにアップデートする必要があります。これはユーザーからすると購入してもいない書籍のためにアプリを更新しなくてはならず、不便ではないでしょうか。また、購入していない書籍データも全てアプリの中に保存されることになるので、アプリのサイズがとても大きくなってしまいます。これもユーザーにとってみると不便といえます。
  • PDFデータはアプリ内課金で購入できるようにする。
    自前で課金システムを構築することも可能ですが、クレジットカード情報などを扱う場合、情報漏えいへの対応を入念に行う必要があります。また、ユーザビリティの面からも改めてクレジットカード情報などを登録し直す(ユーザーは既にPlayストアに登録しているため、2度目の登録ととらえてしまう)必要があり、不便といえるでしょう。
  • 本棚には、PDF以外にWEBページ(ブックマークのようなもの)を登録できるようにする。
    PDFだけでなく、スマホ用WEBページや将来的にはEPUB形式(電子書籍の規格の1つ)、他のアプリを書籍として登録できるようにします。現時点ではPDFとWEBページだけとしますが、EPUBや他のアプリとの連携も行いやすいようにプログラムを設計・構築していきたいと思います。
  • PDFデータは可能な限り複製不可となるように保存する。
    複製できないようにデータの格納場所や暗号化などを行います。
  • ソート、検索機能
    書籍が増えてきた場合を考え、目的の書籍にたどり着きやすくするためにさまざまなソートや検索機能を持たせます。

データの保存方法を検討する

まずはデータをどのように保存するかということを検討していきます。
要件の中に「ソート、検索機能」があります。これは、例えば「本名でソート」や「サイズでソート」あるいは、「Java」というタグのついた本を検索するなどが考えられますが、これらを実現するためには書籍データをデータベースで保存しておいた方が便利です。
そこでまずは、「データベースに保存してあるデータを本棚に一覧表示する」という機能を実装するところからスタートしていきたいと思います。

図を書くと以下のようになります。
プレゼンテーション2

全書籍データベースはPHPやJSP/サーブレットなどのサーバーサイドで実装するものとし、現時点では深く考えないことにします。
処理の流れを把握しておきましょう。

  1. 【サーバー】新しい書籍を登録する
  2. 【アプリ】ブックストア(未購入の書籍一覧画面のようなもの、まだ作っていません)にその書籍が表示される
  3. 【アプリ】アプリ内課金で書籍を購入する
  4. 【アプリ】サーバーから書籍データ(書籍名、サイズ、サムネイル、PDFデータ)をダウンロードする
  5. 【アプリ】ダウンロードしたデータをデータベースに保存する
  6. 【アプリ】本棚に購入した書籍が表示される

データベースを利用したアプリ

AndroidアプリでデータベースにはSQLite3を使用することができます。データベースの作成やテーブルの作成を支援するためのクラス「SQLiteOpenHelper」が用意されているため、このクラスを継承した新しいクラスを作成します。

public class DBHelper extends SQLiteOpenHelper {

	@Override
	public void onCreate(SQLiteDatabase arg0) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
		// TODO Auto-generated method stub

	}

}

自動生成されたクラスはこのようになっていますが、いくつか修正していきます。
まずは、引数名が[argX]となっていますので、以下のように修正しましょう。

public void onCreate(SQLiteDatabase db) { ... }
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { ... }

次に、クラス名「DBHelper」でコンパイルエラーが検出されていますので、エラー原因となっているコンストラクタを作成します。

public class DBHelper extends SQLiteOpenHelper {
	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "myapp.db";
	
	public DBHelper(Context context) {
		super(context, DB_NAME, null, DB_VERSION);
	}
	// 省略
}

Contextを引数とするコンストラクタを作成し、中ではスーパークラスのコンストラクタを呼び出します。
その際に第2引数にはデータベース名、第3引数にはCursorオブジェクト作成する際に使用するFactoryオブジェクト(通常はnull)、第4引数にはデータベースのバージョンを指定します。
今回はデータベース名とバージョンは定数として定義しました。

データベース名はSQLite3のファイル名としても利用され、ファイルの保存場所は以下となります。

/data/data/パッケージ名/databases/データベース名

データベースのバージョンは、データベーススキーマの変更に対して付与するバージョンのことで、このバージョンが変わると自動的にonUpgradeメソッドが実行されることになります。
アプリのバージョンアップに伴ってデータベーススキーマの変更(テーブル定義の変更や、データパッチなど)を行いたいときに、データベースのバージョンを変更するとそれに合わせた処理を行うことができます。

説明が前後してしまいましたが、onCreateメソッドはデータベースを新規に作るときに実行されるメソッドで、この中ではテーブルの作成やサンプルデータの投入などを行います。

では、onCreateメソッドを実装していきます。

public class DBHelper extends SQLiteOpenHelper {
	private static final int DB_VERSION = 1;
	private static final String DB_NAME = "myapp.db";
	
	// テーブル作成用SQL
	private static final String CREATE_BOOKS_TABLE = ""
			+ "CREATE TABLE books ("
			+ "  id INTEGER PRIMARY KEY,"
			+ "  name TEXT,"
			+ "  size INTEGER,"
			+ "  thum_path TEXT,"
			+ "  contents_path TEXT"
			+ ")";
		
	public DBHelper(Context context) {
		super(context, DB_NAME, null, DB_VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		// テーブル作成
		db.execSQL(CREATE_BOOKS_TABLE);

		// サンプルデータ投入
		String InsSQL = "INSERT INTO books "
				+ "(name, size, thum_path, contents_path) "
				+ "VALUES (?, ?, ?, ?)";
		
		db.execSQL(InsSQL, new Object[] { 
				"Java経験者のためのAndroidBasic", 100,  
				"/mnt/sdcard/android.jpg",
				"/mnt/sdcard/android.pdf"});
		db.execSQL(InsSQL, new Object[] { 
				"HTML5 SEMINAR", 11,  
				"/mnt/sdcard/html5.jpg",
				"/mnt/sdcard/html5.pdf"});
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		// バージョンアップ
	}
}

最後に新しく作成したDBHelperクラスをMainActivityクラスから呼び出します。

public class MainActivity extends ActionBarActivity implements OnClickListener {

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

		/* 追加 */
		DBHelper helper = new DBHelper(this);
		SQLiteDatabase db = helper.getWritableDatabase();
		
		ImageView imageView1 = (ImageView) findViewById(R.id.imageView1);
		imageView1.setOnClickListener(this);
		ImageView imageView2 = (ImageView) findViewById(R.id.imageView2);
		imageView2.setOnClickListener(this);
	}
	// 省略
}

では、アプリを実行してみましょう。

device-2014-09-02-180134

実行後の画面はとくに変わりありません。バックグラウンドでデータベースが作成されていますが、画面処理にはまだ反映させていませんので、ただデータベースがつくられただけです。
ただし、この画面が出ずにアプリが強制終了してしまった場合には、作成したソースプログラム(特にSQLの部分)に間違いがあることが多いですので確認してみてください。
ちゃんとデータベースが作成され、サンプルデータが保存されているかは次回確認したいと思います。

次回第6回(PDFデータの保存方法を検討する つづき)


Android Android Tips



  未経験OKの仕事 |  上場企業の仕事 |  高待遇の仕事 |  外資系の仕事 |  社内SEで検索 |  自社サービスで検索





メールアドレス
ご質問・問い合わせ等、ご自由にお書きください。