20200206のAndroidに関する記事は8件です。

Okhttp3でpfx証明書を使う

追記履歴

2020/02/08 参考リンク追加
2020/02/10 非同期で使うことになった理由を追記

はじめに

クライアント証明書を使ってHTTPS通信対応をすることになった時に書いたコードを会社を辞めてソースコードが見れなくなった時用にメモ代わりとして残しておく
何故Okhttpにこだわるかというと、大昔に苦しめられたもんだから大嫌いになったんですよねHttp(s)URLConnectionが
一部書き方が雑な部分や現役バリバリな人には釈迦に説法のような部分もあるかもですがご容赦をいt・・・ボク用のメモだから別にいっか!

事前準備

1.main配下(main、resと同じ階層)に「assets」フォルダを作成
2.assetsフォルダ内にpfx証明書を格納(今回は便宜上「hogehage.pfx」とするヨ)
3.「AndroidManifest.xml」にインターネット通信のパーミッションを追加(↓こんな感じ)
追加場所はmanifest間なら問題ないが、activityの間には書くな

<manifest ・・・
...
//インターネット接続用のパーミッション
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

4.「build.gradle(app配下の方)」の「dependencies」部分に以下を追加
バージョンは2020/02/05のもの
最新かどうかは https://square.github.io/okhttp/ の「Releases」部分を確認して

