MPD (Music Protocol Daemon)のインストール

MPDをUbuntuマシンにインストールして、Bluetoothスピーカーで聴く設定方法です。

rtmpdumpで録音した番組を聴く方法を色々試してみましたが、MPDはファイルの同期など必要なく録画したテレビ番組を見るようにただリモンの再生ボタンを押すだけという手軽さが気に入りました。

ここではMPDのインストール方法と、ちょっとハマったBluetoothスピーカで聴くための設定方法を書きます。

MPDとは

MPD (Music Protocol Daemon)は、コンピュータに貯めてある音楽ファイルを再生させるためのプログラムです。

コンピュータに貯めた音楽ファイルを再生する方法は次の3パターンがあります。

  • 手元のスマホやパソコンにコピー(同期)して再生する。
  • 音楽ファイルを配信して手元で再生する。
  • 音楽ファイルを貯めているコンピュータで直接再生する。

MPDは、最後の音楽ファイルを貯めているコンピュータで再生するためのプログラムです。そのため基本的にそのコンピュータに接続されたスピーカーからしか音を出せません1。移動しながら聴くというよりは、家や職場など決まった場所で聴くのに向いています。逆に言うと移動しないためデジタル信号を音に変換するDACやスピーカーの大きさに制限がなく音に凝ることが可能です。

MPDのインストール

今回のパソコン環境は、次のようにUbuntu 16.10(デスクトップ版)です。

$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.10
Release:	16.10
Codename:	yakkety

MPDに必要なプログラムは全てパッケージ化されているので、インストールに難しい所は全くありません。

$ sudo apt install mpd avahi-daemon

avahi-daemonは、zeroconfのために必要です。zeroconfを使用しない場合は、インストールする必要はありません。

MPDの設定

MPDの設定ファイルは、/etc/mpd.confです。このファイルを編集して自分の環境に合わせます。

私は次のようにして、Musicフォルダをパスワード無しで直接公開し、内蔵スピーカとBluetoothスピーカーで聞けるようにしました。またZeroconfを有効にしているので、MPDが動いているコンピュータはDHCPで動的にIPアドレスが割り振られていても問題ありません。

# MPDが音楽ファイルを探すディレクトリー設定
music_directory		"/home/user/Music"

playlist_directory	"/var/lib/mpd/playlists"
db_file			"/var/lib/mpd/tag_cache"
log_file		"/var/log/mpd/mpd.log"
pid_file		"/run/mpd/pid"
state_file		"/var/lib/mpd/state"
sticker_file            "/var/lib/mpd/sticker.sql"

user			"mpd"

# MPDがlistenするアドレス。anyだと全てのインターフェイスでlistenする。
bind_to_address		"any"

# 音楽ファイルが追加や削除されると自動的にMPDに反映されるようにする。
auto_update    		"yes"

# Zeroconfを有効にする。
zeroconf_enabled	"yes"
zeroconf_name		"Music Player T400"

input {
        plugin "curl"
}

# 内蔵スピーカ
audio_output {
	type		"alsa"
	name		"Internal Speaker"
	mixer_type      "disable"
}

# USB接続のスピーカ
# deviceは、aplay -lコマンドを使って調べる。
audio_output {                                                                  
        type            "alsa"                                                  
        name            "USB Speaker"                                           
        device          "hw:1,0"
}

# sinkの値は、pactlコマンドを使って調べます。
# http://aki.sunnyday.jp/blog/?p=1310
# この設定については後で詳しく説明します。
# PluseAudioの設定を修正しないとBluetoothスピーカからは音が出ません。
audio_output {
	type		"pulse"
	name		"Bluetooth Speaker"
	server		"localhost"
	sink		"bluez_sink.00_1A_7D_E2_36_7B"
}

