20191128のAndroidに関する記事は10件です。

ARCoreを使ったARアプリをエミュレータで動かす

ARKitを使ったARアプリはエミュレータで動かせませんが、ARCoreを使ったARアプリはエミュレータで動かすことができます。
エミュレータで動かす際に少しハマったことがあるので共有します。
(Unityで作成したARアプリは、この記事とは違うやり方なので注意してください。)

必要な環境

  • Android Studio 3.1以降
  • Android Emulator 27.2.9以降

サンプルプロジェクトのダウンロード

この記事ではARCore SDKにあるサンプルプロジェクトを使用します。
ARCore SDKをDLします。
そして、samples/hello_ar_javaをAndroid Studioで開きます。

エミュレータ

エミュレータを起動します。
端末によってはARCoreをサポートしてないため、エミュレータはなるべく新しめの端末の方がいいです。
API Levelは27(Oreo)以降が必要です。(API Levelも最新の29が良さそう)

サンプルアプリの起動

ここからが少しハマったところです。
サンプルアプリを起動します。
すると...

Screenshot_1574947031.png

This application requires the atest version of Google Play Services for AR.
(このアプリを使用するには、最新バージョンのGooglePlay開発者サービス(AR)が必要です。)

と、出てきます。
どうやら、エミュレータでARアプリを使うにはGoogle Play Services for ARが必要らしいです。
CONTINUEを押すとGoogle Play Storeに飛ばされます。

Screenshot_1574947339.png

これをインストールしなきゃいけないのに
Your device isn't compatible with this version.
(お使いのデバイスはこのバージョンに対応していません。)

って表示されて、インストールできなく、ARのサンプルアプリが無限にエミュレータで起動できない状態になってしまいます。

こんな時は、大人しくARCoreのドキュメントをみましょう!

どうやらGitHubのリリースページから最新のGoogle_Play_Services_for_AR.apkをDLする必要があるっぽいです。
*現在(2019年11月時点)では、v1.13.0が最新
DLしたapkファイルをエミュレータ上にドラッグするか、エミュレータ起動中に、以下のadbコマンドを叩きます。

adb install -r Google_Play_Services_for_AR_1.13.0_x86_for_emulator.apk

うまくインストールされるとアプリの一覧に表示されます
SCREENSHOT_1574948695 3.png

そして、もう一度サンプルアプリを立ち上げると...
Screenshot_1574950058.png

無事、エミュレータでARアプリを動かせました!!!!
なぜか平面認識すると白くなるようです。他のアプリだとそうならないので、おそらくサンプルアプリの影響です。

エミュレータ内で動くためには以下のKeyを使います

platform key
mac option + WASD
Linux, Windows Alt + WASD

ちなみに、エミュレータの右にある「・・・」の中にあるVirtual sensorsで、センサの値を直接いじっても動きます。

API Levelを28以下にすると...

API Levelを29じゃなく、28や27でやった場合、
this device does not support ARと表示されてアプリが使えないです。apkファイルのバージョンを下げたらいけそう...って思ったのですが、そもそもGoogle_Play_Services_for_AR.apkを認識してくれませんでした。
知見ある方、コメント待ってます。

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

【Androidアプリ入門】内部ストレージに保存・読込する♪

今回は、前回のAndroidアプリで内部ストレージに保存・読込を追加した。
情報は、ググるとそれなりに出てくるが結構ハマって、しかも日本語の読込は失敗しているが、現状進捗がなさそうなので、ここでまとめておこうと思う。

英語保存 英語読込 日本語保存 日本語読込
eigo.jpg eigo_output.jpg nihongo.jpg nihongo_bake.jpg

内部ストレージへの保存

これは、以下の参考のまま動きました。
ここでは、入力された文字列をinputStrに取得しているので、それを書き込むこととした。
また、File名は"testfile.txt"とした。
【参考】
Android Internal Storage Example Tutorial

