Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
はじめてのAndroidアプリ開発_読書メモ

01 イントロダクション

Androidの概要を理解する

AndroidはLinuxベースのオペレーションシステムを基盤にしたソフトウェアの集合体である
Androidを構成するコンポーネント群は、大きく以下のレイヤに分類できる

  • Linuxカーネル
  • HAL(Hardware abstraction layer)
  • 標準ライブラリ
  • Androidランタイム
  • アプリケーションフレームワーク
  • アプリ

AndroidアプリはDalvik仮想マシン、またはART(Android Runtime)上で動作する

Androidの開発環境を整える

JDK
Android Studio
Android SDK(Software Development Kit)

Android Studioの画面構成を理解する

プロジェクトウィンドウ
エディタ
Android Monitor
ターミナル

02 初めてのAndroidアプリ

Androidアプリのプロジェクトを理解する

アプリの動作に関わる全てのファイルは、プロジェクトの単位で管理する
プロジェクトには、Javaソースコード、レイアウト情報、アプリの構成情報などが含まれる
作成したアプリは、エミュレータ、または、USBケーブルで接続することで実機で動作できる

サンプルアプリの内容を確認する

Activityクラスは、Androidアプリの画面そのものを表すオブジェクトである

public class MainActivity extends AppCompatActivity { 
    @Override
    protected void onCreate(Bundle savedInstanceState){ 
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
    }
}

Activityクラス作成時のポイント

  • Activityクラスを継承する
    • AppCompatActivityクラスは、アクションバーに対応したActivityクラス
  • アプリ起動時の処理をonCreateメソッドに記述する
  • setContentViewメソッドでビューを設定する
    • ビューとは、画面に表示すべきコンテンツを表すオブジェクトのこと
    • Rクラスは、リソースを呼び出すための各種ID値をまとめて管理している(自動生成されるものなので、自分で編集するのはだめ)

文字列や数値のデータはvaluesフォルダ以下にxmlファイルを作成して管理する

  • 文字サイズなどの情報は、dimens.xmlに
  • 文字列リソースは、strings.xmlに

アプリの基本情報はマニフェストファイルで管理する

アプリ開発の基本キーワードを理解する

Androidアプリはイベントドリブンモデルを採用している
イベントが発生したタイミングで実装すべきコードのことをイベントハンドラと呼ぶ
何らかのイベントが発生した時に呼び出されるメソッドが用意された、イベント処理専用のクラスをイベントリスナと呼ぶ

public class MainActivity extends AppCompatActivity { 
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 中略
        Button btn = (Button)findViewById(R.id.btnCurrent);
        btn.setOnClickListener(
                new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        TextView txt =     (TextView)findViewById(R.id.txtResult);
                        txt.setText(new Date().toString());
                    }
                }
        );
    }
}

画面を回転させた場合、画面はいったん破棄され、改めて再作成される
そのため、画面の表示内容を保持したい場合は、onSaveInstanceState、onRestoreInstanceStateの実装が必要となる

public class MainActivity extends AppCompatActivity { 
    // 中略
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        TextView txtResult = (TextView)findViewById(R.id.txtResult);
        outState.putString("txtResult", txtResult.getText().toString());
    }
    
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        TextView txtResult = (TextView)findViewById(R.id.txtResult);
        txtResult.setText(savedInstanceState.getString("txtResult"));
    }
}

トーストとは、Androidアプリで画面に短いメッセージを表示するための機能で、

Toast.makeText(this, new Date().toString(), Toast.LENGTH_LONG)
      .show();

のような形で呼び出せる

Androidでは、ログ出力はLogクラスで行う。構文はこんな感じ

Log.d("CurrentTime", new Date().toString());

03 ビュー開発(基本ウィジェット)

基本的な入力/出力を理解する

テキストラベルを表示する場合は、TextViewを使う
画像を表示する場合は、ImageViewを使う
Androidでは、解像度に応じて画像を切り分ける仕組みを提供している。/resフォルダ配下の、どのフォルダに保存するかで切り替わる仕組み

  • drawable-xhdpi・・・最高解像度(〜320dpi)
  • drawable-hdpi・・・高解像度(〜240dpi)
  • drawable-mdpi・・・中解像度(〜160dpi)
  • drawable-ldpi・・・程解像度(〜120dpi)

テキストボックスを表示する場合は、EditTextを使う

入力ウィジェットを理解する

チェックボックスを表示する場合は、CheckBoxを使う
オン/オフをわかりやすくボタンで表現したい場合は、ToggleButtonを使う
ラジオボタンを表示する場合は、RadioButtonを使う
シークバーを表示する場合は、SeekBarを使う
セレクトボックスを表示する場合は、Spinnerを使う