# ncmpcppでビジュアライゼーションを有効にしたい時だけ。
# https://wiki.archlinuxjp.org/index.php/Ncmpcpp                                
#audio_output {                                                                 
#    type                    "fifo"                                             
#    name                    "my_fifo"                                          
#    path                    "/tmp/mpd.fifo"                                    
#    format                  "44100:16:2"                                       
#}                                                                              

filesystem_charset	"UTF-8"
id3v1_encoding		"UTF-8"

MPDの設定はこれだけです。

設定がすんだら再起動して設定を反映させます。

$ sudo systemctl restart mpd.service
$ sudo systemctl status mpd.service
● mpd.service - Music Player Daemon
   Loaded: loaded (/lib/systemd/system/mpd.service; enabled; vendor preset: enab
   Active: active (running) since 木 2017-02-23 09:23:34 JST; 8s ago
     Docs: man:mpd(1)
           man:mpd.conf(5)
           file:///usr/share/doc/mpd/user-manual.html
 Main PID: 32426 (mpd)
    Tasks: 6 (limit: 4915)
   CGroup: /system.slice/mpd.service
           └─32426 /usr/bin/mpd --no-daemon

 2月 23 09:23:34 T400 systemd[1]: Started Music Player Daemon.
 2月 23 09:23:35 T400 mpd[32426]: Feb 23 09:23 : server_socket: bind to '0.0.0.0

MPDのテストと音楽ファイルを再生

Zeroconfの確認

まず初めにzeroconfiが有効になっているか確認します。私はAndroidのZeroConf Browserを使用しました。

MPDが見つからない時は、avahiが動いているかとMPDの設定でzeroconfを有効にしているか確認します。avahiが動いているかは、netstatコマンドを使用します。

$ netstat -ln46|grep 5353
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp        0      0 0.0.0.0:5353            0.0.0.0:*
udp6       0      0 :::5353                 :::*

zeroconfでMPDを見つけるには、avahi-utilsパッケージに含まれるavahi-browseコマンドも使用できます。ただしこのコマンドは、コマンドを実行したコンピュータ上のサービスを表示しません。

$ avahi-browse -t -all
+ enp0s25 IPv6 Music Player on RERUN                         Music Player Daemon  local
+ enp0s25 IPv4 Music Player on RERUN                         Music Player Daemon  local

systemdでの問題

Ubuntuなどsystemdを使用しているマシンでは、次のようなログを残してZeroconfでMPDを見つけられないことがあります。

/var/log/mpd/mpd.log

zeroconf: No global port, disabling zeroconf

この場合は、Creating a home music server using mpdにあるようにmpd.socketを停止します。

sudo systemctl stop mpd.service
sudo systemctl stop mpd.socket
sudo systemctl disable mpd.socket
sudo systemctl start mpd.service

これでZeroconfでMPDを見つけられるようになります。

MPDで音楽を再生

MPDは音楽を再生するサーバなので、クライアントプログラムが必要です。

クライアントプログラムには、次のように色々な種類があります。

CUI
mpc, ncmpc, ncmpcppなど。mcpは純粋にコマンドラインで使用するプログラムなのでシェルプログラムと組み合わせて使用するのに都合が良いです。ncmpcとncmpcppは、ncursesを使用するので、CUIと言ってもGUIのような感じで使用できます。ncmpcとncmpcppの使い方はほとんど同じなので、使い方はNcmpcppを参照して下さい。
GUI
sonata, arioなど。sonataは、初期設定ではアルバム名が表示されませんが、設定で変更できます。それでもarioの方が私は好みでした。
Android
MPDroid, plainMPDCなど。MPDroidが人気みたいですが、私はplainMPDCが気に入りました。ただplainMPDCは結構頻繁にMPDと接続できなることが欠点です。

Bluetoothスピーカ

MPDの音声をBluetoothスピーカに送る設定の部分は合っているはずですが、Bluetoothスピーカから音が出ない、または再生できないといういうエラーになります。ここでだいぶ悩みました。

mpd.confからBluetoothに関わる部分を抜き出すと次のようになっています。

audio_output {
	type		"pulse"
	name		"Bluetooth Speaker"
	server		"localhost"
	sink		"bluez_sink.00_1A_7D_E2_36_7B"
	# sink            "bluez_sink.00_1A_7D_E2_36_7B.a2dp_sink" # On Ubuntu 17.04
}

鍵となるのは、serverとsinkの設定です。serverはPulseAudioの動いているホスト名で、sinkは音声の送り先です。

sinkの値が間違っているのかと思いpactlコマンドを使って調べましたが、間違っていません。

$ sudo pactl list|grep Sink
Sink #0
Sink #37
	Monitor of Sink: alsa_output.pci-0000_00_1b.0.analog-stereo
	Monitor of Sink: n/a
	Monitor of Sink: bluez_sink.00_1A_7D_E2_36_7B
		a2dp_sink: High Fidelity Playback (A2DP Sink) (sinks: 1, sources: 0, priority: 10, available: yes)

そしてはたとひらめきました。

PulseAudioの設定にserverを指定するということはネットワークに関する設定が絡んでいるのでないかと推定しました。そこでググるとTCP サポートと匿名クライアントがヒットしました。内容を要約するとPulseAudioをネットワーク経由で使うには、ネットワークモジュールのロードと設定が必要だということです。

Ubuntu 16.10のデスクトップ版ではPulseAudioの設定は/etc/pulse/default.paにあります。これを確認すると、ネットワークモジュールがロードされないような設定になっています。

そこでPulseAudioの設定ファイルを次のように修正しました。

# ローカルホストからの接続のみをパスワード無しで受け入れる。
load-module module-native-protocol-tcp auth-ip-acl=127.0.0.1

設定を修正したら、PulseAudioを再起動させます。pulseaudioコマンドを使用する方法もあるようですが、sysctlコマンドまたはserviceで再起動する方法が分からなかったのでパソコンごと再起動しました。

再起動したらpacmdコマンドでmodule-native-protocol-tcpがロードされているか確認します。

$ pacmd list-modules
<SNIP>
index: 11
	name: <module-native-protocol-tcp>
	argument: <auth-ip-acl=127.0.0.1>
	used: -1
	load once: no
	properties:
		module.author = "Lennart Poettering"
		module.description = "Native protocol (TCP sockets)"
		module.version = "9.0"
<SNIP>

これでMPDクライアントから再生を指示するとBluetoothスピーカからも音声が流れてきて問題が解決しました。

参照と注釈

  1. MPDは基本的にそのプログラムが走っているコンピュータのスピーカーからしか音を出せませんが、PulseAudioを使うことで別のコンピュータに音声を送って聴くことができます。

[[コマンドの正規表現(後方参照と文字クラス)

bashの[[コマンドでは正規表現を使用できる。正規表現にマッチした部分は後方参照できる。正規表現にはPOSIX形式の文字クラスを指定できる。

[[コマンド

[[コマンドは、多くの場合ifと組み合わせて条件判断に使用されます。この[[コマンドは、[コマンドのただ等しい・大小比較だけではなく=~で正規表現にマッチするか調べられます。

$ [[ "abcdefg" =~ ^abc ]] && echo TRUE
TRUE

正規表現の後方参照

正規表現にマッチした部分は、変数BASH_REMATCHに代入されます。

$ [[ "a1234b" =~ [0-9]+ ]] && echo ${BASH_REMATCH}
1234

変数BASH_REMATCHは配列変数で、添字を指定することで対応する括弧の部分を取り出すことが可能です。

$ [[ "a1234b" =~ a([0-9]+)([a-z]) ]] && echo ${BASH_REMATCH[@]}
a1234b 1234 b

正規表現に文字クラスを使う

正規表現には、文字クラスも使用可能です。ただし次のような制限があります。

  • 文字クラスは選択[]内でのみ使用できる。
  • Perl系の表記方法(例えば数字を表す\d)を使用できない。代わりにPOSIXの表記方法(数字であれば[:digit:])を使用する。
$ [[ "a1234b" =~ a[:digit:]+b ]] || echo FALSE
FALSE
$ [[ "a1234b" =~ a[[:digit:]]+b ]] && echo TRUE
TRUE
$ [[ "a1x2y34b" =~ a([[:digit:]xy]+) ]] && echo ${BASH_REMATCH[1]}
1x2y34

POSIXの主な文字クラスは次のようになります。

文字クラス Perl系 意味
[:digit:] \d 数字
[:xdigit:] \h(Ruby) 16進数での数字
[:alnum:] \w(\wは_を含む) アルファベットと数字
[:space:] \s 改行を含む空白

参照

「らじる★らじる」をrtmpdumpで録音する

「らじる★らじる」はrtmpdumpコマンドを使用すると簡単に録音できる。rtmpdumpで録音したファイルは、ffmpegコマンドでMP3に変換する。

たまたまNHKのラジオドラマ(オーディオドラマと今は呼ぶらしい)を聞いて、それ以来NHKのラジオドラマを気に入ってしまいました。ラジオドラマは、テレビと違って目を使う必要がないのでご飯を食べながら楽しむのにちょうど良さそうです。

しかしNHKラジオのネット放送「らじる★らじる」radikoと違って放送を後から聞くことができません。そこで「らじる★らじる」を録音して好きな時間に聞けるようにしました。これでNHKのラジオ語学講座も一週間遅れでの配信を待つ必要がなくなります。

「らじる★らじる」の録音

「らじる★らじる」の放送を録音するには、rtmpdumpコマンドを使用します。録音したファイルは、ffmpegコマンドでMP3ファイルに変換します。

インストール

rtmpdumpコマンドのインストールは、Ubuntuでは次のように簡単に導入できます。同時にMP3に変換するffmepgコマンドとID3タグを編集・表示するeyed3コマンドをインストールしておきます。

$ sudo apt install rtmpdump
$ sudo apt install ffmpeg eyed3

録音方法

「らじる★らじる」の放送を録音するrtmpdumpコマンドの肝は指定するオプションです。そこでもう何も考えずにriocampos/らじるらじるをrtmpdumpで録音する(仙台・名古屋・大阪も)を参考にさせてもらいます。

ファイルをMP3に変換

rtmpdumpはMP3よりも高圧縮なMPEG4 Audio(M4A)形式で録音しますが、私の音楽プレイヤがサポートしていないようなのでffmpegコマンドを使って一般的なMP3形式にトランスコードします。必要であればビットレートの指定などもffmpegコマンドのオプションで指定できます。

$ ffmpeg -i nhk.m4a -acodec libmp3lame nhk.mp3

MP3ファイルにID3タグを登録

ID3タグはffmpegコマンドにmetadataオプションを指定することで登録できます。複数のタグを指定する場合は、metadataオプションを必要なだけ追加します。

$ ffmpeg -i nhk.m4a -acodec libmp3lame -metadata "artist"="らじる ★ らじる" nhk.mp3
$ eyeD3 nkh.mp3
nhk.mp3	[ 469.47 KB ]
-------------------------------------------------------------------------------
Time: 01:00	MPEG1, Layer III	[ 64 kb/s @ 48000 Hz - Stereo ]
-------------------------------------------------------------------------------
ID3 v2.4:
title: 		artist: らじる ★ らじる
album: 		year: 
track: 		
Publisher/label: 

たくさんのID3タグがありますが、今回は次のタグ(metadataキー)を使用しました。

metadataのキー 意味
album アルバム名(番組名)
artist アーティスト名(らじるに固定)
title タイトル(放送日)
publisher 出版社名(放送局)
date 出版年(放送年)

「らじる★らじる」を録音するシェルスクリプト

以上のコマンドをまとめて「らじる★らじる」を録音するシェルスクリプトを作成しました。このスクリプトを使えば、次のようにcrontabに設定することでNHKのラジオドラマを自動的に録音してMP3ファイルに変換することができます。

45 22 * * 1,2,3,4,5 rec-radio.sh fm 15m 青春アドベンチャー
0 22 * * 6 rec-radio.sh fm 50m FMシアター
20 19 * * 0 rec-radio.sh r1 30m 新日曜名作座

ログファイルへの書き込みを追いかける

目標

ログファイルに処理記録が書き出される時に、追加された書き込みを見守りたい。

方法

ファイルに追加された書き込みを追いかけるには、tailコマンドに-Fオプションを付けて使用します。

$ tail -F logfile

これで追加された書き込みが標準出力に表示され続けます。

tailコマンドには似たオプションの-fがあります。これも同じく追記された部分が表示されますが、ログローテートなどでファイル名が変更された時の動作が異なります。

オプション ファイル名が変わった時の動作
-F 同名のファイルを開いて追記を追いかける。
-f そのまま追記を待ち続ける。

ログを追いかけている時にファイル名が変わるのは大抵ログローテートによるものなので、新しいログファイルを開いて追記を追いかけれれる-Fオプションを使うことになります。

lessコマンドもある

ログの追記を見守りたいが、同時に過去の書き込みも遡って読みたい時にはlessコマンドが便利です。

$ less logfile

Gキーを押すと常にファイルの最後が表示され、再度Gを押すとその時の最後に更新されます。

そしてFキーを押すとtail -fと同じくファイルに追記されるたびに更新されます。ただし追記から更新までのタイムラグがtail -fよりも少し大きくなります。また-fオプションと同じなので、ファイル名が変更されても同名の新しいファイルには自動では切り替わりません1

脚注

  1. lessのコマンド入力モードでhキーを押してヘルプを表示させれ戻ると、開いていたファイルと同名の新しいファイルの内容が表示されます。逆に元のファイルに戻りたい時にはどうしたら良いのか?

ListViewを含むDialogFragmentの表示を速くする

症状

custom viewを表示するリストをDialogFragmentで表示させようとしたところ、Dialogが表示されるまで二秒以上かかっていました。

どこで時間がかかるのか調べてみると、ListViewに設定したadapterのgetView()メソッドが繰り返し呼び出されていました。

改善方法

そこで同じような例がないかとググってみると、layout_heightをmatch_parentにすればgetView()メソッドの無駄な呼び出しを抑えられるというページ1を見つけました。

そこでDialogFragment内でAlterDialog.Builderに設定するViewを次のようにしてみましたが、最初のgetViewから最後まで2.1秒もかかって効果はありませんでした。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/node_list_view" />
</LinearLayout>
12-27 12:29:22.896 31433-31433/jp.nosuz.* D/DialogList: getView: 0
12-27 12:29:22.916 31433-31433/jp.nosuz.* D/DialogList: getView: 1
...
12-27 12:29:25.028 31433-31433/jp.nosuz.* D/DialogList: getView: 7
12-27 12:29:25.028 31433-31433/jp.nosuz.* D/DialogList: getView: 8

そこでさらに検索するとRelativeLayoutが有効というページ2を見つけました。

このページに従ってLinearLayoutを次のようにRelativeに書き換えると、getView()メソッドを呼び出す回数がぐっと減りました。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/node_list_view" />
</RelativeLayout>

最初のgetViewから最後まで2.1秒かかっていたものが0.07秒になり、表示されるまでの「あれ?」というストレスがなくなりました。

12-27 12:39:58.967 9244-9244/jp.nosuz.* D/DialogList: getView: 16
12-27 12:39:58.977 9244-9244/jp.nosuz.* D/DialogList: getView: 15
12-27 12:39:58.987 9244-9244/jp.nosuz.* D/DialogList: getView: 14
12-27 12:39:59.007 9244-9244/jp.nosuz.* D/DialogList: getView: 13
12-27 12:39:59.007 9244-9244/jp.nosuz.* D/DialogList: getView: 12
12-27 12:39:59.017 9244-9244/jp.nosuz.* D/DialogList: getView: 11
12-27 12:39:59.027 9244-9244/jp.nosuz.* D/DialogList: getView: 10
12-27 12:39:59.027 9244-9244/jp.nosuz.* D/DialogList: getView: 9
12-27 12:39:59.037 9244-9244/jp.nosuz.* D/DialogList: getView: 8

欠点

ただしlayout_heightにmatch_parentを指定しているので、表示する項目数が少ない場合にもDialogが最大の大きさで表示され無駄な空白ができてしまいます。

参照

ArrayAdapterがNullPointException

NullPointExceptionという例外でアプリが終了するのはよくあるバグです。ログを見ればどこで例外を起こしているか分かるのですが、今回は次のようにArrayAdapterの中で例外が起きていて困りました。

このNullPointExceptionの原因は、ArrayAdapterを作る時に渡したList<>の中にnullが入っていたことでした。

FATAL EXCEPTION: main
Process: jp.nosuz.secretkeyring, PID: 11911
java.lang.NullPointerException
    at android.widget.ArrayAdapter.createViewFromResource(ArrayAdapter.java:394)
    at android.widget.ArrayAdapter.getView(ArrayAdapter.java:362)
    at android.widget.AbsListView.obtainView(AbsListView.java:2263)
    at android.widget.ListView.makeAndAddView(ListView.java:1790)
    at android.widget.ListView.fillDown(ListView.java:691)
    at android.widget.ListView.fillFromTop(ListView.java:752)
    at android.widget.ListView.layoutChildren(ListView.java:1630)
    at android.widget.AbsListView.onLayout(AbsListView.java:2091)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.support.v7.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:433)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
    at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
    at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
    at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
    at android.view.View.layout(View.java:14948)
    at android.view.ViewGroup.layout(ViewGroup.java:4631)
    at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1991)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1748)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1004)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5692)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
    at android.view.Choreographer.doCallbacks(Choreographer.java:574)
    at android.view.Choreographer.doFrame(Choreographer.java:544)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:212)
    at android.app.ActivityThread.main(ActivityThread.java:5151)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:877)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)
    at dalvik.system.NativeStart.main(Native Method)

参照

Spinner: NullPointerException at ArrayAdapter.createViewFromResource –stackoverflow

Androidで高速なファイルコピー

Android(Java)でファイルコピーをするのには、単純にInputから読んで、それをそのままOutputに書き出す方法が単純です。しかしFileChannelを使用するとシンプルかつ高速にファイルをコピーすることができます。

ただしFileChannelを使用したコピーでは一度にコピーできるサイズが2GBに制限されるという仕様があるので、どこまでコピーしたか確認しながらコピーするように修正しました。

public static boolean copy(File src, File dst) {
        boolean result = false;
	
        FileInputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            inStream = new FileInputStream(src);
            outStream = new FileOutputStream(dst);
            FileChannel inChannel = inStream.getChannel();
            FileChannel outChannel = outStream.getChannel();
            long pos = 0;
            while (pos < inChannel.size()) {
                pos += inChannel.transferTo(pos, inChannel.size(), outChannel);
            }
            result = true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (inStream != null) inStream.close();
                if (outStream != null) outStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if ((! result) && dst.exists()) dst.delete();

        return result;
    }

純粋にJavaでのコピーならが、Files.copy()を使用するのが一番簡単です。

参照

How to make a copy of a file in android? – stackoverflow

AndroidのService#OnStartCommand()の返り値

Androidのサービスは、起動する時に呼ばれるメソッドOnStartCommandの返り値によって強制終了された時の振る舞いが変わります。

START_STICKY
強制終了されても自動的にサービスが再起動されます。再起動時にはOnStartIntentに渡されるintentがnullの可能性があります。
START_NOT_STICY
強制終了されても再起動はされない。
START_REDELIVER_INTENT
強制終了されても自動的にサービスが再起動されます。この時START_STICKYとは異なり最初に起動された時のintentが再起動する時にも渡されます。

START_STICKY or START_REDELIVER_INTENT

サービスが強制終了された時に自動的に再起動するための返り値が二種類あるけど、intentの有無の他に違いはないのでしょうか?

そこで簡単に実験してみたところ、START_STICKYとSTART_REDELIVER_INTENTではサービスが再起動されるまでの時間が異なるようです。具体的には、START_STICKYは強制終了してもすぐに再起動されますが、START_REDELIVER_INTENTは再起動されるまでに分単位で時間がかかりました。

返り値 再起動までの時間
START_STICKY すぐに再起動
START_REDELIVER_INTENT 再起動まで1分くらい

サービスが強制終了させられてもすぐに同じintentでサービスを開始したい場合は、intentの内容をSharedPreferenceやデータベースに書いておき、START_STICKYを指定してサービスを起動するのが良さそうです。そしてintentがnullでサービスが起動されたら保存しておいたintent内容を読んで再開するという方法を取ります。

参照

Service – Developers

UbuntuでCapsLockキーをControlキーに

LinuxでCapsLockとControlキーを入れ替えるには一般的にxmodmapコマンドを使用します。しかしUbuntuでCapsLockキーとControlを入れ替えるには、dconfコマンドで簡単に変更できます。

参照したページではUbuntu 14.04LTSが対象と書かれていましたが、Ubuntu 16.04LTSでも有効でした。

dconfでCapsLockキーもControlキーに

今回はControlキーが壊れたようで認識しなくなったので、交換ではなく次のようにしてCapsLockもControlキーとすして使えるようにしました。

$ dconf write /org/gnome/desktop/input-sources/xkb-options "['ctrl:nocaps']"

xmodmapでCapsLockキーもControlキーに

xmodmapコマンドを使用してCapsLockキーもControlキーと使用できるようにするには、例えば設定を記述するファイルとして~/.Xmodmapファイルを作成して、そこに次のように設定を書き込みます。

keycode  66 = Control_L Control_L Control_L Control_L
remove Lock = Control_L
add Control = Control_L

これをxmodmapコマンに読み込ませます。

$ xmodmap ~/.Xmodmap

この方法は、dconfと異なりログアウトすると設定を忘れてしまうので、.bash_profileにxmodmap ~/.Xmodmapを追加しておく必要があります。

参照

UbuntuTips/Desktop/HowToSetCapsLockAsCtrl – ubuntu Japanese Team Wiki

Android Studioの編集画面を二分割

別のファイルや編集中のファイルの別の箇所を参照しながら編集する時には、画面を分割して同時に見られるそうにすると便利です。

Android Studioで編集画面を分割するには、メニュータブからは、「Window→Editor Tabs→Spilit」と進みます。

標準のキーマップではこの操作にショートカットが設定されていませんが、Emacsに変更すると、次のようにEmacsでの画面分割キーシーケンスを使用できます。

縦に二分割(上下に分割)
Ctrl-x, 2
横に二分割(左右に分割)
Ctrl-x, 3
分割画面を結合
ファイル名タブの「X」をクリックするとタブが消え、全てのタブがなくなると画面が結合されます。またCtrl-x, 1では、全ての分割を一度に消して一つの画面にできます。

分割した画面の割合は、画面の境界をドラッグすることで自由に変更できます。

分割した画面で同じ操作を繰り返すと、さらに場面を分割することができます。

screenshot-from-2016-12-14-16-43-28

別のウィンドウへ

画面を分割ではなく別のウィンドウで開くことも可能です。

別のウィンドウで開くには、ファイル名のタブをつかんでAndroid Studioのウィンドウの外にドラッグします。