MainActivity.kt
when(view.id) {
                //表示ボタンの場合…
                R.id.btClick -> {
                    //入力された名前文字列を取得。
                    val inputStr = input.text.toString()
                    //メッセージを表示。
                    output.text = df.format(date) + "\n"+inputStr + "さん、こんにちは!"  //inputStr + "さん、こんにちは!"
                    val fOut = openFileOutput("testfile.txt", Context.MODE_PRIVATE)
                    fOut.write(inputStr.toByteArray())
                    fOut.close()
                }
                //クリアボタンの場合…
                R.id.btClear -> {...

実際に書き込まれているかどうかを確認するために参考のような手順で確認してみた。
【参考】
[Android] アプリの内部メモリを覗くとパーミッションでブロックされる

C:\Users\MuAuan\AndroidStudioProjects\HelloSample_qiita>bash
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb run-as
プログラム 'adb' は以下のパッケージで見つかりました:
 * adb
 * android-tools-adb
次の操作を試してください: sudo apt install <選択したパッケージ>
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ sudo apt install adb
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb shell
generic_x86:/ $
1|generic_x86:/ $ run-as com.example.hellosample

generic_x86:/data/data/com.example.hellosample $ ls
cache code_cache files
generic_x86:/data/data/com.example.hellosample $ cd files
generic_x86:/data/data/com.example.hellosample/files $ ls
file\ name testfile.txt

generic_x86:/data/data/com.example.hellosample/files $ ls -l
total 12
-rw-rw---- 1 u0_a137 u0_a137 0 2019-11-26 12:11 file\ name
-rw-rw---- 1 u0_a137 u0_a137 4 2019-11-26 14:17 testfile.txt

ということで、以下のようにtestfile.txtにfshkが書き込まれていることが分かります。

generic_x86:/data/data/com.example.hellosample/files $ cat testfile.txt
fshkgeneric_x86:/data/data/com.example.hellosample/files $

読み込み

書き込んだファイルから、以下のコードで読み込みも出来ました。
ただし、英語のみ正しく読み込めました。
つまり、上記のように日本語もタイ語も文字化けしてしまいました。

MainActivity.kt
R.id.btClick1 -> {
                    val fin: FileInputStream = openFileInput("testfile.txt")
                    var c=0
                    var temp=""
                    while (fin.read().also({ c = it }) != -1) {
                        temp = temp + Character.toString(c.toChar())
                    }
                    fin.close()
                    output.text = df.format(date) + "\n"  + temp
                    input.setText("")
                }

そして、日本語やタイ語がファイルにどう書き込まれているかを上記の方法でも可能ですが、今回は以下の方法で調べました。
【参考】
Android Studioでファイル一覧を確認したい!
この方法で、スマホ実機の内部ストレージが覗けます。
さらに、そこにあるファイルをコピペすると見事に安全な?Dirにダウンロードして以下の図のようにtestfile.txtに書き込まれている内容が見えました。
deviceFileExplorer.jpg

まとめ

・前回のAndroidアプリで入力データを内部ストレージに保存・読込してみた
・スマホ実機の内部ストレージを覗けるようになった

・日本語は文字化けしてうまく出来ていない

おまけ

日本語文字化け解消のために以下を実施したがうまくいっていない
【参考】
Kotlin で手軽にテキストファイルを読み込む
試したコードは以下のとおり
このコードで一応実行は出来るが、読み込みを実施すると、アプリが中断してしまう。
これは、上記のコードでもファイル名が異なったりすると出る事象なので、やはりtemp = File(pathUtf8).readText(Charsets.UTF_8)で起きているようだ。
この部分をコメントアウトすると、読み込みはできないが、中断は起こさない。

MainActivity.kt
               R.id.btClick1 -> {
                    var temp=""
                    var pathUtf8="testfile.txt"
                    temp = File(pathUtf8).readText(Charsets.UTF_8)
                    output.text = df.format(date) + "\n"  + temp
                    input.setText("")
                }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Androidアプリ入門】内部ストレージに保存・読込する♪ハマった編

今回は、前回のAndroidアプリで内部ストレージに保存・読込を追加した。
情報は、ググるとそれなりに出てくるが結構ハマって、しかも日本語の読込は失敗しているが、現状進捗がなさそうなので、ここでまとめておこうと思う。

英語保存 英語読込 日本語保存 日本語読込
eigo.jpg eigo_output.jpg nihongo.jpg nihongo_bake.jpg

内部ストレージへの保存

これは、以下の参考のまま動きました。
ここでは、入力された文字列をinputStrに取得しているので、それを書き込むこととした。
また、File名は"testfile.txt"とした。
【参考】
Android Internal Storage Example Tutorial

MainActivity.kt
when(view.id) {
                //表示ボタンの場合…
                R.id.btClick -> {
                    //入力された名前文字列を取得。
                    val inputStr = input.text.toString()
                    //メッセージを表示。
                    output.text = df.format(date) + "\n"+inputStr + "さん、こんにちは!"  //inputStr + "さん、こんにちは!"
                    val fOut = openFileOutput("testfile.txt", Context.MODE_PRIVATE)
                    fOut.write(inputStr.toByteArray())
                    fOut.close()
                }
                //クリアボタンの場合…
                R.id.btClear -> {...

実際に書き込まれているかどうかを確認するために参考のような手順で確認してみた。
【参考】
[Android] アプリの内部メモリを覗くとパーミッションでブロックされる

C:\Users\MuAuan\AndroidStudioProjects\HelloSample_qiita>bash
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb run-as
プログラム 'adb' は以下のパッケージで見つかりました:
 * adb
 * android-tools-adb
次の操作を試してください: sudo apt install <選択したパッケージ>
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ sudo apt install adb
ubuntu@MyComputer:/mnt/c/Users/MuAuan/AndroidStudioProjects/HelloSample_qiita$ adb shell
generic_x86:/ $
1|generic_x86:/ $ run-as com.example.hellosample

generic_x86:/data/data/com.example.hellosample $ ls
cache code_cache files
generic_x86:/data/data/com.example.hellosample $ cd files
generic_x86:/data/data/com.example.hellosample/files $ ls
file\ name testfile.txt

generic_x86:/data/data/com.example.hellosample/files $ ls -l
total 12
-rw-rw---- 1 u0_a137 u0_a137 0 2019-11-26 12:11 file\ name
-rw-rw---- 1 u0_a137 u0_a137 4 2019-11-26 14:17 testfile.txt

ということで、以下のようにtestfile.txtにfshkが書き込まれていることが分かります。

generic_x86:/data/data/com.example.hellosample/files $ cat testfile.txt
fshkgeneric_x86:/data/data/com.example.hellosample/files $

読み込み

書き込んだファイルから、以下のコードで読み込みも出来ました。
ただし、英語のみ正しく読み込めました。
つまり、上記のように日本語もタイ語も文字化けしてしまいました。

MainActivity.kt
R.id.btClick1 -> {
                    val fin: FileInputStream = openFileInput("testfile.txt")
                    var c=0
                    var temp=""
                    while (fin.read().also({ c = it }) != -1) {
                        temp = temp + Character.toString(c.toChar())
                    }
                    fin.close()
                    output.text = df.format(date) + "\n"  + temp
                    input.setText("")
                }

そして、日本語やタイ語がファイルにどう書き込まれているかを上記の方法でも可能ですが、今回は以下の方法で調べました。
【参考】
Android Studioでファイル一覧を確認したい!
この方法で、スマホ実機の内部ストレージが覗けます。
さらに、そこにあるファイルをコピペすると見事に安全な?Dirにダウンロードして以下の図のようにtestfile.txtに書き込まれている内容が見えました。
deviceFileExplorer.jpg

まとめ

・前回のAndroidアプリで入力データを内部ストレージに保存・読込してみた
・スマホ実機の内部ストレージを覗けるようになった

・日本語は文字化けしてうまく出来ていない

おまけ

日本語文字化け解消のために以下を実施したがうまくいっていない
【参考】
Kotlin で手軽にテキストファイルを読み込む
試したコードは以下のとおり
このコードで一応実行は出来るが、読み込みを実施すると、アプリが中断してしまう。
これは、上記のコードでもファイル名が異なったりすると出る事象なので、やはりtemp = File(pathUtf8).readText(Charsets.UTF_8)で起きているようだ。
この部分をコメントアウトすると、読み込みはできないが、中断は起こさない。

MainActivity.kt
               R.id.btClick1 -> {
                    var temp=""
                    var pathUtf8="testfile.txt"
                    temp = File(pathUtf8).readText(Charsets.UTF_8)
                    output.text = df.format(date) + "\n"  + temp
                    input.setText("")
                }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Navigation Componentによる画面遷移時のToolbar

はじめに

この記事ではNavigation Componentの基本的な使い方は記載していません。
Navigation Componentを使った画面遷移時のToolbarの処理に着目しています。

Activityが持つViewの処理をActivityに書く

Toolbarを表示する際、ほとんどの場合ホストとなるActivityがToolbarを持ちます。
その際、ゲストのFragmentでToolbarに関する処理を書くと

val toolbar = view.findViewById<Toolbar>(R.id.toolbar)
requireActivity().setSupportActionBar(toolbar)

となりますが、あまり書きたくないです。
Toolbarに限らずFloatingActionButtonなど、Activityが持つViewに関する処理の変更はFragmentに書かずに極力Activityに書き、正しく役割分担することにより保守性を高めたいです。

Navigation Componentによる画面遷移時も同様

Navigation Componentを使う場合に作成するActivityとFragmentも同様に正しく役割分担させたいです。
この記事ではNavigation Componentによる画面遷移時の、Activityが持つToolbarの処理に着目し、Activityに処理を書いています。
以下の項目について記載しています。

  • FragmentによってToolbarの処理を分岐
  • Fragment間の画面遷移時にToolbarに関するViewを変更

各種バージョン

コードを読みやすくするためにRxBindingを使っています。

  • Kotlin 1.3.60
  • Navigation Component 2.2.0-rc02
  • RxBinding 3.1.0

FragmentによってToolbarの処理を分岐

Navigation Componentを使っているActivityに、表示しているFragmentによってToolbarの戻るボタン押下時の処理を分岐させた例です。
現在のDestinationのID(navigationレイアウトのfragmentのID)に応じて処理を分岐させています。

MainActivity.kt
binding.toolbar
    .navigationClicks()
    .filter { navController.currentDestination != null }
    .subscribe {
        when (navController.currentDestination!!.id) {
            R.id.firstFragment -> finish()
            R.id.secondFragment -> navController.navigate(
                SecondFragmentDirections.secondToFirst()
            )
        }
    }

現在のDestinationがFirstFragmentの場合、Activityを終了(finish())します。
現在のDestinationがSecondFragmentの場合、FirstFragmentに遷移します。

Fragment間の画面遷移時にToolbarに関するViewを変更

NavControllerにはaddOnDestinationChangedListenerというDestinationの変更に関するListenerがあります。
これを利用し画面遷移時にToolbarにあるButtonのvisibilityを変更しています。

MainActivity.kt
navController.addOnDestinationChangedListener { _, destination, _ ->
    when (destination.id) {
        R.id.firstFragment -> binding.toolbarButton.visibility = View.GONE
        R.id.secondFragment -> binding.toolbarButton.visibility = View.VISIBLE
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidでToggleButtonを用いてServiceを起動・終了させてみる(起動時に通知バーに表示させる)

はじめに

Androidアプリ開発(javaも)を触るのはかなり久々で何も覚えていなかったのですが、ちょっと思い立ってアプリを作ってみようかと思って奮闘中です。あまり詳しくないので細かい解説は出来ませんしn番煎じ感が否めませんが、ログとして残す意味でも書いておきます。
昔は結構重かったEclipseを使ってAndroid用のプラグインやらAVD入れてゴチャゴチャやって・・・って感じでしたが、今ではAndroid studioでレイアウトもGUIで直感的に作れるようになって、エミュレータやら外部端末の設定も楽々で、サクサクになっていて便利になりましたね。

やりたいこと

ある一定間隔で何らかの処理をさせるAndroidアプリを開発したいと思っています。勝手に死んで欲しくはないので、ここではIntentServiceではなく、Serviceを使おうと思いました。
サービスの開始・停止にはToggleButtonを使用します。

環境について

  • 開発環境はAndroid studio 3.5.2
  • テストを走らせる端末はAndroid 8.0.0 & 9
  • 言語はJava

実装

レイアウトについて

activity_main.xml
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">

    <ToggleButton
        android:id="@+id/service_toggle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="ToggleButton"
        android:textOff="サービス開始"
        android:textOn="サービス終了"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

作り方は簡単です。
activity_main.xmlを開くとレイアウトをGUIで作れるDesignモードで開けるので、Palletteの中からButtons->ToggleButtonを選択します。On / Off時の表示させるテキストは右側Attributesから編集出来ます。

MainActivityについて

ここでは、主にToggleButtonの処理を行います。

MainActivity.java
package com.example.testproject;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.widget.CompoundButton;
import android.widget.ToggleButton;

public class MainActivity extends AppCompatActivity implements CompoundButton.OnCheckedChangeListener {

    ToggleButton service_toggle;

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

        service_toggle = (ToggleButton)findViewById(R.id.service_toggle);
        service_toggle.setChecked(TestService.isActive);
        service_toggle.setOnCheckedChangeListener(this);
    }

    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
        if (isChecked) {
            startService(new Intent(MainActivity.this, TestService.class));
        } else {
            stopService(new Intent(MainActivity.this, TestService.class));
        }
    }
}

serviceを起動するToggleButtonservice_toggleをレイアウトと紐付けます。後に出てくるTestServiceが起動中かを取得して、その結果をToggleに反映させます(service_toggle.setChecked(TestService.isActive);)。
こうすることによって、例えばActivityが破棄された状態でもバックグラウンドでServiceが起動していればToggleがOnになるように出来ます。

implementしているCompoundButton.OnCheckedChangeListeneronCheckedChangedをOverrideして、Toggleを操作した時にServiceへIntentを送ります。

Serviceについて

ここでは具体的にバックグラウンドで処理させたいものを書きます。
今回はServiceのStart / Stop時にToastを出力させ、Serviceの実行中は1秒毎にLogを出力させるようにします。

TestService.java
package com.example.roadmemo;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import android.widget.Toast;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import static android.widget.Toast.*;

public class TestService extends Service {
    static boolean isActive = false;
    final Handler handler = new Handler();
    final Runnable runnable = new Runnable() {
        Integer count = 0;
        @Override
        public void run() {
            ++count;
            Log.d("LocalService", "runnable" + count.toString());
            handler.postDelayed(this, 1000);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();;
        Log.d("LocalService", "onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Context context = getApplicationContext();
        int notificationId = 1;
     //------------ for test ------------
        Log.d("LocalService", "onStartCommand Received start id" + startId + ": " + intent);
        Toast toast = makeText(this, "Start service", LENGTH_LONG);
        toast.show();

     // -------------- begin notification ---------------
        Intent notificationIntent = new Intent(this, MainActivity.class);
        notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
        String channelId = "default";
        NotificationChannel channel = new NotificationChannel(
                channelId, context.getString(R.string.app_name), NotificationManager.IMPORTANCE_LOW);
        if (notificationManager != null) {
            notificationManager.createNotificationChannel(channel);
        }

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setContentTitle("サービス")
                .setContentText("サービスが起動中です。終了するには通知をタップしてアプリから終了してください。")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);

        Notification notification = builder.build();
        notification.flags |= Notification.FLAG_ONGOING_EVENT;

        startForeground(notificationId, notification);
// -------------- end notification ---------------

        handler.post(runnable);

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        Log.d("LocalService", "onDestroy");
        Toast toast = makeText(this, "Stop service", LENGTH_LONG);
        toast.show();
        handler.removeCallbacks(runnable);

    }
}

作ったServiceが動かせるように、AndroidManifest.xmlに以下を追記します。

AndroidManifest.xml
 <service android:name=".CachePositionService" />

Android 9以降に対応するのには、こちらにあるようにAndroidManifest.xmlに、以下の権限

AndroidManifest.xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

を追加しなければなりません。

一定時間毎に行う処理

まず、一定時間(ここでは1秒)ごとの処理を記述するのにはHandler.postDelayed()メソッドを使います。このHandlerは他のThreadとの通信をするもの。Runnableクラスのインスタンスを投げることが出来ます。Runnableクラスのインスタンスはお決まりがあるようです(Android studioでは、Runnableのインスタンスを書こうとすると自動的にRunnable.run()をOverrideする形で記述するように出てきた)。
Serviceが実行中に行われるonStartCommand内で

handler.post(runnable)

とし、Serviceが終了される時に行われるonDestroy内で

handler.removeCallbacks(runnable)

とすることで、handlerの処理を制御出来ます。

通知に関して

通知バーに出てくる通知をタップすると、MainActivityが呼ばれるようにしたいです。この際にもIntentを渡します。が、この時は通知を押されるという処理が行われるまでIntentを発行したくないので、そんな時にはPendingIntentというものを使います。
そして、NotificationManagerCompat1にてこのServiceにおけるContextについて通知を出すよと書いて、Builderで具体的な通知内容の雛形を書き、その雛形を元にBuildします。
ここでのポイントですが、BuildしたNotificationのインスタンスに、バックグラウンドで動いているときには通知を消せないように

notification.flags |= Notification.FLAG_ONGOING_EVENT;

を追加します。
また、notificationIdに0を入れると通知が表示されないので、別の数字にします。

作ったServiceが動いているかをActivityで確認する

今、自分が作ったServiceが動いているのかをActivityで確認するのに最初は, MainActivity.javaにて

    boolean isActive() {
        boolean active = false;
        ActivityManager activityManager = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
        for (ActivityManager.RunningServiceInfo serviceInfo : activityManager.getRunningServices(Integer.MAX_VALUE)) {
            if (TestService.class.getName().equals(serviceInfo.service.getClassName())) active = true;
        }
    return active;

のように全ての起動中のServiceを舐め回して、Stringで一致する時にtrueを返すようにしていましたが、これはAndroid 8 (API > 26)からはgetRunningServicesメソッドが非推奨となったようなので、代わりにServiceの方でstaticなbool型変数を用意しておいて、onDestroy()が呼ばれるまでtrueにしておくという形にしました。

結果

まず、出来た画面がこちら
Screenshot_1574933592.png
ToggleButtonをタップすると"Service start"というToast表示が出力されると共に通知バーに通知が現れます。Logcatを確認するとTestService.onStartCommandが呼ばれているのが分かります。
Screenshot_1574933596.png
通知もこのように出てきます(目隠ししているのはアプリの名前です)
InkedScreenshot_1574933606_LI.jpg
Serviceが生きている時に、Activityを破棄(アプリを終了)されても、Serviceは生きていることが確認出来ます。通知バーの通知は消えませんし、Logcatの方ではServiceが生き続けていて1秒毎にログが出力されている筈です。
最後に、通知をタップしてActivityを再び起動させ、ToggleButtonを見ると、On状態のままになっています。このToggleを押すと"stop service"というToastと共に通知が消えます。TestService.onDestroyも無事にCallされていることがLogを見ても分かります。
Screenshot_1574933620.png

これで狙った挙動になりました!

参考

一通りのチュートリアルは公式のこちら
https://developer.android.com/training/basics/firstapp?hl=ja
レイアウトの作り方(公式)
https://developer.android.com/training/basics/firstapp/building-ui?hl=ja
トグルボタンについて
https://developer.android.com/guide/topics/ui/controls/togglebutton?hl=ja
Serviceの解説(公式)
https://developer.android.com/guide/components/services
サービス中の通知について(公式)
https://developer.android.com/guide/components/services#Foreground
サービスの使い方
https://akira-watson.com/android/service.html
定期的に処理を行う方法について
https://qiita.com/aftercider/items/81edf35993c2df3de353


  1. 公式のReferenceを見ると、NotificationManager,Notificationを用いるよりNotificationManagerCompat,NotificationCompatを使用した方が複数バージョンの互換性があるらしいです。 

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

UXデザイナーと連携する際に考慮したい事項

1ピクセルのデザインにこだわるUXデザイナーとの連携で、いろいろ苦労した経験からの知見を簡易的にまとめます

ピクセル単位での調整を可能にする

エンジニアが、都度、確認できるデザインスペックが必要
XD、Sketch、Prott、Abstractなど、ピクセル単位で確認でき、素材を書き出せるツール・サービスを使う

要件定義書

最初、エクセルやスプレッドシートを使っていたが、エンジニアは更新しやすいものの、デザイナーや企画者は使いづらい
さらに、デザインスペックを別で用意する必要がある
→ XDで要件定義を作成してもらい、デザインスペックも同時に作成&確認できるようにした

ワイヤーフレーム

要件定義からデザインを起こすまでのステップで、デザイナーがどのレベルまでデザインに落とし込めばいいか分からない
→ アプリエンジニアは、要件とデザインの両方がないと、実現可能かを判断できないケースが多いので、ある程度のデザインは必要だが、完成版を見せられると、その時点で判断不能に陥ることが多いので、ワイヤーフレームレベルのデザインで十分。
デザイナーとエンジニアで話し合うステップが必要なので、ワイヤーフレームレベルのデザインで一旦、エンジニアに共有する

デザインスペックの指示通りに、修正は結構な手間がかかる

デザイナーがXcodeを使えるようにして、文字サイズやフォント、ピクセル単位での修正は、デザイナーにやってもらう

デザイナーから受け取った素材を、リネームする手間がかかる

スプレッドシートで、素材依頼シートを作って、名前を指定して書き出してもらう
※あまりいい運用じゃなかったので、もっといい方法を考えた方がいい

ピクセル単位で調整していると同じような素材が増え、アプリサイズが肥大化する

共通で使う素材は、命名規則で判別しやすいようにする。common_XXXXX など
デザイナーがエンジニアに渡す際に、共通で使う素材かどうかを伝えてもらえると助かります。

カラーが細かくなり、どのカラーを使えばいいか、迷ってしまう

基本は、デザインスペック通りだが、間違えてるのではと思う箇所が出がちなので、大まかに、テキスト用のカラーとか、テーマカラーとかを決めて、間違いに気づけるように

iOSのTips
- 行間は、TextFieldだと行間指定できないので、UILabelかTextViewに変更する必要がある
- フォントを、システムフォントじゃなくて、ヒラギノに明示的に設定する
AndroidのTips
- Android は、dpで指定 (端末のフォントサイズ設定に対応する場合は、spがいいが、フォントサイズ対応すると、レイアウト崩れを考慮しないといけないので、逆にdp指定で対応させない)

デザインのベースにする解像度の決め方

各プラットフォームでの端末シェアを考慮

【2019最新】スマホ・タブレットの解像度一覧表(画面サイズの割合)
https://webdesign-abc.com/tech/resolution-list/

【2019年】最新のディスプレイ・モニター解像度シェアが知りたい!
https://web-knight.net/display-resolution/

「iPhone6/7/8」で解像度は「375 * 667」
「Android XPERIA」。解像度は「360 x 640」
が無難

デザイナーにお願いしたいこと

Atomic Design を考慮して、パーツ・コンポーネント単位で定義してほしい→

これが出来ると、開発でもコンポーネント化しやすい
[参考]
Atomic Design を分かったつもりになる
https://design.dena.com/design/atomic-design-%E3%82%92%E5%88%86%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%A4%E3%82%82%E3%82%8A%E3%81%AB%E3%81%AA%E3%82%8B/

AppleのHIGを隅々まで熟読してほしい

Human Interface Guidelines
https://developer.apple.com/design/human-interface-guidelines/
HIGから学ぶ、3つのナビゲーションとそのあるべき姿とは
https://note.com/ta0o_o0821/n/n140cd75cc023

HIGを理解したあと、iOSとAndroidアプリの違いや、マテリアルデザインを熟読

マテリアル デザインでアプリの魅力を高める
https://developer.android.com/distribute/best-practices/develop/use-material-design?hl=JA
ただ、個人的には、マテリアルデザインのリファレンスは、より具体的な実装方法のマニュアル的な部分が多く記事も多いので、熟読するというより、マテリアルデザインがどういうもので、どんな事ができるかを把握してほしいという感じ
Human Interface Guidelines は概念的な内容で、マテリアルデザインはデザイン手法のマニュアルといった感じ

その他

iOSとAndroidのUIの違い
https://nogson2.hatenablog.com/entry/2018/11/16/123256

iOS、Androidの解像度について(iOSはpt / Androidはdp)
https://qiita.com/eKushida/items/ff1ecaacdb54ce7f9f5c

エンジニアもデザインのことを知る必要がある

【これからのスキル】デザイナーとエンジニアの境界線がどんどん無くなる
https://blog.btrax.com/jp/designer-engineer/
優れたプロダクト開発のために。エンジニアがデザインを学ぶ時のおすすめ本
https://goodpatch.com/blog/designbooks-for-developer/

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

Android(Pixel 3) OSダウングレード手順メモ

はじめに

Androidアプリ開発で、OS依存の問題かを確認するためにAndroidOSをダウングレードする必要がありました。
少しハマったので手順をメモします。Android Studioは使わずに実施しました。
バックアップしてから実施してください。

環境

PC:MacBook Pro
Android端末:Pixel3 AndroidOS10

やりたいこと

Pixcel3のAndroidOSのバージョンを10から9にダウングレードしたい。

手順

1.ファクトリーイメージのダウンロード(PC)

pixcel3のイメージは以下にありました。
https://developers.google.com/android/images#blueline

2.ダウンロードしたファクトリーイメージのフォルダを展開する(PC)

3.カレントディレクトリを2で展開したフォルダに変更する(PC)

4.開発者向けオプションの『OEMロック解除』と『USBデバッグ』を有効にする(Android)

開発者オプションの表示の仕方は以下。
https://enjoypclife.net/2018/12/23/android-9-developer-option-pixel-3/

5.Android端末とPCをUSB接続する

6.電源ボタンと音量(下)ボタンを長押しでBootloaderを起動する(Android)

PC側ではadb reboot bootloaderを実行すれば起動できます。

7.fastboot flashing unlockを実行する(PC)

ブートローダーがアンロックされます。

8.「Unlock fastboot」を選択する(Android)

これでDevice statusがunlockになります。

9.sh flash-all.shを実行する

しばらく待つと、Pixcel3が再起動して無事にダウングレードすることができました。

おわりに

手順4の「OEMロック解除」をせずに実施していたこと、手順8が抜けていたことが原因で少しハマりました。
普段Androidを使っていないこともあって、Androidの勝手が分からず簡単なところで躓いてしまいます。。

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

[ Android ] HTTP通信時に発生するタイムアウトエラー処理

AndroidでHttp通信を行う際、起きるタイムアウトの処理について述べる。

サンプル

まずHttp通信は以下のような処理を行う。

        HttpURLConnection connection = null;
        try {
            URL url = new URL(params[0]);
            connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(20000);           // 接続タイムアウト
            InputStream is = connection.getInputStream();
        }catch・・・

接続タイムアウト

サンプルではタイムアウトを20secに設定してある。サーバからのレスポンスが20secで返ってこなかった場合、SocketTimeoutExceptionが発生する。この例外のキャッチブロック中にタイムアウトが発生した場合の処理を書けば、例外処理できる。

        }catch(SocketTimeoutException e){
            //タイムアウトが発生したときの処理
        }cacth(IOException e){
            //・・・
        }

IOExceptionはHttpURLConnectionのキャッチブロックによく利用されるので書いている。
これらの順序が逆になるとコンパイルエラーになるので、気を付けて頂きたい。

参考URL

https://ku6.jp/report/52.html

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

MediaPlayer Classを使う

MediaPlayerの機能を使ってみます
image.png

簡単なI/Fを作ります。

activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:gravity="center">

    <Button
        android:id="@+id/btnPlay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Play"
        android:onClick="onPlay"

         />

    <Button
        android:id="@+id/btnPause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Pause"
        android:onClick="onPausePlayer"/>


    <Button
        android:id="@+id/btnStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Stop"
        android:onClick="onStopPlayer"
        />


res->new resource directory->resource type(raw)->resource name(好きなように)、を作ります。そして、mp3かwavなどの音声ファイルをこのdirectoryにコピペします。
(注意:ファイル名は小文字オンリー)

MainActivity
public class MainActivity extends AppCompatActivity {

    MediaPlayer mp;

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

    public void onPlay(View v){
        /*MediaPlayer consumes too much memory resource
        therefore we only create it when we want to play something
         */
        if (mp==null){
            /*makesure there's no old one before create new one
            we don't want to create too much and use too many resource
             */
            mp= MediaPlayer.create(this,R.raw.globe);
            mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                @Override
                public void onCompletion(MediaPlayer mediaPlayer) {
                    DestroyPlayer();
                }
            });
        }

        mp.start();
    }

    public void onPausePlayer(View v){
        if (mp!=null) mp.pause();

    }

    public void onStopPlayer(View v){
        DestroyPlayer();

    }

    private void DestroyPlayer(){
        if (mp!=null) mp.release();
        mp=null;
        Toast.makeText(this, "Media Player destroyed", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onStop() {
        super.onStop();
        DestroyPlayer();
    }
}

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

AdaptiveIconに対応する

環境

Android Studio 3.5

概要

AdaptiveIconについては下記URLを参照。
https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive?hl=ja

アイコンの表示の違い

4rv4JcAaT-clipboard.png
未対応の場合はアイコンの中央に配置された状態でアプリアイコンが表示されます。

様々な形状に対応

H5vvlsNGi-clipboard.png

画像の用意

使用する画像はベクタ画像がおすすめです。
用意する画像は単色の背景の場合はColorResourceを使うのでアイコンのイラスト部分にあたる画像のみでOKです。
背景に画像を適用したい場合は別途画像が必要です。
ベクター画像の場合のサイズは108x108dpで作成します。
png等を利用する場合はサイズ次第で荒くなるので注意が必要です。
pngで画像を作る場合は背景透過で全体のサイズを432x432px、イラスト部分を288x288pxで作成すると粗さもなくいい感じになります。

アイコンの作成

アイコンの作成はAndroidStudioのImageAssetを利用して作成するのが便利です。
New→ImageAssetで作成ツールが起動します。
NameとLayerNameはデフォルトのままにしておくと既存のアイコンに上書きされるので設定の手間が少なくなります。

アイコン作成手順

SwAvI1x3s-clipboard.png
1. ForegroundLayerタブを開き、前景に配置する画像を設定します。
 Resizeのスライダーで画像のサイズ調整ができるのでガイドからはみ出ていたりする場合はこれで調整しましょう。
V0h5ochlR-clipboard.png
2. 次にBackgroundLayerタブを開き、前景に配置する画像を設定します。
  単色背景の場合はAssetTypeをカラーに、画像を使用する場合はImageを選択します。
  ImageはForegroundLayerと同様にResizeのスライダーで調整可能です。
  調整が終わったらNextをクリックします。
3. 生成される画像が一覧で表示されるので表示に問題ないかチェックしましょう。
  mipmap-anydpi-v26がAndroid8以降のAdaptiveアイコン、それ以外はAndroid8以前のアイコンとして使用されます。
4. 問題なければFinishをクリックします。

アイコンの設定

AndroidManifestのapplicationタグのiconに作成したアイコンのDrawableを入力します。
作成時にデフォルトのままにしていた場合は上書きされるので上記の作業は不要です。
あとはアプリをインストールしてアイコンが設定されているかを確認しましょう。

端末のアイコンの形状の設定変更方法

前提として開発者モードを有効にしておきます。
1. 設定アプリを起動
2. システム→詳細設定→開発者向けオプション→テーマ設定セクションのアイコンの形
3. 任意の形状を選択する
※端末によっては設定方法が異なるので注意

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