便利ウィジェットを活用する

レートの表示/更新を行いたい場合は、RatingBarを使う
アプリにWebページを埋め込みたい場合は、WebViewを使う
インターネット接続を許可するには、マニフェストファイルに以下の追記が必要

<uses-permission android:name="android.permission.INTERNET" />

04 ビュー開発(ListView/RecyclerView)

リスト作成の基本を理解する

リスト項目を静的に設定する場合は、要素のandroid:entries属性を指定する
動的に設定したい場合は、アダプタを利用する
アダプタとは、データとウィジェットの間を受け渡しするためのオブジェクトのこと

final ArrayList<String> data = new ArrayList<String>();
data.add("項目1");
data.add("項目2");

ArrayAdapter<String> adapter = new ArrayList<String>(this,
    android.R.layout.simple_list_item_1,data);
ListView list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);

ListViewのイベント処理を理解する

OnItemClickListenerを利用することで、クリック可能なリストを作成できる

ListView list = (ListView) findViewById(R.id.list);
list.setOnItemClickListener(
    new AdapterView.OnItemClickListener(){
        public void onItemClick(AdapterView<?> av, View view, int position, long id){
            // クリック時の処理を記述
        }
    }
);

android:choiceMode属性を使うことで、選択可能なリストを作れる

  • none・・・選択できない(デフォルト)
  • singleChoice・・・単一選択可能
  • multipleChoice・・・複数選択可能
  • multipleChoiceModel・・・カスタムの選択モード(複数選択可能)

複数選択可能とする場合には、#setMultiChoiceModeListenerでリスナの挙動を実装する
OnScrollListenerを使用することで、リストのスクロールを検知できる

リストのレイアウトをカスタマイズする

SimpleAdapterを使用することで、リスト項目を表示するためのレイアウトファイルとテキスト表示すべきデータとの関連付けを自由に変更できる。利用する際に必要な手順は以下の通り

  • リスト項目に表示方法を決めるためのレイアウトファイルを用意する

  • データを用意し、レイアウトファイルと関連付ける

    public class MainActivity extends AppCompatActivity {

      @Override
      protected void onCreate(Bundle savedInstanceState){
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
    
          // dataにはArrayList<HashMap<String,String>>のデータを詰める
          
          SimpleAdapter adapter = new SimpleAdapter(
              this, data, R.layout.list_item,
              new String[] {"title","tag","desc"},
              new int[] { R.id.title, R.id.tag, R.id.desc }
          );
      }
    

    }

BaseAdapterを継承することで自作のアダプタを作成できる
最低限、実装が必要なメソッドは以下の通り

  • getCount()
  • getItem(int position)
  • getItemId(int position)
  • getView(int position, View convertView, ViewGroup parent)

レイアウトファイルをViewオブジェクトに変換するには、LayoutInflater#inflateメソッドを使用する

ListViewをもっと活用する

ListActivityを利用することで、リストのみの画面を簡単に作成できる(レイアウトファイル不要)
ExpandableListViewを利用することで、階層型のリストを作成できる
検索機能付きリストを作るならSearchView

より柔軟性のあるリストを作成する

RecyclerViewを使用することで、より自由度の高いリストを作れる
事前準備として、build.gradleファイルに依存関係の追加が必要

dependencies{
  ... 中略 ...
  compile 'com.android.support.recyclerview-v7:24.2.1'
}

RecyclerViewでは、アダプタ/ビューホルダ/レイアウトマネージャを利用してリストを生成する
ビューホルダクラスの実装例
RecyclerView.ViewHolderを継承して作成する

public class MyViewHolder extends RecyclerView.ViewHolder {
    View view;
    TextView title;
    TextView tag;
    TextView desc;

    public MyViewHolder(View itemView) {
        super(itemView);
        this.view = itemView;
        this.title = (TextView) view.findViewById(R.id.title);
        this.tag = (TextView) view.findViewById(R.id.tag);
        this.desc = (TextView) view.findViewById(R.id.desc);
    }
}

アダプタクラスの実装例
RecyclerView.Adapterを継承し、以下のメソッドをオーバライドして作る

  • onCreateViewHolder
  • onBindViewHolder
  • getItemCount

レイアウトファイルからViewオブジェクトを生成するには、LayoutInflater#fromを使う

public class MyListAdapter  extends RecyclerView.Adapter<MyViewHolder> {
    private ArrayList<ListItem> data;

    public MyListAdapter(ArrayList<ListItem> data) {
        this.data = data;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(v);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.title.setText(this.data.get(position).getTitle());
        holder.tag.setText(this.data.get(position).getTag());
        holder.desc.setText(this.data.get(position).getDesc());
    }

