社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

CalledFromWrongThreadException (メインスレッド以外から、UIの更新)

問題
Android のプログラミング中に以下の例外が発生。

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

どうも、UIを実装しているスレッド以外のスレッドから、UIに更新をしようとしている時に発生しているよう。

原因
Android の制約らしい。

回避策
Handler のインスタンス経由で、別スレッドからメインスレッドに更新を依頼すれば良いらしい。
メインスレッド内で、Handler のインスタンスを生成。

val handler = Handler()

他のスレッド内で、handler.post に処理を渡してあげる。

thread {
  handler.post( Runnable() {
    val resultView: TextView = findViewById(R.id.result)
    resultView.text = "test"
  })
}

この場合、メインスレッドが画面の更新を担うことになるので、Android の制約に反せず、例外は発生しない。

Android textView を動的に追加 (kotlin)

表記のとおり、textView を動的に追加するコードを試してみました。
静的に追加するなら、xml に記述すればOKです。
動的に追加する場合、textView のインスタンスを生成し、それをレイアウトに追加します。
例として、2つの TextView を作成し、レイアウトに追加してみました。

// TextView の配列(サイズ2)を作っています
val texts: Array<TextView?> = arrayOfNulls(2)

// FrameLayout のインスタンスを取得しています
// textsWindow は予めレイアウトファイル(.xml)に定義したものです
val layout: FrameLayout = findViewById(R.id.textsWindow)

// それぞれのTextView に設定を行い、FrameLayout のインスタンスに追加しています
// 今回はそれぞれのテキストが重なってもOKなように、FrameLayout を使っています
// 特に制約がなければ、LinearLayout などでも構いません
for (i in 0..1){
  texts[i] = TextView(this)
  texts[i]?.text = "test text"
  texts[i]?.setTextColor(Color.WHITE)
  texts[i]?.x = 400.0f
  texts[i]?.y = 400.0f * (i + 1)
  layout.addView( texts[i], LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 50))
}

変数名や値は適当です。
画面は掲載していませんが、定義したテキスト("test text")が表示されることが確認できました。

感想
TextView の配置位置が予め決まっていて、表示・非表示の切り替えのみしたい
> visibility だけで対応できそう。
ユーザーの操作に合わせて、TextView を色んな位置に表示させたい
> 今回のように動的に追加すれば対応できそう。

WireMock で IllegalArgumentException(request + " does not have a getUri or getHttpURI method")

WireMock は簡単にREST API のモックが作れるのでとても便利です。
その WireMock を実装中に、表題のエラーに出くわしました。

IllegalArgumentException(request + " does not have a getUri or getHttpURI method")

WireMock のソースファイルを見てみると、どうも JettyUtils 内でスローされている様子。
https://github.com/tomakehurst/wiremock/blob/master/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyUtils.java

getUri あるいは getHttpURI のメソッドを探したが見つからなくて、例外が発生しているように見えます。
Exception で catch していますが、恐らくは NoSuchMethodException 辺りがスローされているのではと思います。

それらのメソッドは、org.eclipse.jetty.server.Request から探そうとしているようなのですが。
Request って名前のクラスって色んなところで使われていると思うんですよね。
実際、自分が扱っていたモジュール内を探してみても、4つか5つくらいの Request クラスが見つかりました。

これは、Request クラスがコンフリクトしているような気がしたので、依存ライブラリのロード順を変えてみたり、exclusion してみたところ、エラーを回避することができました。

1モジュールが大きくなってくると、依存ライブラリの数も多くなってきて、整合性が取りづらくなってきます。
モジュールの役割を明確にし、あまり大きくなり過ぎないように気をつけたいです。
あと、クラス名付ける時は分かり易いのがもちろんいいですが、なるべくユニークな名前にしておいた方が、コンフリクトに悩む機会は減るかもしれません。
とは言え、いつもコンフリクトに悩まされるのは、ほとんど内製ではない依存ライブラリからなんですけどね。。

android の getAssets() を MainActivity class 以外から使いたい

