phicdy devlog

Androidアプリ開発やその他技術系の記事をたまに書きます

AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

【Android】PreferenceActivity/PreferenceFragmentを使った設定画面

(追記) 2018/11/15 書き直しました。

phicdy.hatenablog.com

Androidの設定画面について

  • AndroidではAPI1から設定画面を生成してくれるPreferenceActivityが用意されている
  • Android 3.0で各設定画面をFragmentに分けるためのPreferenceFragmentが追加された
  • Android 4.0ではPreferenceActivityが拡張され、PreferenceFragmentと組み合わせることでハンドセットでは1画面、タブレットでは2画面の設定画面を自動的に作成できる

設定項目

クラス 説明
CheckBoxPreference オン/オフの設定項目
SwitchPreference オン/オフの設定項目
API14から
ListPreference リストの中から1つ選ぶ設定項目
MultiSelectListPreference リストの中から複数を選ぶ設定項目
EditTextPreference 入力からの設定

1画面の設定画面

  • 設定画面を2ページに分けない場合は、画面のルートに当たるandroid.R.id.contentに直接PreferenceFragmentを設定する

PreferenceActivity

public class SettingActivity extends PreferenceActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getFragmentManager().beginTransaction()
            .replace(android.R.id.content, new SettingFragment())
            .commit();
    }
}

PreferenceFragment

  • addPreferencesFromResource()で設定画面を定義したXMLを読み込む
  • 必要であれば、onActivityCreated()で各設定項目の初期値等を設定する
  • XMLで設定したPreferenceを取得するにはandroid:keyを使い、PreferenceFragment#findPreference()で行う
public class SettingFragment extends PreferenceFragment {

    public SettingFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.setting_fragment);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initView();
    }

    private void initView() {
        ListPreference listPreference = (ListPreference)findPreference(getString(R.string.key_list_preference));
        listPreference.setValueIndex(0);
    }

}

PreferenceFragmentから読み込む設定XML

  • PreferenceFragmentから読み込むXMLのトップはPreferenceScreenでなければならない
  • 各Preferenceにはandroid:keyを設定しなければならない。これが各Preferenceを特定するIDとなる。
  • カテゴリを設定する場合はPreferenceCategoryを使う。android:keyは必須ではない。
  • ListPreferenceの場合、選択候補に表示されるリスト(android:entries)と実際の値のリスト(android:entryValues)を設定する必要がある
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
    <PreferenceCategory android:title="Category Title" >
        <ListPreference
            android:title="Title"
            android:key="@string/key_list_preference"
            android:entries="@array/entry_sample"
            android:entryValues="@array/entry_sample_values"/>
    </PreferenceCategory>

</PreferenceScreen>

その他の属性は以下を参照

http://developer.android.com/reference/android/preference/Preference.html

array.xml

ListPreferenceで使う値を定義する

<resources>
    <array name="entry_sample">
        <item>@string/entry1</item>
        <item>@string/entry2</item>
    </array>

    <string-array name="entry_sample_values">
        <item>1</item>
        <item>0</item>
    </string-array>

</resources>

string.xml

  • android:keyに使う文字列はXMLとPreferenceFragmentで共有するので、string.xmlに定義しておく
<resources>
    <string name="entry1">Entry1</string>
    <string name="entry2">Entry2</string>
    <string name="key_list_preference">KeyListPreference</string>
</resources> 

初期値設定

XMLで初期値を設定する場合はandroid:defaultValueに設定する。SharedPreferenceから保存した値を読み込んでそれに応じて初期値を設定する場合など、Javaコードの中で初期値を設定を行う場合はPreferenceによって設定方法が異なる

SwitchPreference

setChecked()で行う

SwitchPreference switchPref = (SwitchPreference)findPreference(getString(R.string.key_internal_browser));
switchPref.setChecked(true);

ListPreference

  • ListPreferenceのentryValuesのstring-arrayの各itemが数字だとsetDefaultValue()が動かなかった
  • setValueIndex()を使うことで解決
  • entryValuesをinterger-arrayにするとNullPointerExceptionで落ちてしまう