build.gradle
・・・
dependencies {
・・・
implementation("com.squareup.okhttp3:okhttp:4.3.1")

1と2はHTTPS通信用、3と4はOkhttp用の記述
もうここまでで書くの面倒くさくなってきた・・・FE風花雪〇やりたい

コード

メインスレッドではなくAsynctask・・・俗にいう非同期処理で書いてる
前まではメインスレッド(MainActivityとかとか)で書いてたが・・・あれ?なんで変えたんだっけ?
→元々はAsynctaskでHttpurlconnectionを使っていたため。試しに非同期でokhttpを使ってみたらできたし、アプリの動作が見せかけだが軽くなったのでそのまま採用した(追記)

AsyncHttps.java
package com.example.test;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

//非同期処理ってやつ
//HTTPS通信を行うためのとこ
//呼び出し元では画像をバイト配列に変換して、このactに渡してた
public class AsyncHttps extends AsyncTask<byte[], Void, String>
{

    private Context mContext;
    public AsyncHttps(Context context)
    {mContext = context;}

    public static String res=null;

    final Handler handler = new Handler();
    //URLsListの配列番号用
    public static int url_id = 0;

    //通信結果格納変数
    public static String result = null;

    // 非同期処理
    @Override
    protected String doInBackground(byte[]... params)
    {

        //url一覧を別のactに配列で書き、送信元で配列番号を送ってやればいいことに気が付いた
        String urlSt = URLsList.URL[url_id];

        byte[] word = params[0];

    //
        return result;
    }


    //http通信用
    public void http_post(byte[] bytes)
    {
        //JSONを使用して送信するための設定らしい
    //MediaTypeは送る物に合わせて変更が必要(画像なら"image/jpg"みたいな感じ)
        MediaType mediaType= MediaType.parse("application/json; charset=utf-8");
        //Request Body作成。送信する物
        RequestBody requestBody = RequestBody.create(mediaType,bytes);
        //リクエストを作成、郵便物の梱包みたいなもんでしょ(テキトー)
        Request request = new Request.Builder()
                //送り先
                .url(URLsList.URL[url_id])
                .post(requestBody)      //送る中身
                .build();               //これらをビルドアップする


        KeyManagerFactory keyManagerFactory;
        //証明書作成したときに設定したパスワード
        final char[] PASSWORD = "***ここにパスワード***".toCharArray();
        InputStream inputStream;
        TrustManagerFactory trustManagerFactory;
        SSLSocketFactory sslSocketFactory;
        X509TrustManager trustManager;
        try
        {
            //クライアント証明書ファイルの指定(assetsフォルダに配置しとけ)
            inputStream = mContext.getResources().getAssets().open("hogehage.pfx");
            //参考にしたとこの拡張子はp12だったからそっちも行けんじゃない?
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            //ここから下は何も考えずコピペでOK
            keyStore.load(inputStream,PASSWORD);
            trustManagerFactory = TrustManagerFactory.getInstance
                    (TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager))
            {
                throw new IllegalStateException("Unexpected default trust managers:"
                        + Arrays.toString(trustManagers));
            }
            trustManager = (X509TrustManager)trustManagers[0];

            keyManagerFactory = KeyManagerFactory.getInstance("X509");
            keyManagerFactory.init(keyStore,PASSWORD);
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
            sslSocketFactory = sslContext.getSocketFactory();
            //コピペここまで

            final OkHttpClient client = new OkHttpClient.Builder()
                    //.connectTimeout(10, TimeUnit.SECONDS) //タイムアウト設定3銃士
                    //.readTimeout(10, TimeUnit.SECONDS)    //何を設定しているかは知らん
                    //.writeTimeout(10, TimeUnit.SECONDS)   //あとで見といて
                    .sslSocketFactory(sslSocketFactory,trustManager)
                    .build();

            client.newCall(request).enqueue(new Callback()
            {
                @Override
                public void onFailure(@NonNull Call call, IOException e)
                {
                    //例外発生時の処理は別処理にすると安定するらしい。
                    failMessage();
                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException
                {
                    result = String.valueOf(response);
                    //何か返ってくる場合はここで何かしたりしなかったり
                    client.connectionPool().evictAll();
                }
            });
        }catch (IOException e)//こっから下は何か爆発した時Errorタブのログに出る 特に触るとこはない
        {Log.e("エラー内容:", String.valueOf(e));}
        catch (NoSuchAlgorithmException e)
        {Log.e("エラー内容:", String.valueOf(e));}
        catch (CertificateException e)
        {Log.e("エラー内容:", String.valueOf(e));}
        catch (UnrecoverableKeyException e) 
        {Log.e("エラー内容:", String.valueOf(e));}
        catch (KeyStoreException e) 
        {Log.e("エラー内容:", String.valueOf(e));}
        catch (KeyManagementException e) 
        {Log.e("エラー内容:", String.valueOf(e));}
    }

    //例外発生時(何かしらエラった時)の処理
    private void failMessage()
    {Log.d("通信結果:","ダメでした…");}

}

参考

Okhttp公式:
https://square.github.io/okhttp/
Android アプリ内でのクライアント証明書認証:
https://qiita.com/c_ume/items/d082ffd20b3316aab805
Now that SSLSocketFactory is deprecated on Android, what would be the best way to handle Client Certificate Authentication?
https://stackoverflow.com/questions/31002159/now-that-sslsocketfactory-is-deprecated-on-android-what-would-be-the-best-way-t

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android でもとりあえず Ubuntu のデスクトップ環境を使いたい(Termux 版)

はじめに

先に公開した Android Studio を使うための最低限のデスクトップ環境の構築方法を記載します。
日本語入力もありませんが、その代わり最短で構築できると思います。

注意点

デスクトップ環境(LXDEやXfce)のインストールは数時間かかります。
時間を確保してから実施するほうが良いと思います。

インストール

Play ストア で Termux と XSDL をインストールしてください。
Termux は Linux という OS の一種が動作する環境になります。
Android も OS ですので、OS の上で OS が動作するという、ちょっとややこしい環境になります。
XSDL は画面を担当するアプリケーションです。
Termux はコマンドラインまでしかサポートしませんので、XSDL で画面をサポートしてもらいます。

Termux の設定

Termux を起動して、以下のコマンドでパッケージ(Linux アプリケーションの倉庫のようなもの)を更新してください。

# パッケージのアップデート
pkg upgrade

pkg は apt コマンドのラッパーとのことです。
apt コマンドはパッケージを操作するためのプログラムを指します。
Termux では基本的に pkg を使用してアプリケーションのインストールを行います。

続いて、termux-setup-storage を実行して Termux と Android の間でファイルのやりとりをできるようにします。

# Android で扱っているファイルを Termux からも扱えるようにする
termux-setup-storage

Termux がアクセスして良いかと聞くダイアログが出ますので、OKを選択して下さい。

Termux と Android のディレクトリの対応を記載します。
ファイルの受け渡しなどの参考にしてください。

Termux Android
/data/data/com.termux/files/home/storage/shared/ (本体 側)/sdcard/
/data/data/com.termux/files/home/storage/downloads/ (本体 側)/sdcard/Download/
/data/data/com.termux/files/home/storage/external-1/ (SD Card 側)/storage/(SD Cardの名前)/Android/data/com.termux/files/
/storage/(SD Cardの名前)/ (SD Card 側)/storage/(SD Cardの名前)/

Ubuntu のインストール

続いて Termux に Ubuntu の環境を構築します。
Ubuntu も OS の一種になります。
Termux という Linux の上に Ubuntu という Linux を構築するという、Linux の上に Linux を載せるという形になります。
公式サイトのやり方ではありませんが Ubuntu をインストールする方法が載っています。
今回はこちらの方法を使用させてもらいます。
以下のサイトで、Installation steps と書かれている箇所のコマンドを実行して下さい。

https://github.com/MFDGaming/ubuntu-in-termux

Ubuntu と Android のディレクトリの対応を記載します。
ファイルの受け渡しなどの参考にしてください。

Ubuntu Android
/sdcard/ (本体 側)/sdcard/
/storage/(SD Cardの名前)/ (SD Card 側)/storage/(SD Cardの名前)/

以下の公式サイトに Ubuntu のインストール方法が書いてあるのですが、SD Card などの Android とのファイル共有の方法が分からなかったため、今回は使用しません。

https://wiki.termux.com/wiki/Ubuntu

Ubuntu の起動

先述のサイトの Installation steps の 10.Now just start ubuntu: に記載がありますが、Termux から Ubuntu を起動するコマンドを記載します。

cd
cd ubuntu-in-termu
./startubuntu.sh

Ubuntu デスクトップ環境のインストール

ここからは Ubuntu での操作になります。

まずは apt でパッケージを更新します。

apt update
apt upgrade -y

次にデスクトップ環境をインストールします。
調べた限りでは、LXDE, Xfce の2種類が使えます。
LXDE の方が軽いそうですが、Xfce の方が見た目が良くLXDE程ではないですけれども十分な軽さを持っているそうです。
ここは好みで選択すれば良いと思います。

なお、ここでネットワークやスマートフォンの速度にもよると思いますが数時間掛かります。
後述の設定は、最初や最後ではなく(やや後ろの方ではありますが)途中で出てきます。
お勧めはインストールを開始して数時間放置し、設定を行ってからまた数時間放置するやり方です。

・ LXDE のインストール

apt install -y lxde
# 途中で地域の質問があるので 6 → Enter → 79 → Enterという操作で選択する。数字の意味は、6:Asis、79:Tokyo となる。
# 次にキーボードの質問があるが、 31 → Enter → 1 → Enterという操作で選択する。数字の意味は、31:America(US)、1:Engrish(US) となる。ここは好みだが ソフトウェアキーボードの人は、 Hacker's Keyboard を使うので、この選択が間違いがないと思われる。
# デスクトップマネージャである gdm3 と lightgdm を選択するように言われるが、特にこだわりがないなら軽い lightgdm を選択すれば良いと思われる。

・ XFCE のインストール

apt install -y xfce4

環境の設定

XSDL を立ち上げてしばらく待って下さい。
最後なにやら文字列を表示している画面が出るので以下をメモしてください。

上から2行目に以下の文字列が出るはずです。DISPLAY のポート番号をメモしてください。

export DISPLAY=(何かのIPアドレス):(ポート番号)

上から3行目に以下の文字列が出るはずです。同様に PULSE_SERVER のポート番号をメモしてください。

export PULSE_SERVER=(何かのIPアドレス):(ポート番号)

IPアドレスはメモする必要はないです。
再び Termux の Ubuntu に戻って下さい。
XSDL に接続するために、設定ファイルに先程メモしたポート番号とローカルのIPアドレスを記載します。

# テキストエディタ vim をインストールしていない人はインストール
apt install -y vim

# .bashrc にXDSLの接続設定を記載する
cd
vim .bashrc

.bashrc の一番下辺りにでも、以下を記載して下さい。

export DISPLAY=:(さっきメモした DISPLAY のポート番号)
export PULSE_SERVER=127.0.0.1:(さっきメモした PULSE_SERVER のポート番号)

vim の操作方法を知らない人は以下だけ覚えればなんとかなります。

キーボードのキー 役割
i コマンドモードから入力モードに入る
ESC 入力モードを抜けてコマンドモードに戻る
:wq 保存して終了する。コマンドモードで操作すること。

今回に限り、先程記載した設定を手動で実行します。
次回からは自動的に実行するにで以下の操作は不要になります。

source .bashrc

デスクトップ環境の起動

まず、XSDLを立ち上げてください。先程のポート番号等が出る画面まで待ちます。
次に、Termux に戻り、Ubuntu の上で以下のコマンドを実行します。

・ LXDE の場合

startlxde

・ Xfce の場合

startxfce4

XSDL に戻り、しばらく待つと Ubuntu のデスクトップ環境が立ち上がるはずです。

この辺りはボタン一発でデスクトップ環境が立ち上がる UserLAnd の方が楽ですね。

参考サイト

Android で動く Linux 環境 UserLAnd が XServer XSDL に対応

通勤中のTermuxでおすすめの兵法

AndroidタブレットにUbuntuをインストールする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android でも Android アプリを開発したい

はじめに

どれだけの人が Android で Android アプリを開発したいと思っているのか分かりませんが、本記事は Android 上でセルフ開発を行うための環境構築を目的としています。
セルフ開発とは、自身の端末で自身の端末向けのアプリケーションを開発することを指します。
Windows や Mac、Linux などはセルフ開発が出来ますが、Android は少なくとも公式には開発ツールが出てませんでしたのでついやってみたくなり、思い立ったが吉日とばかりに構築してみました。
(とはいっても公式の Linux 版を使用しているのですけれどもね。。。)
まだ構築したばかりですので、もしかすると実際に開発を行うと不備があるかもしれませんが、その辺りはご容赦ください。
何か見つかれば調査して解決方法を書いていきたいと思います。
本記事では Android Studio を最低限の動作を行うところまでを構築します。

なお Android Studio を使うにあたってはスタイラスペンはあった方が良く、100円ショップのでも無いよりはマシです。
Android Studio のメニューが細いので親指はおろか小指でのタッチでもつらいです。
なお先端がプラスチックの Nintendo 3DS 向けのものは買わないで下さい。タッチパネルが反応しせんでした・・・

事前準備(Ubuntu のデスクトップ環境)

事前に UserLAnd や Termux 等で Ubuntu のデスクトップ環境を用意してください。
もし構築していない人は、後で最低限のデスクトップ環境の構築手順を記載しますので良かったら参考にして下さい。
おそらく Android Studio の動作速度は Termux の方が速いですが手順が面倒です。
速い端末をお持ちの人は UserLAnd で楽をするのも手かもしれません。

なお Debian はJDKのバージョンの問題で、Android Studio が動作しませんのでおすすめしませんが、openjdk 8 をなんとか用意できる人なら動かせるかもしれません。

ダウンロード

Android Studio, SDK tools, Arm64版attp2 をダウンロードします。
ダウンロードは Windows での操作を前提に書いていますが、Android でも同じ事はできると思います。

Android Studio, SDK tools のダウンロード

Android Studio downloads に入って Android Studio package の Linux (64-bit)版と SDK tools package の Linux 版をダウンロードします。

私の時には、android-studio-ide-191.6010548-linux.tar.gz と sdk-tools-linux-4333796.zip がありました。

後で、このファイル名を指定してコマンドを実行している箇所がありますが、もしファイル名が変わっていましたら、ご自分のダウロードしたファイル名に置き換えてコマンドを実行して下さい。

Android SDK Toolsは、開発とデバッグのためのツール一式を含むものになります。
Android Studio は単体ではなく、様々な外部ツールと連携して動作することになります。
後で Gradle と呼ばれるビルドツールで更に別のコマンドラインツール等をダウンロードします。

Arm64版attp2 のダウンロード

aapt2 とは、APKファイル(Androidでインストールに使用するアプリケーションのパッケージファイル)を生成等をするためのツールになります。
公式には Arm64 CPU 版は出ていませんが、Githubで公開されている方がいらっしゃいましたのでありがたく使わせていただきました。

https://github.com/thejunkjon/android-tools/tree/master/build/android-9.0.0_r33/aapt2/arm64-v8a/bin

上記のサイトに行き、Download ボタンを押下して、aapt2 ファイルをダウンロードしてください。

設置

Android の何か適当なファイル管理ツールでの操作になります。
私は、X-plore という Android アプリを使用していますが、ファイルを操作できれば何でも良いと思います。
先程ダウンロードした Android Studio, SDK tools を UserLAnd または Termux の Ubuntu から見える位置に設置します。

UserLAnd の場合

以下の対応表を見て、好みの位置に設置して下さい。

Ubuntu Android
/storage/internal/ (本体 側)/sdcard/Android/data/tech.ula/files/storage/
/storage/sdcard/ (SD Card 側)/storage/(SD Cardの名前)/Android/data/tech.ula/files/storage/

Termux の場合

以下の対応表を見て、好みの位置に設置して下さい。

Ubuntu Android
/sdcard/ (本体 側)/sdcard/
/storage/(SD Cardの名前)/ (SD Card 側)/storage/(SD Cardの名前)/

JDKのインストール

Ubuntu での操作になります。
まだデスクトップ環境を立ち上げる必要は無いので、シェルだけ立ち上げたほうが実施しやすい方はそうして下さい。

openjdk 8 をインストールします。
なお、Termux は root アカウントのため、先頭の sudo は不要です。

# openjdk 8 のインストール
sudo apt install -y openjdk-8-jdk

# openjdk 8 がインストールされているか確認
ls /usr/lib/jvm/java-8-openjdk-arm64

# テキストエディタ vim をインストールしていない人はインストール
sudo apt install -y vim

# 環境設定ファイルの編集
sudo vim /etc/enviroment

enviroment ファイルには以下を記載して保存します。

JAVA_HOME="/usr/lib/jvm/java-8-openjdk-arm64"

vim の操作方法を知らない人は以下だけ覚えればなんとかなります。

キーボードのキー 役割
i コマンドモードから入力モードに入る
ESC 入力モードを抜けてコマンドモードに戻る
:wq 保存して終了する。コマンドモードで操作すること。

保存したら環境設定ファイルを自分の環境に反映します。

source /etc/enviroment

Arm64版の openjdk 11 だと Android Studio が動作しません。
もし 11 も入れている人が居るのでしたら、後述のおまけに書かれている方法で 8 に切り替えましょう。

Android SDK tools のインストール

続いて、Android SDK tools をインストールします。

# UserLAnd の場合
mkdir -p /home/(自分のID)/Android/Sdk
cd (sdk-tools-linux-4333796.zip のあるディレクトリ)
cp sdk-tools-linux-4333796.zip /home/(自分のID)/Android/Sdk
cd /home/(自分のID)/Android/Sdk/
unzip sdk-tools-linux-4333796.zip

# Termux の場合
mkdir -p /root/Android/Sdk
cd (sdk-tools-linux-4333796.zip のあるディレクトリ)
cp sdk-tools-linux-4333796.zip /root/Android/Sdk
cd /root/Android/Sdk
unzip sdk-tools-linux-4333796.zip

UserLAnd の場合には ID ごとにディレクトリが変わりますが、ID は最初に Ubuntu を選択した時に登録する ID になります。
おそらくコマンドプロンプトの左側に書いてあるはずですので、その値を使用して下さい。

Android Studio のインストール

いよいよ Android Studio のインストールです。

# UserLAnd, Termux 共通

cd (android-studio-ide-191.6010548-linux.tar.gz のあるディレクトリ)
cp android-studio-ide-191.6010548-linux.tar.gz /usr/local/
cd /usr/local/
tar -xvzf android-studio-ide-191.6010548-linux.tar.gz
cd /usr/local/android-studio/bin/
sudo chmod +x studio.sh

Android Studio 自体のインストールはここまでですが、後で外部ツールの一部を手作業で作成して設置する必要があります。

Android Studio の設定

ここからは Ubuntu のデスクトップ環境での操作になります。

注意点ですが、XSDLを使用している方は、font scale を小さく(私は 0.5 にしました)設定して下さい。
大きく設定すると Android Studio のダイアログが、画面よりも大きくなってしまいボタンが画面からはみ出して押せなくなります。
また解像度が低くてもダイアログが画面からはみ出してしまうので、ある程度の大きさが必要なようです。私の場合は、1280x720 にしてあります。

コマンドラインから studio.sh を実行して Android Studio を立ち上げます。

LXTerminal (画面左下の鳥のマーク → System Tools → LXTerminal) 等を立ち上げて以下のコマンドを実行して下さい。

※私の場合にはデスクトップ環境にLXDEを使用しているので画面左下の鳥のマークと書いていますが、Xfceの場合には左上と読み替えて下さい。

# UserLAnd, Termux 共通

/usr/local/android-studio/bin/studio.sh

設定ファイルをインポートするダイアログが出るので、設定ファイルがあれば指定してOKボタンを押下してください。
なければ、そのままOKボタンを押下してください。

android studio(なぜか小文字)が書かれたスプラッシュウィンドウが出た後に、何やらデータを Google に送って欲しいというダイアログが出ます。
好きなボタンを選んで下さい。

Welcome ダイアログが出るので、Next ボタンを押下してください。

Install Type ダイアログが出ますが、私は Standard しか選んだことがないので、ここから先は Satndard を選んだものとして話を進めます。Next ボタンを押して下さい。

Select UI Theme ダイアログが出るので、好みのデザインを選択してから Next ボタンを押して下さい。

Veryfy Settings ダイアログが出るので、 Next ボタンを押下します。もしここでエラーメッセージが出るようでしたら、SDK Tools のインストール先が間違っているので、一旦 Cancel ボタンを押下して Android Studio を終了し、インストール先を修正してから再度 Android Studio を立ち上げて下さい。

Emulator Settings ダイアログが出ますが、特にやることはないので Finish ボタンを押下します。

Downloading Components ダイアログが出てコンポーネントをダウンロードします。ダウンロードが終わったら、Finish ボタンを押下します。
私の環境ではダウンロードに10分ほど掛かりました。

Android Studio ダイアログが出ますので、Start a new Android Studio project を押下します。

Create New Project ダイアログが出ますので、適当なパネルを選択し、Next ボタンを押下します。

Configure your project ダイアログが出ますので、API レベルは自分のスマートフォンの Android のバージョン(スマートフォンごとに Android の設定画面のメニューが違うので、Android のバージョンの確認方法は各自調査してください)に合わせて、他は適当な値を設定し、Finish ボタンを押下します。
ここでちょっと固まりますが、あわてず待ちましょう。しばらく待つと IDE が立ち上がるはずです。

右下に 2 processes running... の文字が見えたら gradle と呼ばれるビルドツールを自動的にダウンロードして、ビルドを行います。

gradle の動作が止まったら、メニューから File → Project Structure を選択して Project Structure ダイアログを開いて下さい。
SDK Location の JDK locations を JAVA_HOME に設定し、OK ボタンを押下します。

再びビルドが始まるので、気長にエラーが出るまで待ちます。
ビルドが始まらなかったら Android Stdio のメニュー Build → Rebuild Project を選択します。

ここでは gradle が使用する外部ツールでエラーが発生します。
Android Studio の下の方に Gradle Sync という文字が出ており、その右側にバーが出ているはずです。そのバーの動きが止まるまで待って下さい。
環境にもよりますが、私が UserLAnd で行ったときには10分以上はかかりました。

gradle がオフラインで動作するように設定します。
メニューから File → Settings を選択して Settings ダイアログを開いて下さい。
Build, Execution, Deployment → Gradle を選択し、Global Gradle settings にある Offline work にチェックを入れて、OK ボタンを押下します。
この処理は後述の jar ファイルが置き換えられないようにできないかなと思って設定していますが、ちょっと調べた限りでは置き換えが起こるかどうか分かりませんでした。。。←どうやらオンラインのままでも置きかわらないようです。でもオフラインの方が普段は速いので、オフラインにしておいましょう。たまに(LXDEからXfceに環境を変えたときなど)ビルド時にオフラインを解除してとエラーメッセージが出るときがあるので、その時に一旦解除してビルドを行い、再びオフラインにすると良いと思います。

aapt2 の為の jar ファイルの作成

先程、エラーがでましたが、原因は appt2 というツールです。
このツールの公式版は Intel CPU 向けなので Arm64 CPU であるスマホの上だと動作しません。
Arm64 CPU 版の aapt2 に置き換えます。

aapt2 は jar ファイルに含まれているので、まずは jar ファイルを探します。

画面左下の鳥のマーク → System Tools → File Manager PCManFM(以後、PCMan と呼びます) を選択します。
PCMan が立ち上がったらメニュー View → Show Hidden にチェックを入れます。
以下のフォルダを開いて下さい。

・UserLAnd の場合

/home/(ユーザーID)/.gradle/caches/modules-2/files-2.1/com.android.tools.build/aapt2

・Termux の場合

/root/.gradle/caches/modules-2/files-2.1/com.android.tools.build/aapt2

この下に aapt2-x.x.x-xxxxxx-linux.jar (x にはバージョンを表す数字が入る)があるはずですので探して下さい。
proto とついている方は今回は無視して大丈夫です。

aapt2-x.x.x-xxxxxx-linux.jar をどこかにコピーします。
ファイル名の拡張子を jar から zip に書き換えて解凍します。
aapt2 が入っているはずなので、最初にダウンロードした Arm64 CPU 版の aapt2 に置き換えます。
META-INFフォルダ、aapt2、NOTICE の3つを、aapt2-x.x.x-xxxxxx-linux.zip に圧縮します。
ファイル名の拡張子を zip から jar に書き換えます。
jar ファイルを、最初に見つけた jar ファイルの位置に上書きします。

Android Studio で再びビルド

Android Studio に再び戻り、メニューの Build → Rebuild project を選択して、リビルドでエラーが発生しないことを確認して下さい。

続いてメニューの Build → Build Bundle(s) / APK(s) → Build APK(s) を選択して APK ファイル(Androidアプリケーションのインストール用パッケージ)を作成してみて下さい。
右下に Build APK(s) というインフォメーションが出て、文字列の中に locate の文字が出るので、その文字列をクリックして下さい。
フォルダが開いて、その中に拡張子が apk のファイルが生成されていたら成功です。

試してみたい方は、APK ファイルを Android 側に渡して、インストールを試してみましょう。
もしインストールで解析ができないとか言われた場合には、自分のスマートフォンの Android のバージョンが APK ファイルの指定しているバージョンよりも古い可能性がありますので、プロジェクトを新たに作成し、自分のスマートフォンの Android のバージョンに合わせたSDKを(プロジェクトの生成ダイアログの Minimum API level の設定)合わせて下さい。

以上で開発環境の構築は終わりです。
空いた時間にでも軽く(?)開発するのもおつなものかもしれませんね。

おまけ

openjdk 11 を入れてしまった人の為の解決方法

まずは先述の通り、openjdk 8 をインストールします。

続いて以下のコマンドを実行して java-8-openjdk-arm64 が選択肢に出るので、番号で選択して下さい。

sudo update-alternatives --config java

最後に先述の通り /etc/environment を記載して JAVA_HOME 等を設定します。

参考サイト

Ubuntu18.04にJavaを導入する方法(JREやJDKの違いについても解説)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android] RecyclerView を用いた横スクロールする ListView の実装

