bookmark_border「サーバーで実行」時にサーバー再起動ダイアログを再表示させる【eclipse】

eclipseでサーブレットを「サーバーで実行」するとき、サーバーを再起動するか尋ねるダイアログが出て欲しいのに出てきてくれないときの対処をメモ。

[Windows: ウインドウ/ Mac: Eclipse] → [設定] → [サーバー] → [起動] → [必要な場合はサーバーの再起動]

ダイアログが出てきてくれないときはここが「なし」になっている可能性。

bookmark_border【Mac】サーバーで必要なポート8080はすでに使用中 の一発解決コマンド

下のコマンドを実行する

8080ポートを使用しているPIDをそのままkillに渡しているだけです。

lsof -t -i:8080 | xargs kill -9 

これは何

lsof -i:8080 → 8080を使用しているプロセスが誰か調べる
-t → そのPIDだけもらう

| xargs kill -9 パイプで受け取ったPIDをkill (-9:強制終了)

bookmark_border【SharedPreference】get/putStringSet()の罠挙動【Kotlin】

困ったこと

putStringSet()直後にアプリを再起動すると、getStringSet()の結果が空要素になる。(ならないときもある)

結論

getStringSet()で受け取ったSet<String>に対して要素の操作をしない。

まずいコード

getStringSet()で受け取ったSet<String>に要素を追加して、それをそのままputStringSet()する。

val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf())
set.add("fugafuga")
sharedPreferences.edit().putStringSet("hogehoge", set).commit()
// setの要素数が0
val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf())

改善

getStringSet()で受け取ったSet<String>を直接触らず、toMutableSet()で別Setを作ってそれを操作&putStringSet()する。

val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf()).toMutableSet()
set.add("fugafuga")
sharedPreferences.edit().putStringSet("hogehoge", set).commit()

公式に書いてあった

getStringSet() で取得したSetの要素を操作しないようにとのこと。データの整合性(一貫性)はそもそも保証されない。

Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

SharedPreferences | Android Developer

bookmark_border【okhttp3+Kotlin】Rejecting re-init on previously-failed class java.lang.Class

久々にKotlinでokhttp3を使ったらいきなり落ちたのでメモ。

Rejecting re-init on previously-failed class java.lang.Class: java.lang.NoClassDefFoundError: Failed resolution of: Lorg/conscrypt/ConscryptHostnameVerifier;
val client = OkHttpClient() // ここで落ちる
val request = Request.Builder()
    .url("https://...")
    .build()
client.newCall(request).enqueue(object : okhttp3.Callback {
    @Throws(IOException::class)
    override fun onResponse(call: Call, response: Response) {
        // .....
    }
    override fun onFailure(call: Call, arg1: IOException) {
        // ....
    }
})

対処

okhttp自体のバージョンを下げることで一旦対応。

dependencies {
    // ....
    // implementation 'com.squareup.okhttp3:okhttp:4.4.0'
    implementation 'com.squareup.okhttp3:okhttp:4.0.0'
}

org.conscrypt:conscrypt-android をimplementationするようにという文献もありましたが、本来conscryptはokhttp3にとってoptionでありokhttp側のバグと考えられるので一旦この方法で様子を見ることに。

本家GitHubのIssue

OkHttp 4.3.1 spams log, didn’t find class “org.conscrypt.ConscryptHostnameVerifier” on path #5760

2020/05/28 更新:BugのLabelがついている。まだOpen。

2020/10/14 更新:4.8で解決しそうということでcloseされている。

bookmark_border【Tomcat, Windows】「サーバーで必要なポート8080はすでに使用中です」を一発解決するスクリプト【eclipse】

Macだとアクティビティモニタで簡単にプロセスをkillできますが(自分はそうしてた)、Windowsはnetstatしたりサービス一覧を調べたりする方法が出てきて、なんか大変そうだったので。

このスクリプトを実行する

PowerShellでこのスクリプトを実行します。

$processes = Get-Process -Id (Get-NetTCPConnection -LocalPort 8080,8005,8009).OwningProcess
foreach($process in $processes) {
  Stop-Process $process.Id
}

実行方法

  1. Windows PowerShellを立ち上げる
    スタートメニュー(Cortana)の検索で powershell とか打つと候補に出てくる
  2. 上記のスクリプトをコピペしてEnterで実行

エラーっぽい赤い文字が出るかもしれませんが、8005やか8009番ポートを使用しているアプリケーションが存在していないだけで特に問題ありません。

これは何

8080, 8005, 8009番ポートを使用しているプロセスを一括で終了しています。

「ローカルホストのtomcat vXX サーバーで必要なポート8080はすでに使用中です」 is 何

8080番ポートを使うサーバーを起動したいけど、8080番ポートを使用している先客(プロセス)がすでに実行中なので、起動できない。
(すでに実行中のそれは、大抵はeclipseで自分が起動したプロセスの残骸だったりする)

ポート番号の変更は根本解決ではない

eclipseでtomcatのポート番号を変更する方法もありますが、このエラーは前述の通りプロセスの残骸が原因なので、そのプロセスを終了させるほうが正攻法と思われます。(最終手段のPC再起動も事実上はその残骸プロセスを終了させる方法)