参考

【Rasberry Pi】カメラモジュールでmmal: No data received from sensorエラー

Rasberry Pi B+とカメラモジュールを買ってみたので使ってみたところエラーで買い直しになった・・・


Raspberry Pi Camera Board | Raspberry Pi Video Module Raspberry Pi Camera Board | Raspberry Pi 【通販RS】

$ raspistill -o test.jpg
mmal: No data received from sensor. Check all connections, including the Sunny one on the camera board


調査

設定は以下コマンドで行った。

# 最新カーネルにアップデート
$ sudo apt-get update
$ sudo apt-get upgrade

# 8 Advanced Options -> A5 Enable Camera -> Enable
$ sudo raspi-config


再起動後、vcgencmdコマンドで接続チェックをしたところ、カメラの接続はできている模様・・・raspistillコマンドを打った際にもカメラのランプ自体は付いている。

$ vcgencmd get_camera
supported=1 detected=1


そもそも同じエラーでぐぐってみても87件しかそもそも検索結果がない。

"mmal no data received from sensor" - Google 検索


ソフトウェア側の問題でなんとか解決できないかと思って下記URLなどを参照したが改善される様子はなし・・・

そもそも解決している人が見つからなかった。


https://www.raspberrypi.org/forums/viewtopic.php?f=43&t=105100

stackoverflow.com

raspberrypi.stackexchange.com

morinezumiiii.hatenablog.com

Raspberry Piにカメラを接続する | テクニカルタイムアウト


最後に

秋月電子で買ったので、14日以内にちゃんと試しておけば交換してもらえたのかな・・・まあ勉強代と思って今回は諦めました。

【Java】メインスレッドから別スレッドの停止

自分用メモ。


メインスレッドからの停止

  • Thread#interrupt()で行う。Thread#stop()メソッドは安全性を壊す可能性があり、非推奨
  • Thread#interrupt()自体はインタラプト状態を変更するだけ。
  • ただし、スレッドがThread#sleep(), Thread#wait(), Thread#join()を実行して止まっている間にThread#interrupt()を実行するとInterruptedExceptionを投げる。
  • これはThread#sleep(), Thread#wait(), Thread#join()側がメソッドの中でスレッドのインタラプト状態を見て、明示的にInterruptedExceptionを投げているため
  • Thread#sleep(), Thread#join()を読んだスレッドに対しては、Thread#interrupt()を実行する際にスレッドのロックを取る必要はなく、いつでも呼ぶことができる
  • Thread#wait()を呼んだスレッドに対してThread#interrupt()を呼ぶとロックを再び取り直してからInterruptedExceptionを投げる
  • Thread#isInterrupted()でインタラプト状態を取得できる


スレッド自身を止めるメソッド

メソッド 説明
sleep() 指定された時間だけ実行を停止
wait() スレッドがウェイトセットに入ってnotify/notifyAllされるのを待つ
join() 指定したスレッドが終了するのを待つ


コード

public class Sample {
     private Thread thread = null;

     public void startThread() {
         thread = new Thread() {
              @Override
               public void run() {
                   while(!isInterrupted()) {
                        // 処理
                   }
               }
         };
         thread.start();
     }

     public void stopThread() {
         if (thread == null) {
             return;
         }
         thread.interrupt();
     }
}

Intel Stick PCを買いました

f:id:phicdy:20150612194158j:plain

インテルから発売されたIntel Stick PCを買いました。

www.iodata.jp


Intel Stick PCとは

  • インテルから発売されたスティック型のPC
  • テレビのHDMIポートに刺して使う
  • サイズは37(W)×103(D)×12(H)mmで非常に小さい
  • Windows8.1搭載
  • 値段はアイ・オー・データのページで税込み22140円
  • 冷却ファンを搭載し、安定な動作を実現


スペック

アイ・オー・データのページより抜粋

www.iodata.jp