何を作ったか

Android でおなじみの ListView ですが、水平方向へのスクロールは出来ません。そこで、RecyclerView を継承して横方向へスクロールする ListView のようなウェジェットを作ります。

RecyclerView とは?

複数の View を良しなに表示するウェジェットです。かなり自由度が高いので、大抵のものはこれでできます。詳細に関しては、【Android】RecyclerViewの基本的な実装 が詳しいです。

完成品

HorizontalListView.java
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

public class HorizontalListView extends RecyclerView{

    public interface OnItemClickListener{
        void onItemClick(View view, int position);
    }

    public HorizontalListView(Context context){
        super(context);
        initialize(context);
    }

    public HorizontalListView(Context context, AttributeSet set){
        super(context, set);
        initialize(context);
    }

    public HorizontalListView(Context context, AttributeSet set, int defaultAttr){
        super(context, set, defaultAttr);
        initialize(context);
    }

    private void initialize(Context context){
        LinearLayoutManager manager = new LinearLayoutManager(context);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        setLayoutManager(manager);
    }

    private OnItemClickListener mListener;

    public void setOnItemClickListener(OnItemClickListener listener){
        mListener = listener;
        Adapter adapter = getAdapter();
        if ( adapter instanceof ArrayAdapter ){
            ArrayAdapter a = (ArrayAdapter)adapter;
            a.mListener = this.mListener;
        }
    }