    @Override
    public int getItemCount() {
        return this.data.size();
    }
}

アクティビティからの呼び出し例
LinearLayoutManagerをVERTICALで使用しているため、縦並びのリストとして表示される

public class MainActivity extends AppCompatActivity {

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

        // �(中略)dataの中に表示したい情報を詰める

        RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
        rv.setHasFixedSize(true);

        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        rv.setLayoutManager(manager);
        
        RecyclerView.Adapter adapter = new MyListAdapter(data);
        rv.setAdapter(adapter);
    }
}

05 ビュー開発(レイアウト&複合ウィジェット)

ウィジェットの配置方法を理解する

レイアウトはウィジェットの配置を決めるためのビューである

  • LinearLayout・・・ウィジェットを縦or横一列に配置
  • RelativeLayout・・・ウィジェットの位置を前後の相対位置を指定
  • AbsoluteLayout・・・ウィジェットの位置を絶対座標で指定(非推奨)
  • TableLayout・・・ウィジェットをテーブル上に配置
  • GridLayout・・・ウィジェットをテーブル上に配置(こちらの方が自由度の高いレイアウトが可能)
  • FrameLayout・・・ウィジェットを左上に重ねて配置
  • ConstraintLayout・・・ウィジェットの位置を「制約」と呼ばれるルールで指定

android:layout_gravity属性の設定値

  • horizontal系
    • top・・・上寄せ
    • center_vertical・・・垂直方向に中央寄せ
    • bottom・・・下寄せ
  • vertical系
    • left・・・左寄せ
    • center_horizontal・・・水平方向に中央寄せ
    • right・・・右寄せ
    • start・・・コンテナの先頭
    • end・・・コンテナの終端
  • その他
    • center・・・垂直&水平方向に中央寄せ

ウィジェットのサイズを指定する方法

  • wrap_content・・・コンテンツのサイズに合わせる
  • match_parent・・・親要素のサイズに合わせる
  • 絶対値・・・絶対値でサイズを指定
    • dp・・・解像度に依存しない単位
    • sp・・・解像度に依存しない単位(文字サイズに使用)

RelativeLayout配下で利用できる主なレイアウト属性

  • 基準系
    • android:layout_above・・・指定したウィジェットの上に配置
    • android:layout_below・・・指定したウィジェットの下に配置
    • android:layout_toLeftOf・・・指定したウィジェットの左に配置
    • android:layout_toRightOf・・・指定したウィジェットの右に配置
    • android:layout_toStartOf・・・指定したウィジェットの始端に配置
    • android:layout_toEndOf・・・指定したウィジェットの終端に配置
  • 配置系
    • android:layout_alignTop・・・指定したウィジェットの上端に合わせる
    • android:layout_alignBottom・・・指定したウィジェットの下端に合わせる
    • android:layout_alignLeft・・・指定したウィジェットの左端に合わせる
    • android:layout_alignRight・・・指定したウィジェットの右端に合わせる
    • android:layout_alignStart・・・指定したウィジェットの始端に合わせる
    • android:layout_alignEnd・・・指定したウィジェットの終端に合わせる
  • マージン系
    • android:layout_margin・・・上下左右の余白
    • android:layout_marginTop・・・上の余白
    • android:layout_marginBottom・・・下の余白
    • android:layout_marginLeft・・・左の余白
    • android:layout_marginRight・・・右の余白
    • android:layout_marginStart・・・始端の余白
    • android:layout_marginEnd・・・終端の余白

国/地域によっては、文字を左→右ではなく、右→左で表現する言語もある
国際化を意識した場合、Start〜、End〜を使う方が望ましい
マージンは「他の要素との余白」、パディングは「枠とコンテンツ本体との余白」

タブパネルやフリップ可能なビューを作成する

タブパネルを作成する場合、TabHostを使用する
ビューをフリップ操作で切り替えたい場合、ViewFlipperを使用する(フォトギャラリーを作りたい場合とかに使う)
画面をスクロール可能にするには、ScrollViewを使用する

06 ビュー開発(ダイアログ&メニュー)

さまざまなダイアログを作成する

標準ライブラリを利用することで以下のダイアログを作成できる

  • AlertDialog・・・警告ダイアログ(ボタン、ラジオボタン、チェックボックス、リストなども表示できる)
  • DatePickerDialog・・・日付選択ダイアログ
  • TimePickerDialog・・・時刻選択ダイアログ
  • ProgressDialog・・・プログレスバーや待ちアイコンを表示するための進捗ダイアログ

ダイアログは、アクティビティではなくフラグメントとして実装する

public class MyDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        return builder.setTitle("Sample Dialog")
                .setMessage("Hello World!")
                .create();
    }
}