項目 仕様
OS Windows 8.1 with Bing 32ビット
CPU インテル® Atom™ プロセッサー Z3735F(4コア、1.33GHz)
メモリ DDR3L(1.35V、1333 MHz、2GB)
ストレージ 32GB eMMC
解像度 1920×1080
通信機能 IEEE802.11b/g/n、Bluetooth4.0
インターフェイス HDMI映像出力×1、USB 2.0ポート×1、microSDXCカードスロット×1
ネットワーク IEEE 802.11 b/g/n、Bluetooth® V4.0
外形寸法(本体のみ) 37(W)×103(D)×12(H)mm ※突起部除く
質量(本体のみ) 約54g


中身

f:id:phicdy:20150612195348j:plain

  • 本体
  • 電源アダプター
  • 変換プラグ
  • HDMI延長ケーブル
  • 電源供給用USBケーブル
  • マカフィーアンチウイルスプラス1年ライセンス
  • かんたんセットアップガイド
  • Quick Start Guide(英語)

電源アダプターの変換プラグが何種類かついているので海外旅行にも持っていけそう。。。

f:id:phicdy:20150612195726j:plain


接続

まず電源アダプターに日本用のプラグをつける。

f:id:phicdy:20150612195852j:plain

f:id:phicdy:20150612195909j:plain

f:id:phicdy:20150612195924j:plain

電源アダプターと本体をUSBケーブルで繋ぐ

f:id:phicdy:20150612200001j:plain

テレビのHDMIポートに接続する。

電源アダプターをコンセントに入れると自動で電源がつく

f:id:phicdy:20150613115939j:plain

初期設定の注意点として有線マウス・キーボードがないと初期設定ができない。

私は後で気付いて買いに行きました・・・


使用感

4KのYoutubeを再生してみたところ、4K(2160p)、HD(1440p)は再生が厳しかったが、それ以下なら再生できた。


GoPro: Tomorrowland in 4K - YouTube

USB端子が1つしかないのでUSBハブが必須。

キーボードとマウスならだけ電源アダプター付きのUSBハブでなくてもいけたが、これ以上何かつけようと思ったら電源アダプター付きのUSBハブにする必要がある。

旅行に持っていきやすいとは思うが、キーボードとマウスを持っていく必要があるので、それならノートPCでいいような気がする・・・

テレビの裏につけるだけでWindows PCが使えるので、ノートPCを買うまでもない人なら2万程度でPCが増えるのでいいと思う。

Minecraft買って動かせるか試してみたいところ。

モバイルバッテリーでも起動できるかもしれないのでこちらも試してみたい。

DockerでAndroidの環境を構築する

Androidでテストを同時並行に実行したいなーと思っていたので作ってみた。

github.com


Javaのインストール

github.com

こちらからコードを拝借。ただ、add-aptがデフォルトではUbuntuに入っていないのでatp-getでインストールする

# For add-apt-repositor in order to install Java
RUN apt-get install -y software-properties-common