    @Override
    public void setAdapter(RecyclerView.Adapter adapter){
        super.setAdapter(adapter);
        if ( adapter instanceof ArrayAdapter ){
            ArrayAdapter a = (ArrayAdapter)adapter;
            a.mListener = this.mListener;
        }
    }

    @Override
    protected void onDetachedFromWindow(){
        super.onDetachedFromWindow();
        setOnItemClickListener(null);
        setAdapter(null);
        setLayoutManager(null);
    }

    private static class SimpleViewHolder extends ViewHolder{
        private SimpleViewHolder(View view){
            super(view);
        }
    }

    public static abstract class ArrayAdapter<E> extends RecyclerView.Adapter<ViewHolder> {

        public ArrayAdapter(List<E> list){
            mDataList = new ArrayList<>(list.size());
            mViews = new ArrayList<>(list.size());
            mDataList.addAll(list);
        }

        private List<E> mDataList;
        private OnItemClickListener mListener;
        private List<View> mViews;

        /**
         * リスト要素となるViewをインスタンス化する
         * @param parent
         * @return null not acceptable
         */
        public abstract View getView(ViewGroup parent);

        /**
         * リストに表示するデータをViewへ反映する
         * @param view 反映先のView
         * @param data 反映させるデータ{@link #getItem(int)}でも取得可能
         * @param position リスト上での位置
         */
        public abstract void onBindView(View view, E data, int position);

