Androidでファイル名と行番号がログに出力する

これをやるとログを書いたところのファイル名と行番号がログに出力されます

多分どこぞの誰かがすでにやってると思いますが(検索したら大量に出てきて常識レベルだったっぽい!恥ずかしい。。)

import android.util.Log;

public class Logger {
	public static void log(String msg) {
		StackTraceElement calledClass = Thread.currentThread().getStackTrace()[3];
		Log.d(calledClass.getFileName() + ":"
				+ calledClass.getLineNumber(), msg);
	}
}

これだけです
あとは呼び出し側でこんなふうに呼び出せば

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        setContentView(R.layout.activity_main);
	Logger.log("Logging!!");
     }
}

こんな感じでわかる!!!

08-05 20:34:12.592: D/MainActivity.java:41(1431): Logging!!

takam

macでfontforgeをインストールするときにlibiconvがbrewから使えなくて困った

brew install fontforge

でインストールしようとして

ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[1]: *** [../libgunicode.la] Error 1
make: *** [libgunicode] Error 2

こんな感じで怒られたので

brew install fontforge -v

したら

Undefined symbols for architecture x86_64:
  "_libiconv", referenced from:
....

んな感じで怒られてたので
libiconvをインストールしてみると。。。

brew install libiconv 
Error: No available formula for libiconv
Apple distributes libiconv with OS X, you can find it in /usr/lib.
Some build scripts fail to detect it correctly, please check existing
formulae for solutions.

っていう感じで怒られたので、
ホントは/usr/libに入っているのを使えたら良かったんだけどやり方がよく分からなかったので

wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.13.1.tar.gz
tar xvfz libiconv-1.13.1.tar.gz
cd libiconv-1.13.1
./configure --prefix=/usr/local/Cellar/libiconv/1.13.1
make
sudo make install

でとりあえず自力でインストール(http://bullrico.com/2012/07/12/installing-nokogiri-after-updating-homebrew/)を参考にしちゃったのですが、今は
http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz
のほうがいいかもです。
このままだとまだ最初と同じエラーが出るので

brew link libiconv
brew install fontforge

でいけましたという話

takam

JNIを使った 最小構成

OpenCVとか触っていきたいので、ちょっとずつ学んでいきます。
ほぼメモ書きです。

とりあえず
MainActivity.java
Android.mk
hello-jni.c
の3ファイルが有り、ちゃんとEclipseの設定がしてあれば動く模様です。
JNIから文字列を取得してトーストに出す例です。
ほぼサンプルから持ってきただけです。
src/Packageへのパス/MainActivity.java

package com.example.ndksample;

import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;

import com.example.ndk_sample.R;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this, stringFromJNI(), Toast.LENGTH_LONG).show();
    }


    public native String  stringFromJNI();
 
    static {
        System.loadLibrary("hello-jni");
    }
}

jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.c

include $(BUILD_SHARED_LIBRARY)

jni/hello-jni.c

#include <string.h>
#include <jni.h>

jstring
Java_com_example_ndksample_MainActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

takam

録音と再生のIOストリームで入力した音をリアルタイム再生する

録音の仕方
データストリームとして音声データを読み込む - Androidプログラマへの道 〜 Moonlight 明日香 〜 - Seesaa Wiki(ウィキ)
再生の仕方
音声を使って楽しげなものを作りたい。再生をする。 - 素人のアンドロイドアプリ開発日記
オーディオのオブジェクトの取得の仕方
android - AudioRecord object not initializing - Stack Overflow

一部意味などは分かっていませんが
この3つを組み合わせてできました

