Let's EncryptでSSLHandshakeException

環境

Pixel 4a(Android 11)

エラー

HTTP FAILED: javax.net.ssl.SSLHandshakeException: 
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

状況

  • https通信時に上記エラー。
  • 通信はOkHttpを使用。
  • WebViewからの通信も同様のエラー。
  • 端末のChromeアプリから該当サーバーへの疎通は問題なし。

解決

Chain of Trust - Let's Encrypt から必要な証明書をダウンロードして raw ディレクトリに保存し、OkHttpやWebViewに追加する。

OkHttp

build.gradleに以下を追加

implementation "com.squareup.okhttp3:okhttp-tls:$okhttp_version"

okhttp/okhttp-tls at master · square/okhttp · GitHub

保存した証明書を追加する。

private fun buildOkHttp(): OkHttpClient {
    val x1Certificates = context.resources.openRawResource(R.raw.isrg_root_x1).reader()
        .use(InputStreamReader::readText).decodeCertificatePem()
    val x2Certificates = context.resources.openRawResource(R.raw.isrg_root_x2).reader()
        .use(InputStreamReader::readText).decodeCertificatePem()
    val r3Certificates = context.resources.openRawResource(R.raw.lets_encrypt_r3).reader()
        .use(InputStreamReader::readText).decodeCertificatePem()

    val certificates =
        HandshakeCertificates.Builder()
            .addTrustedCertificate(x1Certificates)
            .addTrustedCertificate(x2Certificates)
            .addTrustedCertificate(r3Certificates)
            .build()
    return OkHttpClient
        .Builder()
        .sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager)
        .build()
}

okhttp-tlsのサンプルはこちら
okhttp/CustomTrust.kt at master · square/okhttp · GitHub

WebView

xml/network_security_config.xml として以下のファイルを作成。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" >
        <trust-anchors>
            <certificates src="@raw/isrg_root_x1" />
            <certificates src="@raw/isrg_root_x2" />
            <certificates src="@raw/lets_encrypt_r3" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

AndroidManifest.xmlに以下の記述を追加

<manifest...>
  <application
      ...
      android:networkSecurityConfig="@xml/network_security_config"
      ...
  </application>
</manifest>

参考

stackoverflow.com

developer.android.com

  
 
 
 
 
 
 
 
 
 

プロローグにはコンテンツを指定できません。

環境

Android Studio 4.1.2
Android Gradle Plugin 4.1.2

エラーメッセージ

FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:extractDeepLinksDevDebug'.
> org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; プロローグにはコンテンツを指定できません。

状況

AndroidStudioでビルドすると上記エラーとなった。./gradlew app:assembleDebug でも同様。
直前の成功ビルドからの差分はなく、AndroidStudio上でbuild variantを切り替えたタイミングで突然発症し、initial commit 付近まで戻しても同様のエラーが出るようになった。
以下試すも効果なし。

  • .idea/ 削除
  • app/build/ 削除
  • ~/.gradle/caches 削除
  • Invalidate Caches / Restart

解決

リソースのnavigationディレクトリを作り直したら何故か症状がでなくなった。

main/res/navigation/ ディレクトリを削除して、resディレクトリの右クリックから [New] -> [Android Resource File] でナビゲーションのxmlを作り直して復旧。 git上の差分は特になし。

   

   

   

   

   

   

ConstraintLayoutで垂直方向で中央に集める

環境

Android Studio 4.1.2

やりたいこと

ConstraintLayoutでViewを上下真ん中に集めたい。
これを
f:id:mst335:20210223105447p:plain

こうしたい
f:id:mst335:20210223105558p:plain

やったこと

いじる前のレイアウトはこちら。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>