フラグメントの用意ができたら、アクティビティから起動

public class MainActivity extends AppCompatActivity {    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DialogFragment dialog = new MyDialogFragment();
        dialog.show(getFragmentManager(), "dialog_basic");
    }
}

ダイアログにボタンを表示したい場合は以下のメソッドを使う

  • setPositiveButton
  • setNegativeButton
  • setNeutralButton

リストなど、その他UIを表示したい場合は以下のメソッドを使う

  • setItems
  • setSingleChoiceItems
  • setMultiChoiceItems

ダイアログへの値の引き渡しはDialogFragment#setArgumentsメソッドを利用し、Bundle経由で行う
AndroidのUIはシングルスレッドで動作している(別スレッドからUIを更新しようとすると例外を吐く)
スレッド間でメッセージをやりとりする際には、Handlerクラスを利用する

アクティビティの実装例

public class MainActivity extends AppCompatActivity {
    MyDialogFragment dialog;
    Handler handler;
    Thread t;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        handler = new MyHandler(this);

        Button btn = (Button)findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog = new MyDialogFragment();
                dialog.show(getFragmentManager(), "dialog_basic");

                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 100; i++) {
                            handler.sendEmptyMessage(1);
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        handler.sendEmptyMessage(0);
                    }
                });
                t.start();
            }
        });
    }
}

ハンドラの実装例

public static class MyHandler extends Handler {
    private final WeakReference<MainActivity> activity;

    MyHandler(MainActivity activity) {
        this.activity = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        MainActivity a = activity.get();
        switch (msg.what) {
            case 1:
                a.dialog.setProgress(a.dialog.getProgress() + 1);
                break;
            case 0:
                a.dialog.dismiss();
                break;
        }
    }
}

オプションメニューやコンテキストメニューを作成する

メニューの項目や構造は、メニュー定義ファイルで定義する
オプションメニューは、onCreateOptionsMenuメソッドで作成できる

public class MainActivity extends AppCompatActivity {
    // 中略
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.option_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item){
        // メニュー項目選択時の動作を定義
        return true;
    }
}

コンテキストメニューは、onCreateContextMenuメソッドで作成できる
コンテキストメニューを有効にするには、

registerForConetxtMenu(View view)
    view: コンテキストメニューを関連づけるべきビュー

で「このビューを長押しした時にコンテキストメニューを表示しますよ」ということを登録しておく必要がある

07 ビュー開発(応用)

ビュー描画の基本をおさえる

ビューを自作したい場合は、Viewクラスを継承する
具体的な描画コードはonDrawメソッド内に記述する
ビューに対して図形を描画する際には、Canvasクラスを使用する
ビュー描画のスタイルは、Paintクラスで指定する

ビュー描画の理解を深める

invalidateメソッドを呼び出すことで、ビューの状態が更新される
Viewクラスは描画が低速なため、表示を頻繁に更新する必要がある場合はSurfaceViewを使う
SurfaceViewは例外的に別スレッドからのGUI操作が認められている(Handlerなどを使わなくて良い)

アプリにアニメーション機能を実装する

Androidのアニメーションクラスいろいろ

  • View・・・特定のビューを移動/回転/拡大させていくアニメーション
  • Property・・・任意のオブジェクトを移動/回転/拡大させていくアニメーション
  • Drawable・・・複数の画像を順番に入れ替えていくアニメーション

アプリのデザインを一元管理する

スタイルは、個々のウィジェットに対するスタイル関係の属性をまとめたリソースファイルのこと
テーマは、アクティビティ全体、アプリ全体で適用できるスタイル設定のこと
テーマとスタイルは、styles.xmlで定義できる
テーマは、マニフェストファイルのapplication、またはactivity要素のandroid:style属性で適用できる

08 インテント

インテントの基本を理解する

インテントとは、アクティビティ間で情報をやりとりするための仕組みのこと
インテントには、明示的インテントと暗黙的インテントがある

  • 明示的インテント・・・インテントの送り先(クラス名)を明示的に指定するインテント。アプリ内部での画面移動に使用
  • 暗黙的インテント・・・やりたいことのみを指定し、送り先を明示しないインテント。別のアプリを呼び出すのに使用

呼び出し元画面のアクティビティ

public class MainActivity extends AppCompatActivity {
    // 中略
    public void btnSend_onClick(View v){
        // SubActivityへのインテントを作成
        Intent i - new Intent(this,SubActivity.class);
        // アクティビティを起動
        startActivity(i);
    }
}

呼び出し先画面のアクティビティ

public class SubActivity extends AppCompatActivity {
    // 中略
    public void btnBack_onClick(View v){
        // SubActivityを終了
        finish();
    }
}