        @Override
        public final ViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
            return new SimpleViewHolder(getView(parent));

        }

        @Override
        public final void onBindViewHolder(final ViewHolder holder, int position){

            final View view = holder.itemView;
            E data = getItem(position);
            onBindView(view, data, position);

            mViews.add(holder.itemView);
            holder.itemView.setOnClickListener(new OnClickListener(){
                @Override
                public void onClick(View v){
                    if ( mListener != null ){
                        mListener.onItemClick(view, holder.getAdapterPosition());
                    }
                }
            });
        }

        @Override
        public void onDetachedFromRecyclerView(RecyclerView recyclerView){
            super.onDetachedFromRecyclerView(recyclerView);
            for ( View view : mViews ) view.setOnClickListener(null);
            mViews = null;
            mListener = null;
            mDataList = null;
        }

        @Override
        public final int getItemCount(){
            return mDataList.size();
        }

        public E getItem(int position){
            return mDataList.get(position);
        }
    }
}

実装の説明

横スクロールの実現

横方向を指定した LinearLayoutManager を RecyclerView に設定するだけでOK。

private void initialize(Context context){
    LinearLayoutManager manager = new LinearLayoutManager(context);
    manager.setOrientation(LinearLayoutManager.HORIZONTAL);
    setLayoutManager(manager);
}