package com.example.recodeplay;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder.AudioSource;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {
	AudioRecord audioRec = null;
	Button btn = null;
	boolean bIsRecording = false;
	int bufSize;
	int samplingRate;
	private AudioTrack audioTrack;

	private void recodingAndPlay() {
		if (bIsRecording) {
			bIsRecording = false;
		} else {
			// 録音開始
			Log.v("AudioRecord", "startRecording");
			audioRec.startRecording();
			bIsRecording = true;
			// 録音スレッド
			new Thread(new Runnable() {
				@Override
				public void run() {
					byte buf[] = new byte[bufSize];
					// TODO Auto-generated method stub
					while (bIsRecording) {
						// 録音データ読み込み
						audioRec.read(buf, 0, buf.length);
						audioTrack.write(buf, 0, buf.length);
						// Log.v("AudioRecord", "read " + buf.length +
						// " bytes");
					}
					// 録音停止
					Log.v("AudioRecord", "stop");
					audioRec.stop();
				}
			}).start();
		}
	}

	private static int[] mSampleRates = new int[] { 8000, 11025, 22050, 44100 };

	public AudioRecord findAudioRecord() {
		for (int rate : mSampleRates) {
			for (short audioFormat : new short[] {
					AudioFormat.ENCODING_PCM_8BIT,
					AudioFormat.ENCODING_PCM_16BIT }) {
				for (short channelConfig : new short[] {
						AudioFormat.CHANNEL_IN_MONO,
						AudioFormat.CHANNEL_IN_STEREO }) {
					try {
						Log.d("TAG", "Attempting rate " + rate + "Hz, bits: "
								+ audioFormat + ", channel: " + channelConfig);
						int bufferSize = AudioRecord.getMinBufferSize(rate,
								channelConfig, audioFormat);

						if (bufferSize != AudioRecord.ERROR_BAD_VALUE) {
							// check if we can instantiate and have a success
							AudioRecord recorder = new AudioRecord(
									AudioSource.DEFAULT, rate, channelConfig,
									audioFormat, bufferSize);

							if (recorder.getState() == AudioRecord.STATE_INITIALIZED)
								bufSize = bufferSize;
							samplingRate = rate;
							return recorder;
						}
					} catch (Exception e) {
						Log.e("TAG", rate + "Exception, keep trying.", e);
					}
				}
			}
		}
		return null;
	}

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

		// AudioRecordの作成
		audioRec = findAudioRecord();

		audioTrack = new AudioTrack(AudioManager.STREAM_VOICE_CALL, samplingRate,
				AudioFormat.CHANNEL_CONFIGURATION_MONO,
				AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STREAM);
		audioTrack.play();
		findViewById(R.id.button1).setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				recodingAndPlay();
			}
		});
	}

}

普通に出した声が耳の所で聞こえる!!
つまりこれができるってことはラインみたいな音声通話って結構簡単にできそう。。。
takam

HttpURLConnectionのdisconnect()を使うと他の通信が中断されてしまうことがあるので注意