AppCompatActivity() を継承
MainActivity とは違う新しいクラスを生成し、AppCompatActivity() を継承し、getAssets() を呼び出してみた。

getAssets().open("sample.txt")

コンパイルエラーは起きないものの、以下のエラーが発生。

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference

一応、activity クラスを作ったつもりだけど、恐らく正しく作る(使う)ことが出来ていない。
調べてみたところ、MainActivity の context のインスタンスを渡してあげる方が良さそう。

MainActivity の context のインスタンスを渡す

class NonActivityClass(context: Context) {
  val myContext = context
}

# from MainActivity
NonActivityClass(this)

ちなみに、applicationContext という変数があるが、それを(this の代わりに)渡してあげても、エラーは解消されなかった。

Context について

  • Context とは、アプリケーションのグローバルな環境情報を受け渡すために使用されるもの。
  • Context には、Application Context と Activity Context の2種類が存在する。
  • Application Context は Activity クラス内で getApplicationContext() で取得できる。
  • Activity Context は Activity 自身のこと。

即ち少なくとも自分のコードでは、Application Context ではなくて、Activity Context が必要とされていることになると思いますが、両者の違いを理解しないといけない。

Java と kotlin で android の MainActivity

以下、どちらも Android Studio で自動的に生成されたクラスになります。
Java

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

kotlin

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }
}

継承
kotlin では : (コロン)で継承しているみたいです。何となく変数の型を指定しているように見えますが、class に対してなので。

?
Bundle? のように、型宣言に ? を付けた場合は nullable(null の代入可能) という意味になるようです。
kotlin は明示的に ? を付けないと、null を代入できないようです。

Java challenge Optional

なにが出力されるのか?

public static void main(String[] args){
	List<String> list = Arrays.asList("X", "Zero", "Sigma", "Willy");
	
	Optional<String> op = list.stream().findFirst();
	Optional<String> op2 = list.stream().filter(e -> e.equals("Signa")).findAny();
	
	System.out.println(op.orElse(""));
	System.out.println(op2.orElse("Double"));
}

考察
一つ目の Optional op は、最初の要素だけ取得するから、"X" が入っているはず。
二つ目の Optional op2 は、"Signa" を探しているけど、該当する要素はないので、何も入っていないはず。

なので、出力は
X
Double(null に対して orElse の中身が出力される)
となるはず。

結果
X
Double

各メソッドは基本的なもので、戻り値(動作)が分かっていれば特に問題はなさそうです。

広告を非表示にする

Java challenge methodReference

なにが出力される?

public static void main(String[] args) {
	List<String> castlevania = new ArrayList<>();
	castlevania.add("Dracula");
	castlevania.add("Alucard");
	castlevania.add("Trevor");
		
	List<String> castlevaniaResult = new ArrayList<>();
		
	Consumer<String> c1 = System.out::println;
	Consumer<String> c2 = castlevaniaResult::add;
	Consumer<String> c3 = c -> castlevaniaResult.removeIf(e -> e.matches("Trevor"));
		
	castlevania.forEach(c2.andThen(c1).andThen(c3));
		
	System.out.println(castlevaniaResult);
}

考察
Consumer が初見だったので調べてみたところ、”引数を消費するインスタンスを定義するためのインターフェイス”とのこと。
castlevania.forEach で最初に c2 に引数が渡されていることになるので、
まず、castlevaniaResult に castlevania の全ての要素が格納されることになるはず。
次に, c1 に引数が渡されるので、全ての要素がここでまず出力されるはず。
Dracula
Alucard
Trevor
そして、c3 に引数が渡されるので、castlevaniaResult から Trevor が除外されるはず。
最後に castlevaniaResult を出力しているので、以下の2つの要素が出力されるはず。
Dracula
Alucard

結果
Dracula
Alucard
Trevor
[Dracula, Alucard]

最後は、List が System.out.println の引数に渡されていたので、リスト形式で出力されるのを失念してしまいました。
Consumer とか、Java8 から登場した関数型のインタフェースみたいですが、Java8 の基礎みたいなので、別で調査してみたいです。