アダプターの用意

RecyclerView も ListView と同様に、データと表示する View を管理するアダプターが必要となります。そこで、RecyclerView.Adapter を継承して HorizontalListView.ArrayAdapter を用意。使用時に実装すべきは以下の2つ。

    public abstract View getView(ViewGroup parent);

    public abstract void onBindView(View view, E data, int position);

getView(ViewGroup) で表示に使う View をインフレートして、onBindView(View,E,int) でデータ E の内容を View へ反映させます。
ListView を使う時、android.widget.ArrayAdapter を継承したアダプターでレイアウトをカスタマイズするのと同じ感じで操作できるように、同様なAPIを持たせました。

コールバックの用意

ListView と同様にリストの要素がクリックされたら通知するようなコールバックが欲しいですね。作りましょう。

public interface OnItemClickListener{
    void onItemClick(View view, int position);
}

クリックされた View と表示位置を通知します。実装としては、先ほど作った HorizontalListView.ArrayAdapter が getView(ViewGroup) で取得して表示する View に OnClickListener をセットして、受け取ったコールバックを中継します。ただし、注意として RecyclerView#setAdapter() で設定するアダプターが HorizontalListView.ArrayAdapter でないと機能しません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Activityで定義したメソッドをAdapterで呼び出す方法(interfaceを使用)