GUIでやる場合

  1. 全部選ぶ
    f:id:mst335:20210223110222p:plain
  2. 右クリックメニューから [Chains] -> [Create Vertical Chain]
    f:id:mst335:20210223110457p:plain
    するとこんな感じになる
    f:id:mst335:20210223111433p:plain
  3. 右クリックメニューから[Chains] -> [Vertical Chain Style] -> [packed]
    f:id:mst335:20210223111118p:plain
    すると真ん中に寄る
    f:id:mst335:20210223105558p:plain

手動でやる場合

  1. チェーンな関係にする
  2. layout_constraintVertical_chainStylepacked を指定する

チェーンとは、『双方向の位置制約を設定して、相互にリンクさせたビューのグループ』のこと。(参照:ConstraintLayout でレスポンシブ UI を作成する)
button1の layout_constraintBottom_toTopOf にbutton2を、button2の layout_constraintTop_toBottomOf にbutton1を指定した関係がチェーンとなる。
今回の場合Viewが3つあるので、button <-> button1と、button1 <-> button2 をそれぞれチェーンにする必要がある。

チェーンに対してスタイルを指定することができる。ここに packed を指定することで期待する中央寄せの状態になる。

app:layout_constraintVertical_chainStyle="packed"

結果

垂直方向で中央に集まった状態のレイアウトはこちら。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toTopOf="@+id/button2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toTopOf="@+id/button3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>

参考

developer.android.com

   

   

   

   

   

   

Android Layout Cookbook アプリの価値を高める開発テクニック

Android Layout Cookbook アプリの価値を高める開発テクニック

AndroidStudioでrubyのシンタックスハイライト

環境

Android Studio 4.0

やりたいこと

AndroidStudioでRubyソースコードを見やすくしたい。

やったこと

  1. TextMate Bundles プラグインをインストール
  2. https://github.com/textmate/ruby.tmbundle/ を任意の場所にclone
  3. Preferences -> Editor -> TextMate の + ボタンから、cloneした ruby.tmbundle ディレクトリを選択

結果

参考

https://youtrack.jetbrains.com/issue/OC-13269

   

   

   

   

   

   

XcodeでCreated byの名前を変えるためにやったこと

環境

Xcode 11.6 (11E708)
macOS 10.15.4(19E287)

やりたいこと

Xcodeでファイルを新規作成したときに自動で挿入されるコメント内のユーザー名を変更したい。

//
//  ContentView.swift
//  Sample
//  
//  Created by (ここに表示される名前) on 2020/08/15
//  Copyright © 2020 ergo. All rights reserved.
//

やったこと

~/Library/Developer/Xcode/UserData/ に 以下の内容で IDETemplateMacros.plist というファイルを作成する。
(ここに表示される名前) の部分に任意の名前を入れればOK。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>FILEHEADER</key>
    <string>
//  ___FILENAME___
//  ___TARGETNAME___
//  
//  Created by (ここに表示される名前) on ___DATE___
//  ___COPYRIGHT___
//</string>
</dict>
</plist>

参考

medium.com

   

   

   

   

   

   

Kotlin + dokka でマルチモジュールのJavadocを生成

環境

Android Studio 3.5
macOS 10.14.4
kotlin 1.3.50

対象のモジュール構成

以下のような構成のプロジェクトを想定し、lib1とlib2のJavadocをまとめて生成する。

モジュール 説明
app lib1とlib2に依存したアプリモジュール
lib1 Javadoc生成対象のライブラリモジュール
lib2 Javadoc生成対象のライブラリモジュール

dokka導入

GitHub - Kotlin/dokka: Documentation Engine for Kotlin を参考に、プロジェクトのルートのbuild.gradleに以下を記述する。

buildscript {
    ext.dokka_version = '0.9.18'
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:${dokka_version}"

    }
}

ドキュメント用のモジュールを追加

Javadoc生成対象のソースコードをまとめ上げるモジュールを作成して、sourceSetsにドキュメント生成対象のコードを追加する。
main.java.srdDir にソースセットを追加すると、appモジュールからライブラリのコードが正しく解決できなくなってしまったので、ドキュメント用のBuildTypeを定義してそこに追加するのがよさそう(以下の例では docs というBuildTypeを定義している)。

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.dokka-android'