アクティビティを追加した場合は、マニフェストファイルにアクティビティ定義を追加する

<application ...>
    <activity android:name=".SubActivity"/>
</application>

アクティビティには、生成から破棄までの過程で呼び出されるライフサイクルメソッドが用意されている

  • アクティビティの起動時・・・onCreate → onStart → onResume
  • 画面の切り替え時・・・onPause → (Sub)onCreate → (Sub)onStart → (Sub)onResume → onStop
  • サブ画面終了時・・・(Sub)onPause → onRestart → onStart → onResume → (Sub)onStop → (Sub)onDestroy

画面間でデータを授受する

インテントにデータを設定するには、Intent#putExtraメソッドを利用する
インテントに設定されたデータを取得するには、Intent#getXXXExtraメソッドを利用する
呼び出し先のアクティビティから結果を受け取るには、startActivityForResultメソッドを使う

呼び出し元画面のアクティビティ

public class MainActivity extends AppCompatActivity {
    // 中略
    public void btnSend_onClick(View v){
        Intent i - new Intent(this,SubActivity.class);
        this.startActivityForResult(i,1);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 1 && resultCode == RESULT_OK){
            // 結果を受け取ってトースト表示
            String txtName = data.getStringExtra("txtName");
            Toast.makeText(this,
                String.format("こんにちは、%sさん!",txtName),
                Toast.LENGTH_SHORT).show();
        }
    }
}

呼び出し先画面のアクティビティ

public class SubActivity extends AppCompatActivity {
    // 中略
    public void btnBack_onClick(View v){
        EditText txtName = (EditText) findViewById(R.id.txtName);
        // インテントに入力値をセット
        Intent i = new Intent();
        i.putExtra("txtName", txtName.getText().toString());
        // 結果情報をセット
        setResult(RESULT_OK,i);
        // 現在のアクティビティを終了
        finish();
    }
}

「やりたいこと」からアプリを起動する

暗黙的インテントを使用する場合は、コンストラクタにアクションの種類を指定する

Intent i = new Intent(Intent.ACTION_VIEW,Uri.parse(https://twitter.com));
startActivity(i);
  • 電話を起動する・・・ACTION_VIEWに「tel:電話番号」の形式でUriを渡す
  • 連絡先を起動する・・・ACTION_VIEWに「content://contacts/people/番号」の形式でUriを渡す
  • メール送信画面を起動する・・・ACTION_VIEWに「mailto:メールアドレス」の形式でUriを渡す

Web検索を実行する場合は、ACTION_WEB_SEARCHを使う

Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
i.putExtra(SearchManager.QUERY,"検索文字列");
startActivity(i);

暗黙的インテントを受け取るには、マニフェストファイルで処理できるアクションと、データの種類を宣言しておく

画面サイズの異なるデバイスに対応する

コンテンツをフラグメントで管理することで、タブレット/スマホ双方に対応したアプリを開発しやすくなる
フラグメントを作成するには、fragment継承クラスを準備する
フラグメントをレイアウトに組み込むには、fragment要素、またはFragmentTransactionクラスを利用する

09 データ管理

ファイルにデータを保存する

アプリからアクセスできるフォルダは制限されている(許可されていない場合は例外を吐く)
アプリ専用のフォルダに対してアクセスするには、openFileInput、openFileOutputメソッドを利用する

public abstract FileOutputStream openFileOutput(String name, int mode)
    name: ファイル名  mode: オープンモード

引数モードの設定値

  • MODE_PRIVATE・・・現在のアプリからのみ利用可能

  • MODE_APPEND・・・追記モードで開く

  • MODE_WORLD_READABLE・・・他のアプリから読み込み可能

  • MODE_WORLD_WRITABLE・・・他のアプリから書き込み可能

    public abstract FileInputStream openFileInput(String name) name: ファイル名

Android 4.4以降で、Storage Access Frameworkが追加され、ファイル選択/入力の仕組みを組み込みやすくなった
エミュレータ上のファイルシステムを確認する場合、Android Device MonitorのFile Explorerを利用する

データベースにデータを保存する

SQLiteデータベース利用の流れ

  • SQLiteOpenHelperでデータベースを生成&オープン
  • SQLiteDatabaseでデータベースを操作
  • 操作した結果を画面に反映

SQLiteOpenHelper実装クラスのサンプル

public class SimpleDatabaseHelper extends SQLiteOpenHelper {
    static final private String DBNAME = "sample.sqlite";
    static final private int VERSION = 1;
    
    public SimpleDatabaseHelper(Context content){
        super(content, DBNAME, null, VERSION);
    }

    public void onCreate(SQLiteDatabase db){
        // データベース作成時のDDL等を記述
        db.execSQL("CREATE TABLE ...");
    }

    public void onUpgrade(SQLiteDatabase db, int old_v, int new_v){
        // データベースをバージョンアップした時の処理を記述
    }
}

SQLiteDatabaseでも、準備済みSQLを用意して「?」部分を置き換えると処理を効率化できる

  • public SQLiteStatement compileStatement(String sql)
  • public void bindXxxx(int index, Xxxx value)

データを登録するサンプル

// helperはSQLiteOpenHelperの実装クラスのインスタンス
SQLiteDatabase db = helper.getWritableDatabase();
try{
    ContentValues cv = new ContentValues();
    cv.put("COLUMN_NAME","value");
    db.insert("TABLE_NAME", null, cv);
} finally {
    db.close();
}

書き込みを行う場合は、getWritableDatabaseで書き込み用のオブジェクトを取得する
書き込み系のメソッドは以下の通り

  • insert
  • insertWithOnConflict
  • update
  • delete

データを検索するサンプル

// helperはSQLiteOpenHelperの実装クラスのインスタンス
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cs = null;
try{
  String[] cols = {"isbn", "title", "price"};
  String[] params = {"9784798048536"};
    cs = db.query("books", cols, "isbn = ?", params, null, null, null);
    if(cs.moveToFirst()){
        Toast.makeText(this,cs.getString(1),Toast.LENGTH_SHORT).show();
    }
} finally {
    cs.close();
    db.close();
}

読み込みを行う場合は、getReadableDatabaseで読み込み用のオブジェクトを取得する
読み込み系のメソッドは以下の通り

  • query

データの取得はCursorオブジェクトから行う

アプリの設定情報を管理する

Preferenceは、アプリの設定情報を管理するための仕組み
設定項目を表す定義ファイルを準備するだけで、UIや設定情報の保存を簡単に実装できる
設定項目の主な属性

  • android:key・・・設定項目のキー
  • android:title・・・設定項目の表示タイトル
  • android:summary・・・設定項目の説明文
  • android:defaultValue・・・デフォルトの設定値

1.設定定義ファイルを作成する
2.設定画面のフラグメントを作成する

public class MyConfigFragment extends PreferenceFragment{
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.pref);
    }
}