はじめに

今回は、Androidアプリ開発(Kotlin)における、Activityで定義したメソッドをAdapterから呼び出す方法に関して書いていこうと思います。
具体的な実装としては、ListAdaptersetOnClickListenerメソッドが走るタイミングで、Activityで定義したメソッド(Fragmentを表示する)を呼び出すというものになります。

少々前置きが長いため、実装方法について見たい方はこちらをタップ!

背景

Activityで定義したメソッドをAdapterで呼び出したい時、interfaceを使う以外のアプローチとして、

  1. Coroutineを使って行う方法
  2. LiveDataを使って行う方法
  3. グローバル変数をメソッド呼び出しのキッカケとする方法

などがあると考えましたが、以下の理由から、今回はinterfaceを使用して実装しました。

Coroutineを使ってメソッドを呼び出すアプローチを取らなかった理由

CoroutineのGlobalScopeはActivityのライフサイクルに依存せず動くため、
親となるActivityCoroutineのGlobalScopeを付与し、Adapter下のCoroutineに親のScopeを持たせ、メソッドを走らすタイミングをGlobalScopeで管理しようと考えました。

しかし、Adapterabstract classに変更し親のScopeを継承すると、ActivityでUI(Fragment)を表示するメソッドが呼び出せなくなってしまうため、この方法を取りませんでした。

LiveDataを使って行う方法を取らなかった理由

次に、LiveDataを使って値の購読処理を行い、値の変更を検知した際にActivityで定義したメソッドを呼び出す方法を検討しました。
具体的な流れは、Activityで値の購読処理を記述し、Adapterでボタンが押されたタイミングで購読している値を変更する。値が変更されたら、Activityで定義したメソッドを呼び出すというものです。

通常、LiveDataオブジェクトは ViewModelオブジェクトに格納するのが一般的ですが、今回は

  • ViewModelオブジェクトを返さず、Adapterを使用していた
  • 購読したい値は無かった

そのため、購読処理をメソッド呼び出しのためだけに利用するのは適切ではないと判断し、このアプローチを取りませんでした。

しかし、ViewModelオブジェクトを使用してAdapterを実装しており、

  • Adapter内のデータ変更を検知して、Activityからメソッドを呼び出したい場合
  • UIをデータの状態と一致させたい場合

は導入も手軽ですので、良い実装方法だと考えています。

グローバル変数をメソッド呼び出しのキッカケとするアプローチを取らなかった理由

最も単純な実装方法ですが、常にグローバル変数としてメモリを使用するため、このアプローチは取りませんでした。

実装の流れ

前置きが長くなってしまいましたが、実装について書いていきます。

Step1:interfaceを作成する

example)

interface FragmentCallInterface {
    fun setFragment()
}

Step2:Activityクラスにinterfaceを継承し、定義したいメソッドを記述する

example)

Activity.kt
class ShoppingListActivity : AppCompatActivity(), FragmentCallInterface{

    override fun setFragment() {
        // 行いたい処理
        val fragment = ShoppingSiteFragment()
        val fragmentManager = this.supportFragmentManager
        val fragmentTransaction = fragmentManager.beginTransaction()
        fragmentTransaction.replace(R.id.container, fragment)
            .addToBackStack(null)
            .commit()
    }

    // ...
}

Step3:interface呼び出しに関する部分を追記

まず、Adapterクラスにinterfaceに関する処理を追記します。
example)

Adapter.kt
class ShoppingListAdapter(private var activity: Activity, private var items: ArrayList<ShoppingItem>) : BaseAdapter() {

    // フィールドでコールバック先を宣言
    var listener: FragmentCallInterface? = null

    // ...
}

次は、ActivityクラスのAdapterを呼び出している部分に追記します。
example)

Activity.kt
// ...

var adapter = ShoppingListAdapter(this,generateData())
// 以下を追加
adapter.listener = this

// ...

Step4:Adapterクラスの任意の箇所でメソッドを呼び出す

example)

Adapter.kt
listener?.FragmentCallInterface()

おわりに

他にもこんな方法があるよ! 等ございましたらコメントくださると嬉しいです?
最後まで読んでいただきありがとうございました!

【おまけ】Java, Kotlinコード比較

java

MyAdapter.java
import androidx.appcompat.app.AppCompatActivity;

public interface CallInterface {
    void foo();
}

public class MyAdapter extends BaseAdater{
    private MyInterface listener;

    public ShoppingListAdapter(MyInterface listener){
        this.listener = listener;
    }

    // ...
}

Kotlin

MyAdapter.kt
import androidx.appcompat.app.AppCompatActivity

interface MyInterface {
    fun foo()
}

class MyAdapter() : BaseAdater() {
    val listener: MyInterface? = null

    // ...
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ Android ] setFlags()を使って一度に複数の画面遷移

例えば 画面1→画面2→画面3と遷移した後画面3→画面1と戻りたい時がある。そのような時finish()を使うこともできるが、画面が多いとうっとおしくなる。そんな時setFlags()というメソッドを見つけたので使ってみた。

方法