android {
    compileSdkVersion 28

    defaultConfig {
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
    }

    buildTypes {
        // ドキュメント用のビルドタイプを定義する。これをしないとappモジュールからライブラリのコードをうまく解決できない。
        docs {}
    }

    sourceSets {
        // ドキュメント生成対象のコードをソースセットに追加する
        docs.java.srcDirs += '../lib1/src/main/java'
        docs.java.srcDirs += '../lib2/src/main/java'
    }

}

dokka {
    outputFormat = 'javadoc'
    sourceDirs = files('src/main')
    outputDirectory = "$project.rootDir/docs"
    linkMapping {
        // Unix based directory relative path to the root of the project (where you execute gradle respectively).
        dir = "src/main/kotlin" // or simply "./"

        // URL showing where the source code can be accessed through the web browser
        url = "https://github.com/cy6erGn0m/vertx3-lang-kotlin/blob/master/src/main/kotlin"
        //remove src/main/kotlin if you use "./" above

        // Suffix which is used to append the line number to the URL. Use #L for GitHub
        suffix = "#L"
    }
}

Javadoc生成

./gradlew dokka

サンプルコード

https://github.com/ergooo/DokkaMultiModuleSample

Javadoc生成結果

https://ergooo.github.io/DokkaMultiModuleSample/

参考

github.com

 
 
 
 

Kotlinイン・アクション

Kotlinイン・アクション

AWSLambdaClient#invokeでTooManyRequestsException: Rate Exceeded.

環境

やろうとしたこと

以下のようなコードで長めのAWS LambdaをJavaから実行しようとした。

val invokeRequest = InvokeRequest()
        .withFunctionName(FUNCTION_NAME)
        .withPayload("適当なpayload")
        .withInvocationType(InvocationType.RequestResponse)
val lambdaClient = AWSLambdaClientBuilder.standard()
        .withRegion(REGION)
        .withCredentials(AWSStaticCredentialsProvider(credentials)).build()
val invokeResult = lambdaClient.invoke(invokeRequest) // ここでTooManyRequestsException

エラー

Exception in thread "main" com.amazonaws.services.lambda.model.TooManyRequestsException: Rate Exceeded. (Service: AWSLambda; Status Code: 429; Error Code: TooManyRequestsException; Request ID: XXXXXXXXXXXX)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1701)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1356)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1102)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:759)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:733)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:715)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:675)
    at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:657)
    at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:521)
    at com.amazonaws.services.lambda.AWSLambdaClient.doInvoke(AWSLambdaClient.java:3137)
    at com.amazonaws.services.lambda.AWSLambdaClient.invoke(AWSLambdaClient.java:3104)
    at com.amazonaws.services.lambda.AWSLambdaClient.invoke(AWSLambdaClient.java:3093)
    at com.amazonaws.services.lambda.AWSLambdaClient.executeInvoke(AWSLambdaClient.java:1744)
    at com.amazonaws.services.lambda.AWSLambdaClient.invoke(AWSLambdaClient.java:1716)

解決

AWSLambdaClientBuilder#withClientConfiguration()でタイムアウトを設定

val invokeRequest = InvokeRequest()
        .withFunctionName(FUNCTION_NAME)
        .withPayload("適当なpayload")
        .withInvocationType(InvocationType.RequestResponse)
val lambdaClient = AWSLambdaClientBuilder.standard()
        .withRegion(REGION)
         // ↓これを追加
        .withClientConfiguration(ClientConfigurationFactory().config.withSocketTimeout(TIMEOUT))
        .withCredentials(AWSStaticCredentialsProvider(credentials)).build()
val invokeResult = lambdaClient.invoke(invokeRequest)

InvokeRequest#withSdkClientExecutionTimeout() とかInvokeRequest#withSdkRequestTimeout()とかそれっぽいのもあったけどそれらは反映されなかった。。