3.設定画面のアクティビティを作成する

public class MyConfigActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.replace(android.R.id.contemt, new MyConfigFragment());
        fragmentTransaction.commit();
    }
}

4.呼び出し元画面のアクティビティを作成する

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 設定情報の取得
        SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
    }

    public void btn_onClick(View view){
        Intent i = new Intent(this, MyConfigActivity.class);
        startActivity(i);
    }
}

さまざまなPreference

  • CheckBoxPreference
  • SwitchPreference
  • ListPreference
  • MultiSelectPreference
  • RingtonePreference
  • PreferenceCategory

10 ハードウェアの活用

位置情報を取得する

Googleマップを利用するには、Google Play servicesのライブラリのインストールが必要
専用の「Google Maps Activity」テンプレート経由で、Google Maps API Keyを入手すると良い
Google Maps APIの実装例

public class MapsActivity extends FragmentActivity implements OnMapReadyCallbacks {
    private GoogleMap mMap;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        // フラグメントを取得
        SupportMapFragment mapFragment = 
          (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReader(GoogleMap googleMap){
        mMap = googleMap;
        
        // 指定の座標にマーカを追加
        LatLng sydney = new LatLng(-34, 151);
        mMap.addMarker(new MarkerOptions()
          .position(sydney)
          .title("Marker in Sydney"));
        // 表示位置を移動
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
    } 
}

現在位置を監視&表示するサンプル

public class MapsActivity extends AppCompatActivity
        implements OnMapReadyCallback,
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener, LocationListener {
    private GoogleApiClient client;
    private LocationRequest request;
    private FusedLocationProviderApi api;
    private GoogleMap mMap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);
        