bookmark_border【Kotlin】Camera2 プレビューのSurfaceViewタップでフォーカスを固定する

プレビューしているSurfaceViewがタップされた座標に応じてフォーカス固定したかったけども、まとまった情報がなかったので個人的なメモ。
例外のハンドリングやCameraCaptureSessionのnullチェックなどは割愛しているので注意。

// Activityの中
private var cameraDevice: CameraDevice? = null
private var cameraSession: CameraCaptureSession? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
// Activityの中
private var cameraDevice: CameraDevice? = null
private var cameraSession: CameraCaptureSession? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
// 前提:
// ActivityCompat.requestPermissionsで、カメラ使用の許可をユーザから得ている
// カメラがすでにオープンしている。
//   onResume()とかで、上記のpreviewRequestBuilder, cameraDevice, cameraSessionにすでに参照を入れている。
//   CameraCaptureSession.StateCallback#onConfiguredまで行っていて、プレビューが始まっている。
// surfaceViewはプレビュー用のView
surfaceView.setOnTouchListener { _: View, motionEvent: MotionEvent ->
    if (motionEvent.action == MotionEvent.ACTION_DOWN) {
        // タップされた場所から20pxの範囲に対してフォーカスさせる
        // 画面のorientationに応じて別途座標の反転などが必要
        val rectangle = MeteringRectangle(motionEvent.x.toInt(), motionEvent.y.toInt(),
            20, 20, MeteringRectangle.METERING_WEIGHT_MAX)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(rectangle))
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
        // 露出の調整やフラッシュが必要なときは適宜previewRequestBuilder#setする
        // builder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(rectangle))
        // builder.set(CaptureRequest.CONTROL_AE_XXXXXXX, XXXXXXX)
        cameraSession!!.setRepeatingRequest(previewRequestBuilder.build(), null, null)
        // CallbackやHandlerを指定する場合
        /*
        cameraSession!!.setRepeatingRequest(previewRequestBuilder.build(), object :     CameraCaptureSession.CaptureCallback() {
            // コールバックが必要ならここに処理を記述
        }, handler)
        */
    }
    true
}

bookmark_border【Kotlin】GnssStatusの取得【Android】

API Level 24 (Android 7.0)で追加された、GnssStatusの取得方法をメモ。
manifest

android.permission.ACCESS_FINE_LOCATION

MainActivity

override fun onResume() {
    super.onResume()
        // LocationManagerの取得
        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        // ユーザが位置情報のアクセスを許可しているか
        if (ContextCompat.checkSelfPermission(this@MainActivity,
                      android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
           //GnssStatusCallbackの登録
           locationManager.registerGnssStatusCallback(gnssCallback)
        } else {
            // 位置情報の取得が許可されていない
            // TODO: requestPermissionsでユーザの許可をもらう
        }
}
private val gnssCallback: GnssStatus.Callback = object:GnssStatus.Callback(){
    override fun onSatelliteStatusChanged(status: GnssStatus?) {
        status?.let {
            val satelliteCount = it.satelliteCount
            // .....
        }
    }
}
override fun onPause() {
    super.onPause()
    gnssCallback.let {
        locationManager.unregisterGnssStatusCallback(it)
    }
}

bookmark_borderTomcat+Log4j2でコンソール出力 & 任意のファイルに書き込み

Log4j2 + サーブレット(Tomcat)でとりあえず任意のファイルとコンソールへログ出力する上で最低限すべきことをメモ。ログレベルの制御といったことまでは書いていません。

1. Log4j2のダウンロード

Apache ソフトウェア財団から最新版をダウンロード

2. Log4j2のjarをビルドパスに追加

ダウンロードしたzipにいっぱいjarが入っています。そのうち以下のファイルをWEB-INF/libにコピー。
大抵はコピーした段階でeclipseが自動でビルドパスに追加してくれるはずです。

  • log4j-XXX-api-XXX.jar
  • log4j-api-XXX.jar
  • log4j-core-XXX.jar
  • log4j-web-XXX.jar

※XXXの部分にはバージョン番号が入ります。

3. log4j2.xmlの編集

WEB-INF直下に「log4j2.xml」を以下の内容で記述しつつ、ハイライトされている部分を適宜書き換えます。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF">
  <Appenders>
        <RollingFile name="file"
                fileName="C:\XXX\YYY\hogeapp.log" <!-- 書き込んでほしいログファイルの場所 -->
                filePattern="C:\XXX\YYY\hogeapp-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="[%date] %-5p %location %m%n"/>
            <Policies>
                <OnStartupTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="256 KB" />
            </Policies>
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="自分のアプリケーションのパッケージ名" level="trace">
            <AppenderRef ref="file" />
        </Logger>
    </Loggers>
</Configuration>

4. ログ出力のコードを書く

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@WebServlet("/Main")
public class Main extends HttpServlet {
    private static Logger logger = LogManager.getLogger();
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        logger.warn("まずい");
        logger.error("かなりまずい");
    }
}