# Install Java
RUN \
  echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | debconf-set-selections && \
  add-apt-repository -y ppa:webupd8team/java && \
  apt-get update && \
  apt-get install -y oracle-java8-installer && \
  rm -rf /var/lib/apt/lists/* && \
  rm -rf /var/cache/oracle-jdk8-installer

# Define commonly used JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-8-oracle

Android SDKのインストール

Android SDKGoogleのページにあるので、ダウンロードしてくればよい。問題はtoolsなどのインストールで、インストールの最中度々承認を求められる。今回はexpcetコマンドを使って、特定の文が来たらそれに対してキーを送るようにしてスキップしている。

あとうまくいかなかった点としては、32bit互換性のためにライブラリをインストールすることと、toolsのアップデート中にtmpフォルダへのコピーが失敗してインストールが失敗するということがあった。

前者はapt-getでlib32stdc++6をインストールする。 後者は、toolsを一旦別フォルダに退避し、退避したtoolsのandroidコマンドを使ってtoolsのアップデートを行う。

ADD expect-android-update.sh .
RUN chmod +x expect-android-update.sh
RUN apt-get install -y wget
RUN apt-get update
RUN apt-get -y install expect
RUN apt-get -y install lib32stdc++6
RUN wget http://dl.google.com/android/android-sdk_r24.2-linux.tgz && \
    mv android-sdk_r24.2-linux.tgz /opt && \
    cd /opt && \
    tar zxvf android-sdk_r24.2-linux.tgz && \
    rm android-sdk_r24.2-linux.tgz && \
    cd android-sdk-linux && \
    cp -a tools copy-tools && \
    /expect-android-update.sh platform-tools && \
    ./platform-tools/adb kill-server && \
    rm -rf temp/ && \
    /expect-android-update.sh tools && \
    /expect-android-update.sh build-tools-22.0.1,android-18,android-19,android-21,android-22,sys-img-armeabi-v7a-android-22,sys-img-armeabi-v7a-android-21,sys-img-armeabi-v7a-android-19,sys-img-armeabi-v7a-android-18
         
ENV ANDROID_SDK_HOME /opt/android-sdk-linux
ENV ANDROID_HOME /opt/android-sdk-linux
ENV PATH $PATH:/opt/android-sdk-linux/platform-tools:/opt/android-sdk-linux/tools

adbで端末のモデル名を取得する

adb shell cat /system/build.prop | grep ro\.product\.model | awk -F"=" 'NR==1 {print $2}' | tr -d ' ' | awk -v RS='\r\n' '{print $1}'


詳細はこちらから

d.hatena.ne.jp


ほとんどの機種は上記記事のコマンドで問題ないのだが、Nexus5のような端末だとro.product.modelが「Nexus 5」のようにスペースが入っている。

そのため、tr -d ' ' を追加した。

Alfredからビルドする

最近、なるべくマウスから手を離したくないなーと思ってなんでもかんでもターミナルでコマンドなりシェルスクリプトでやろうとしている。

ビルドに関してもオプションを受け取れるようにして、シェルスクリプトで切り替えているのだけども、実行するには一回ターミナルを開いて実行して終わったら元の画面に戻って・・・ということになるので、なるべく開きたくない。

これをAlfredでなんとかならないかなと試行錯誤してできるようになったのでメモ。

環境

Alfredとは

AlfredはMac用のランチャーアプリ。バックグラウンドで常駐し、option+spaceでいつでもどこからでも起動できる。

www.alfredapp.com

AlfredのUI自体はただの入力欄で、起動するとフォアグラウンドに出てきて、アプリの頭文字を入力することで素早くアプリを起動できる。

f:id:phicdy:20150407235051p:plain

Alfredのいいところはいつでもどこからでも起動できるというところで、どんな作業をしていようとアプリを起動できる。

またアプリ起動だけでなく、Google検索やファイル検索といった検索機能もカスタマイズして利用できる。

今回はビルドを行うアプリケーションを作成して、それをAlfredから起動することで、いつでもどこからでもビルドができるようにした。

そのビルドを行うアプリケーションを簡単に作れるのがMacに標準で入っているAutomatorである。

Automatorの設定

Automatorでは開始の通知の表示とビルドスクリプトの実行、 そして終了の通知の表示を行うだけである。

まず、Automatorを開き通知を右の欄にドラッグする。

f:id:phicdy:20150418170039j:plain

タイトル等は適当に。

次にシェルスクリプトの実行をドラッグする。 ここでの注意点として、Automatorからシェルスクリプトを実行する際、bash_profileが読み込まれないので、明示的に読み込む必要がある。

f:id:phicdy:20150418170100j:plain

最後にもう一度通知を追加して保存する。

f:id:phicdy:20150418170125j:plain

保存先は、Alfredの検索対象(/Applicationsなど)となっているフォルダにする。

Alfredから起動できるようになる。

f:id:phicdy:20150418194536j:plain

通知は右上に出る。

f:id:phicdy:20150418194609j:plain

通知がすぐ消えるのを変えたい場合は、設定から通知を通知パネルに変更すれば、自分で消すまでは残り続ける

f:id:phicdy:20150418194623j:plain

おわりに

Alfredからビルドできるようになったけど、ビルド対象やビルドオプションが増えるたびにAutomatorで設定する必要があるのどうにかならないかな。。。