        // パーミッションの確認&要求
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[] {android.Manifest.permission.ACCESS_FINE_LOCATION}, 1);
        }

        // マップを取得
        SupportMapFragment mapFragment = (SupportMapFragment)
                getSupportFragmentManager().findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

        // 位置情報のリクエスト情報を取得
        request = LocationRequest.create()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setInterval(1000)
            .setFastestInterval(15);
        api = LocationServices.FusedLocationApi;
        // Google Plyaへの接続クライアントを生成
        client = new GoogleApiClient.Builder(this)
            .addApi(LocationServices.API)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .build();
    }

    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;
        // デフォルトの表示位置をセット
        LatLng current = new LatLng(35.670292, 139.773006);
        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(current, 16f));
    }
    @Override
    protected void onResume() {
        super.onResume();
        // Google Playへの接続
        if(client != null) {
            client.connect();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 位置情報リクエストの解除、及びGoogle Playからの切断
        if (client != null && client.isConnected()) {
            api.removeLocationUpdates(client, this);
        }
        client.disconnect();
    }

    @Override
    public void onConnected(Bundle bundle) {
        パーミッションの確認
        if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) !=  PackageManager.PERMISSION_GRANTED)  {
            return;
        }
        // 位置情報の監視を開始
        api.requestLocationUpdates(client, request, this);
    }

    @Override
    public void onConnectionSuspended(int i) {
        // 接続が中断された時の処理
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        // 接続が切断された時の処理
    }

    @Override
    public void onLocationChanged(Location location) {
        if(mMap == null) { return; }
        // 位置情報が更新された時、カメラ位置を移動
        LatLng current = new LatLng(
                location.getLatitude(), location.getLongitude());
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(current, 16f));
    }
}

FusedLocationProviderApiクラスを利用することで、適切な方法で現在位置を取得できる
位置情報を取得する際には、パーミッション設定をマニフェストファイルに追加する必要がある �

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

実行時に個々の機能へのアクセス許可を与える仕組みをRuntime Permissionという

HTTPでサーバと通信する

HTTP経由での通信には、HttpURLConnectionクラスを利用する
Androidでは、メインスレッドでのネットワークアクセスは許可されていない(NetworkOnMainThread例外がスローされる)
Androidでは、アプリで非同期処理を実装するための専用クラスとして、AsyncTaskを提供している

AsyncTask派生クラスの実装例

public class AsyncNetworkTask extends AsyncTask<String, Integer, String> {
    private TextView txtResult;
    private ProgressBar progress;

    // 結果を反映させるTextViewを取得
    public AsyncNetworkTask(Context context) {
        super();            
        MainActivity activity = (MainActivity)context;
        txtResult = (TextView)activity.findViewById(R.id.txtResult);
        progress = (ProgressBar)activity.findViewById(R.id.progress);
    }

    // 非同期でHTTP GETリクエストを送信する
    @Override
    protected String doInBackground(String... params) {
        // 進捗の通知をそれっぽく見せるダミー処理。ここから↓
        publishProgress(30);
        SystemClock.sleep(3000);
        publishProgress(60);
        // ここまで↑
        StringBuilder builder = new StringBuilder();
        try {
            URL url = new URL(params[0]);
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("GET");
            // 取得↓レスポンスを読み込み
            BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream(), "Shift-JIS"));
            String line;
            while ((line = reader.readLine()) != null){
                builder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        publishProgress(100);
        return builder.toString();
    }

    // プログレスバーを表示
    @Override
    protected void onPreExecute() {
        progress.setVisibility(ProgressBar.VISIBLE);
    }

    // 進捗状況を反映
    @Override
    protected void onProgressUpdate(Integer... values) {
        // Log.d("url", values[0].toString());
        progress.setProgress(values[0]);
    }

    // 非同期処理を終了した後、TextViewに反映
    @Override
    protected void onPostExecute(String result) {
        txtResult.setText(result);
        progress.setVisibility(ProgressBar.GONE);
    }

    // 非同期処理を終了した後、TextViewに反映
    @Override
    protected void onCancelled() {
        txtResult.setText("キャンセルされました。");
        progress.setVisibility(ProgressBar.GONE);
    }
}

AsyncTaskの型パラメータ

  • Params・・・非同期処理時に別スレッドに渡す情報の型
  • Progress・・・進捗状況を管理するための型
  • Result・・・非同期処理の結果を表す型

AsyncTaskの呼び出し実装例

public class MainActivity extends AppCompatActivity {
    AsyncNetworkTask task;

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

        // 非同期処理を実行
        task = new AsyncNetworkTask(this);
        task.execute("http://www.google.com");
    }

    // キャンセルボタン押下時、非同期処理を取り消す
    public void btn_onClick(View view){
        task.cancel(true);
    }
}

JSONデータを解析する場合には、JSONObjectクラスを利用する

public JSONObject(String json)

キーに対応する値が配列の場合、getJSONArrayで値を取得する
配列から個々の要素にアクセスする場合は、#getJSONOBject()メソッドで、引数にインデックスを渡す

public JSONArray getJSONArray(String name)

取得したJSONオブジェクトから値を取得する際には、getJSONObjectを使う

public JSONObject getJSONObject(String name)

非同期にデータをロードする場合は、AsyncTaskLoaderクラスを使う

音声ファイルを再生/録音する

音声ファイルを再生する場合、MediaPlayerクラスを使う
音声を録音する場合、MediaRecorderクラスを使う