新たに画面1のアクティビティを起動して、これまでに遷移してきた画面をスタックから削除することで実現できる。

    Intent intent = new Intent(SubActivity2.this, MainActvity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //この引数は「スタックをクリア」を表す
    startActivity(intent);

Intent.setFlags()

今回はスタッククリアの引数を使ったが、他にも引数がある。参考URLに乗せておいた。
画面の内容を変えたくなかったので、スタックにある画面1を使いまわす方法も考えたが、画面2,3が残るのが嫌だったのでスタッククリアを使った。

感想

setFlags()の引数はいろいろあるので、うまくやればスタックの画面を使いながら不必要な画面だけクリアするということもできるかもしれない。

参考URL

・AndroidでActivityのスタックを削除する方法
http://9ensan.com/blog/smartphone/android/android-activity-stack-delete/

・intent.setFlagsメソッドの引数について
https://androidroid.info/android/activity/568/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ViewGroupでMaxWidthを実現する

ViewGroup には maxWidth / maxHeight は存在しません。素直に onMeasureoverride して実現しましょう。

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
            return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        }

        measure(
                MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
        )
        setMeasuredDimension(measuredWidth, measuredHeight)
    }

onMeasure 時に MeasureSpec.AT_MOST で最大値を指定して計算し、計算結果を setMeasuredDimension で設定することで、最大値以上にならないようにしています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android】Google Mapsの「赤いピン」のカスタマイズ9種を紹介します(画像付き)

概要

Androidアプリ内でGoogle Mapsを表示するときに、デフォルトのマーカー(例の赤いアレ)ではなく自分で用意した画像をマーカーとして使いたかった。
マーカーの画像を変更する方法と、その他カスタマイズする方法もまとめる。

本記事では東京駅に設置したマーカーをカスタマイズしていく。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions().position(tokyo))
}

tokyo_default.png

マーカーをカスタマイズする

カスタマイズできる項目を以下に示す。

  • Position(Required)
    地図上のマーカーの位置(LatLng)。Markerオブジェクトの必須プロパティ。
  • Anchor
    マーカーを表示する時の画像上のポイント。デフォルトは画像下部中央。
  • Alpha
    マーカーの不透明度。デフォルトは1.0。
  • Title
    マーカーをタップした時に情報ウィンドウに表示される文字列。
  • Snippet
    Titleの下に表示される追加の文字列。
  • Icon
    デフォルトのマーカー画像の代わりに表示される画像。
  • Draggable
    マーカーを移動できるようにしたい場合trueにする。デフォルトはfalse
  • Visible
    マーカーを非表示にする場合falseにする。デフォルトはtrue
  • Flat or Billboard orientation
    デフォルトのマーカーは、カメラに合わせて回転したり傾いたりはしない。フラットなマーカーはカメラに合わせて回転したり傾く。どちらのタイプのマーカーも、ズームによってサイズは変わらない。
  • Rotation
    度数(時計回り)で指定されたマーカーの向き。デフォルトは、フラットなマーカーの場合は北揃えである。そうでないマーカーの場合上向きで、常にカメラに向くように回転する。

マーカーのアンカーを変更する

画像のアンカーポイントを、x軸・y軸それぞれ0から1の位置で設定する。
以下のコードでは画像の中央に設定している。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .anchor(0.5f, 0.5f)
    )
}

tokyo_anchor.png
「東京」の部分にマーカーの中央が来るようになった。

マーカーの不透明度を変更する

マーカーの不透明度を0から1で設定する。
以下のコードでは不透明度50%に設定している。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .alpha(0.5f)
    )
}

tokyo_alpha.png

マーカーの文字列を設定する

マーカーをタップした時に表示されるタイトル(.title())・追加の文字列(.snippet())を設定する。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .title("Tokyo")
        .snippet("東京駅")
    )
}

tokyo_title_and_snippet.png

マーカーの色を変更する

デフォルトのマーカーの色を変更する。
用意されている色はこちら
以下のコードでは青に変更している。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_BLUE))
    )
}

tokyo_change_color.png

マーカーの画像を変更する

.icon()では、マーカーの画像自体を変更することもできる。
マーカー画像を以下の画像に変更した。

sample_marker.png(トラックパッドで書いたので線がガッタガタ)
sample_marker.png

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .icon(BitmapDescriptorFactory.fromResource(R.drawable.sample_marker))
    )
}

tokyo_change_image.png

2x、3xなどの画像を使いたい場合はmipmapの該当サイズのディレクトリに置く。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .icon(BitmapDescriptorFactory.fromResource(R.mipmap.sample_marker))
    )
}

tokyo_change_image_mipmap.png
若干鮮明になったように見える……。

fromResource()の他にも、以下の方法で画像を設定できる。

  • fromAsset(assetName: String)
    アセットディレクトリ内の画像名
  • fromBitmap(image: Bitmap)
    Bitmap画像
  • fromFile(fileName: String)
    ローカルストレージ内の画像名
  • fromPath(absolutePath: String)
    画像ファイルの絶対パス

マーカーを移動できるようにする

マーカーをドラッグできるように設定する。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .draggable(true)
    )
}

マーカーを非表示にする

マーカーが地図に表示されないように設定する。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .visible(false)
    )
}

tokyo_invisible.png

フラットなマーカーにする

カメラの回転や傾きに追従するようになる。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .flat(true)
    )
}

tokyo_flat_false.png tokyo_flat.png
左: デフォルト 右: フラット

マーカーの向きを変更する

マーカーの向きを度数(時計回り)で設定する。
以下のコードでは90°(3時の方向:clock3:)に設定している。

override fun onMapReady(map: GoogleMap) {
    val tokyo = LatLng(35.6804, 139.7690)
    map.addMarker(MarkerOptions()
        .position(tokyo)
        .rotation(90.0f)
    )
}

tokyo_change_rotation.png

参考

https://developers.google.com/maps/documentation/android-sdk/marker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む