AndroidでHTTPでデータをダウンロードしてこようとした。
2.09. ネットワーク通信 · mixi-inc/AndroidTraining Wiki · GitHubを参考に

	private String downloadUrl(String urlString) {
		StringBuilder result = new StringBuilder();
		HttpURLConnection urlConnection = null;
		try {
			URL url = new URL(urlString);
			urlConnection = (HttpURLConnection) url.openConnection();
			urlConnection.connect();
			InputStream inputStream = urlConnection.getInputStream();
			while (true) {
				byte[] data = new byte[1024];
				int size = inputStream.read(data);
				if (size <= 0) {
					break;
				}
				result.append(new String(data, "utf-8"));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			urlConnection.disconnect();
		}
		return result.toString();
	}

こんなように関数を書いていた。(通信するAsyncTaskのdoInBackground()からこの関数を呼び出す)
一回通信するにはちゃんと動いていたのだが、
このような通信をするボタンを連打した時、片方の通信では内容が途中までしか取得できなかった。(Android 4.2のエミュレータ内)
HttpURLConnection (Java Platform SE 6)javadocによると

要求後、HttpURLConnection の InputStream または OutputStream 上で close() メソッドを呼び出すと、そのインスタンスに関連付けられていたネットワークリソースが解放される可能性がありますが、共有されている持続接続への影響はまったくありません。disconnect() メソッドを呼び出した場合、持続接続がその時点でアイドル状態になっていれば、使用していたソケットがクローズされる可能性があります。

と書いており、共有されている接続へ影響がないInputStreamのclose()メソッドを利用することでこの問題は解決できた
最終的に以下のようなソースコードにすることにした。
またAndroid公式のトレーニングConnecting to the Network | Android Developersにもそのように記述されている。

	private String downloadUrl(String urlString) {
		StringBuilder result = new StringBuilder();
		HttpURLConnection urlConnection = null;
		try {
			URL url = new URL(urlString);
			urlConnection = (HttpURLConnection) url.openConnection();
			urlConnection.connect();
			InputStream inputStream = urlConnection.getInputStream();
			while (true) {
				byte[] data = new byte[1024];
				int size = inputStream.read(data);
				if (size <= 0) {
					break;
				}
				result.append(new String(data, "utf-8"));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			urlConnection.disconnect();
		}
		return result.toString();
	}

takam

Chromeアップデートが行われ、HTMLファイルのNotificationが動かなくなった話

Notification API の変更

先日、Google Chrome のアップデートが行われ、Google Chrome 28 になった。
レンダリングエンジンが Webkit から 新レンダリングエンジン Blink に移行したりと大きな変更があった様子。
新機能としてリッチ通知が追加された。
その関係からか、Notification API にも変更があったようだ。

具体的には、HTML ファイルを指定して Notification させる方法が利用できなくなっている。

基本的な Notification の使用

アイコン、タイトル、メッセージを指定して表示する方法

このコードはこれまで通り動く。

var n = window.webkitNotifications.createNotification(icon, title, message);
n.show();

HTMLファイルを指定して Notification として表示する方法

このコードは動かなくなってしまっている。

var file = "notification.html"
var n = window.webkitNotifications.createHTMLNotification(file);
n.show();

createHTMLNotification() がそもそもなくなっているためだ。
createNotification() はこれまで通り存在する。

Chrome のコンソール上で、

console.log(window.webkitNotifications);

と実行してみればこれは確かめられる。

createHTMLNotification() がなくなっているおかげで、これを利用しようとするサイトやブラウザプラグインが軒並み死んでいる。

それでも createHTMLNotification() を使いたい場合はどうすればよいのでしょうか。

解決方法

結論から言うとよくわかりません。
調べてみたが、まだこの関係の情報は少なかった。

ボタンを設置できてたりするリッチな通知を出す方法として、

chrome.notifications

を使うやり方も出てきたが、まるで動かなかった。
正直良く分かりません。

console.log(chrome.notifications); // undefined

とコンソールに吐き出してみても、undefined だ。

暫定的な解決方法

HTML ファイルの Notification を少し使っていたためこの変更に気付いたのだが、そもそもなぜ HTML ファイルの Notification を使っていたかというと、通知をクリックされた際に任意の動作をさせたかったからであった。
つまり、通知される HTML ファイル内の JavaScript 内でクリックイベントを検知していたということだ。

このように単純に一つのクリックイベントを検知したいだけであれば、以下の方法で対応することができる。

var n = window.webkitNotifications.createNotification(icon, title, message);
n.onclick = function(e) {
  console.log("clicked!"); // 任意のコード
};
n.show();

結論

通知に対して単純に一つのクリックイベントを設定するだけなら上記コードのようにするとよい。

これまでのような HTML ファイルを指定した Notification をする方法はよく分からなかった。
また、chrome.notifications を利用したリッチ通知の利用方法もまだ未確認。
こちらに関してご存知の方は教えてくれるととても喜びます。

pywebsocketで80番ポート以外を使おうとするとHeader/connection port mismatch: 80/xxxxというエラーが出る

ブログに書くようなネタがありそうでなくて悩んでいました

pywebsocket - WebSocket server and extension for Apache HTTP Server for testing - Google Project Hosting
pywebsocketを使うとPythonでWebSocketを使うことができます。
80番ポートがApacheとかが動いてる時に他のポートを使いたくて、使ってみた結果タイトルにようなエラーが発生してしまっていました。
parse_host_header()関数で帰ってくるポート番号が80番になっているのがこれがなんで80番が帰ってきてしまうのかよく分からない。(調査不足)、もしかしたらhost_headerにポート番号が設定できて、その設定ができていない?
とりあえずは
https://code.google.com/p/pywebsocket/source/browse/trunk/src/mod_pywebsocket/handshake/hybi00.py#108:handshake/hybi00.py#108らへんのコードを

   #if port != connection_port:
   #     raise HandshakeException('Header/connection port mismatch: %d/%d' %
                                 (port, connection_port))

こんな感じでコメントアウトすればちゃんと動いてくれます
ただ本質的な解決になってないのであれですが、、