ハードウェアのその他の機能

SensorManager/Sensorクラスを使うことで、デバイスに搭載されている各種センサを利用できる
Androidで利用できる主なセンサ

  • TYPE_ACCELEROMETER・・・加速度センサ
  • TYPE_AMBIENT_TEMPERATURE・・・湿度センサ
  • TYPE_GRAVITY・・・重力センサ
  • TYPE_GYROSCOPE・・・ジャイロセンサ
  • TYPE_LIGHT・・・照度センサ
  • TYPE_LINEAR_ACCELERATION・・・線形加速センサ
  • TYPE_MAGNETIC_FIELD・・・磁界センサ
  • TYPE_PRESSURE・・・圧力センサ
  • TYPE_PROXIMITY・・・近接センサ
  • TYPE_RELATIVE_HUMIDITY・・・相対湿度センサ

GestureDetectorクラスを使用することで、長押しやフリックを簡単に検出できる
バイブレーションの動作を実装するには、Vibratorクラスを使う

11 サービス開発&アプリの公開

サービスを開発する

サービスは画面を持たないプログラムである
サービスは、Serviceクラスを継承して作成する

サービスクラスの実装例

public class SimpleService extends Service {

    @Override
    public void onCreate(){
        super.onCreate();
        // サービスの初回起動時の処理を記述
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId){
        // サービス起動の都度、実行する処理を記述
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent){
        // サービスのバインド時に実行する処理を記述
        return null;
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
        // サービスの停止時に実行する処理を記述
    }
}

onStartCommandのメソッド戻り値

  • START_NOT_STICKY・・・サービスを再起動しない
  • START_STICKY・・・サービスを再起動する
  • START_REDELIVER_INTENT・・・終了前と同じインテントを使って再起動
  • START_STICKY_COMPATIBILITY・・・再起動は保証されない

サービスクラスの呼び出し例

Intent i = new Intent(this, SimpleService.class);
startService(i);
// 停止する場合は、stopService()を使う

サービスを追加した場合は、アクティビティと同様、マニフェストファイルに追記が必要

<service android:name=".SimpleService" />

onStartCommandメソッドでは、時間のかかる処理を記述してはいけない
処理を定期実行する場合は、Timer#scheduleメソッドを利用する
別スレッドで動作するサービスを定義する場合は、IntentServiceを使用する。オーバライドが必要なのは以下の二つ

  • コンストラクタ
  • onHandleIntentメソッド

IntentServiceはワークキュープロセッサパターンで動作する
サービスからアクティビティにデータを渡すには、ブロードキャストを利用する
ブロードキャストを配信するには、

  • ServiceクラスのonStartCommand内でsendBroadcastする
  • レシーバを用意する
  • レシーバをシステムに登録する

という段取りが必要
レシーバの実装例

public class SimpleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent){
        String msg = intent.getStringExtra("message");
        Toast.makeText(context,"現在時刻:" + msg,Toast.LENGTH_SHORT).show();
    }
}

レシーバを登録する実装例

SimpleReceiver receiver = new SimpleReceiver();
IntentFilter fileter = new IntentFilter();
filter.addAction(SimpleService.ACTIONS);
registerReceiver(receiver, filter);

サービスの状態をユーザに通知するには、ノティフィケーションを利用する
ノティフィケーションを使うためには、

  • ノティフィケーションの本体を準備する
  • PendingIntentオブジェクトを用意する
  • NotificationManagerにNotificationを登録する

という段取りが必要
実装例

public class SimpleService extends Service {
    private static final String TAG = "SimpleService";
    private static final int NOTIFY_ID = 0;
    private NotificationManager manager;
    private Timer timer = new Timer();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Notification notif = new Notification.Builder(this)
                .setContentTitle("SimpleService")
                .setContentText("サービスは起動中です。")
                .setWhen(System.currentTimeMillis())
                .setVibrate(new long[]{1000, 500, 1000, 500, 2000, 500})
                .setContentIntent(
                        PendingIntent.getActivity(this, MainActivity.ACTIVITY_ID,
                                new Intent(this, MainActivity.class),
                                PendingIntent.FLAG_CANCEL_CURRENT)
                )
                .build();
        manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        manager.notify(NOTIFY_ID, notif);
    
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.i(TAG, "onStartCommand");
            }
        }, 0, 5000);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        manager.cancel(NOTIFY_ID);
        Log.i(TAG, "onDestroy");
        timer.cancel();
    }
}

自作のアプリを公開する

Google Playに登録するには、デベロッパアカウントが必要
アプリを公開するには、あらかじめパッケージファイルや証明書、アイコン画像、スクリーンショットなどを用意しておく必要がある

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment