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

Retrofitでxmlの値を取得する

Retrofitでxmlの要素を取得するためにEntityを定義しました。
初めて行うと意外に苦戦することが多いので、まとめます。

今回対象のxmlは以下になります。

<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>××××××××××××××</title>
    <link>×××××××××××××××</link>
    <description>××××××××</description>
    <lastBuildDate>××××××</lastBuildDate>

        // itemがリストになっている
        <item>
          <title>××××××××××××</title>
          <link>×××××××××××××</link>          
      <description>×××××××</description>          
     <pubDate>××××××××××××</pubDate>   
        </item>
   </channel>

今回作成したEntityは2種類になります。

BlogDetailEntity
@Root(name = "item", strict = false)
class BlogDetailEntity {

    @set:Element(name = "title", required = false)
    @get:Element(name = "title", required = false)
    var title: String? = null

    @set:Element(name = "link", required = false)
    @get:Element(name = "link", required = false)
    var link: String? = null

    @set:Element(name = "date", required = false)
    @get:Element(name = "date", required = false)
    var date: String? = null

    @set:Element(name = "description", required = false)
    @get:Element(name = "description", required = false)
    var description: String? = null
}

BlogDetailEntityはのリストの詳細を表しています。
@Rootでitemを指定することにより、<item>の子要素を取得しています。
name=でタグの名前を指定することで取得できます。
required=は必須チェックを行っています。
また、strict=falseを指定しないと、全ての要素をEntityに記載しないといけなくなるので、注意です。

BlogEntity
@Root(name= "rss", strict = false)
class BlogEntity {

    @set:Element(name = "title", required = true)
    @get:Element(name = "title", required = true)
    var title: String? = ""

    @set:Element(name = "lastBuildDate", required = true)
    @get:Element(name = "lastBuildDate", required = true)
    var lastBuildDate: String? = ""

    @set:ElementList(entry = "item", inline = true)
    @get:ElementList(entry = "item", inline = true)
    var articleEntities: List<BlogDetailEntity>? = null

    @set:Element(name = "channel")
    @get:Element(name = "channel")
    var channel: String? = ""
}

こちらではrss配下の要素を取得しています。
itemはリスト型でnameではなく、entryで指定しています。

実際にやってみて意外と苦戦しました。
simplexmlのリファレンスを見ながらやるのが一番いいと思います。

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

Android Q・GoogleI/O発表でアプリに影響されると思われるところ

Androidエンジニアの備えが必要そうなところをが話題です。

Android Q Betaが3月だったのに今更ですがね!

セキュリティー

https://developer.android.com/preview/privacy/checklist

Scoped storage

https://developer.android.com/preview/privacy/scoped-storage
アプリターゲットAPIがAndroid Qであればダウンロード、または各メディアフォルダー(音楽、写真など)を接近するための新規パーミッション、APIが追加されます。それ以外の共有フォルダーには接近不可能になります。アプリは独立されたストレージを使わないとダメですね。

More user control over location permissions

https://developer.android.com/preview/privacy/device-location
Android Qの環境で動くデバイスで、アプリがフォアグラウンド状態の時だけ位置情報を確認できるパーミッションが追加されます。バックグラウンドでも位置情報を確認したい場合はユーザーに許可を求めるUXが必要になります。

Background activity starts

https://developer.android.com/preview/privacy/background-activity-starts
Android Qの環境で動くデバイスで、バックグランド状態のアプリは新しいActivityの生成ができません。これはフォアグラウンドサービスでも同じです。
必ずバックグラウンドActivityを生成する場合、FullScreenIntentNotificationを使えばいいです。

https://developer.android.com/reference/android/app/Notification#fullScreenIntent

Non-resettable hardware identifiers

https://developer.android.com/preview/privacy/data-identifiers#device-identifiers
Android Qはユーザーがリセットできない識別子を提供しません。Android Q環境で識別子を確認する場合nullunknownが取れます。
対案としては広告ID、InstanceID、SSAIDがあります。

Permission for wireless scanning

https://developer.android.com/preview/privacy/camera-connectivity#fine-location-telephony-wifi-bt
アプリターゲットAPIがAndroid Qであれば、Wi-Fi、Bluetoothなどユーザーの位置を確認するAPIを使うにはACCESS_FINE_LOCATIONパーミッションが必要になります。

ダークモード

https://developer.android.com/preview/features/darktheme
Android Qのシステム設定でDark themeを選択すれば、DayNight Styleで作られたアプリも自動的にダークモードになります。要するに、Android Qのダークモード設定に合わせたいのであればDayNight Style(またはDayNight継承Style)を使う必要があります。

強制・推奨アップデート

https://developer.android.com/guide/app-bundle/in-app-updates
API21以上、Play Core library1.5.0以上ならばサーポートする強制・推奨アップデート機能です。もうバージョンチェックのために別途のシステムが必要なく、アプリ実装で終わること人なりました!888888
App Bundleはアップデートサイズに150MBの制限があって、obbファイルみたいなAPK拡張ファイルは対応しません。

その他

Bubble

https://developer.android.com/preview/features/bubbles
Android QはSYSTEM_ALERT_WINDOWの許可を出しません。対案としてBubblesというAPIが追加されました。

Android Studio 3.5

Live Class Generationが可能になり、rebuildしなくてもData bindingした変数が確認できます。
そしてdata bindingのrefactoringもサーポートし、
ついにdata bindingのcompile errorが確認できます。

JetPack、AndroidX、そしてKotlin

CameraXのJetPack参加、7年ぶりのViewPagerのアップデートであるViewPager2アルファバージョンはAndroidXが必要です。既存のアプリはそろそろJetPackとAndroidXにマイグレーションする準備が必要があるのかな。
そして今年もKotlin推しは相変わらず。Kotlin関連ライブラリはKTXだけではなく、APIデザインもKotlinにすると発表しました。

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

Android 사진 및 파일 접근 권한 요청

시작하며

앱의 사진 불러오기 기능을 사용하기 위해 사용자에게 파일 접근에 대한 권한 요청을 해야 됩니다.
구글 검색 결과 대부분 AppActivity.java 자체의 소스를 추가하는 방식이었는데, 다행히 cocos2d-x와 연동되는 샘플 소스를 찾아 적용할 수 있었습니다.

앱 시작 시 요청이 아닌 해당 기능 사용 시 요청하는 github 샘플 소스.
-> cocos2d-x game with Android Runtime permission

AppActivity 소스 추가

AppActivity.java - onCreate 밑에 아래의 소스를 추가합니다.

AppActivity.java
    // sample sources
    private static final int PERMISSION_REQUEST_CODE = 9001;
    public static native void Permissioncallback(boolean granted);  

    // 사진 기능을 사용할 때 호출하며 결과를 onRequestPermissionsResult()로 콜백해준다.
    @Keep // Annotation for ProGuard not to delete
    public static void askForPermission() {
        if (!hasPermission()) {
            ActivityCompat.requestPermissions(Cocos2dxHelper.getActivity(),
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                    PERMISSION_REQUEST_CODE);
        }
    }

    // 권한이 허용되어 있는지 여부를 리턴해준다.
    @Keep // Annotation for ProGuard not to delete
    public static boolean hasPermission() {
        boolean result = true;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            result = (ContextCompat.checkSelfPermission(Cocos2dxHelper.getActivity(),
                    Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
        }
        return result;
    }

    // 권한 허용 및 거부의 결과를 cocos2dx 소스로 콜백해준다. 
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case PERMISSION_REQUEST_CODE:
                Permissioncallback(grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED);
                break;
        }
    }

CMakeLists 소스 추가

샘플소스에서 Permission.h, Permission-android.cpp를 프로젝트에 추가합니다.

CMakeLists.txt
# add cross-platforms source files and header files 
list(APPEND GAME_SOURCE
Classes/Permission-android.cpp
)

list(APPEND GAME_HEADER
Classes/Permission.h
)

권한 요청

앱의 사진을 불러오는 부분에서 소스 수정 - 샘플 소스에서 HelloWorld.h/cpp에 해당하는 부분입니다.

        // 포토 읽기 권한 존재할 경우 앨범 오픈.
        if(hasPermission()) // return from Appactivity 
        {
            auto opener = PhotoOpener::createScene();
            opener->setName("opener");
            Director::getInstance()->pushScene(opener);
        }
        else    // 권한이 없을 경우 물어본다.
        {            
            // 허용할 경우 Permissioncallback에서 앨범이 오픈된다.
            // 허용한 다음부터 위 hasPermission에서 true 리턴.
            askForPermission(CC_CALLBACK_1(MenuLayer::Permissioncallback, this )); // ask to Appactivity 
        }

권한 요청에 대한 callback

void MenuLayer::Permissioncallback(bool granted) 
{
    if (granted) // 권한 허용했을 경우 정상적으로 기능을 사용한다.
    {
        //label->setString("granted");
        log("Permissioncallback | granted");

        auto opener = PhotoOpener::createScene();
        opener->setName("opener");
        Director::getInstance()->pushScene(opener);

    } else // 권한 거부일 경우 아무일도 일어나지 않음.
   {        
        //label->setString("denied");
        log("Permissioncallback | denied");
    }
}

main.cpp 수정

java에서 cocos2d-x로 Permissioncallback 호출합니다.
main.PNG

샘플소스에서는 Permissioncallback이 아니라 그냥 callback이었기 때문에 이부분의 수정이 필요해 졌습니다.

main.cpp
#include "Permission.h" // permission add

// called from java
extern "C" {
    JNIEXPORT void JNICALL Java_org_cocos2dx_cpp_AppActivity_Permissioncallback(JNIEnv *env, jclass, jboolean granted) {
        LOGD("Permissioncallback called - granted: %s", granted ? "true" : "false");
        returnFromJava(granted);
    }
}

결과

권한 요청의 팝업을 볼 수 있습니다
IMG_8254.png

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

Android에서 Firebase adMob사용을 위한 SHA인증서 등록

Google Play 앱 서명을 사용

Google Play Console에 앱을 등록할 때 구글 인증서 사용을 활성화해 버렸습니다 - 권장하는 사항이었기 때문에..

Google Play Cosole에 apk를 등록한 후 다운로드 해 테스트 해보니 admob 광고가 나오지 않는 문제가 발생했습니다.
admob test app id로 빌드했을 때는 테스트 광고가 정상적으로 출력됐기 때문에 인증과 관련된 문제일 가능성이 높다고 판단하여 SHA인증서를 등록하기로 했습니다.

Google Play Console - 출시 관리 - 앱서명
cer.PNG

Firebase SHA 인증서 지문 등록

위의 SHA라고 돼있는 지문을 복사해서, Firebase Console의 앱 설정의 지문 추가를 해줍니다
sha.PNG

지문 추가 후 google-services.json 다시 다운로드해서 적용합니다.
json.PNG

어떤 인증서를 추가해야 되는지?

Google Play 앱 서명 방식과 Firebase adMob의 인증에 대해 정확히 어떤 SHA지문을 사용해야 되는지 알수가 없었기 때문에 등록 가능한 4개의 지문을 모두 등록했습니다.
하지만 google-services.json 파일을 보면 추가된 4개의 지문 중 SHA-1 지문 2개(앱 인증서/업로드 인증서)만 등록이 된것을 확인할 수 있었습니다.

결과

등록 후 앱을 테스트했을 때 광고가 여전히 나오지 않아서 인증서 문제인지 다른 문제인지 알 수가 없는 상황이 발생했지만, 몇 번의 빌드 후에 정상적인 광고가 나오는 것을 확인할 수 있었습니다.
(Firebase에 SHA지문을 등록하고 시간이 필요한 것일지도 모른다고 추측합니다)

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

cocos2dx Android에서 설정 파일 사용하기

cocos2dx 3.17.1

ios에서 읽을 수 있었던 .txt파일이 Android에서는 약간의 작업이 필요했습니다.

  • 설정 파일의 사용

게임 시작시 아래의 test.ini(txt와 같음)를 불러들여 정보를 초기화하고 있습니다.
1.PNG

Android에서의 사용 방법입니다.


// 설정 파일에서 스트링 불러오기.
auto configText = FileUtils::getInstance()->getStringFromFile("test.ini"); 

 // 쓰기 가능한 경로 얻기
auto path = FileUtils::getInstance()->getWritablePath();  

// 스트링을 쓰기 가능한 경로에 파일로 저장.
FileUtils::getInstance()->writeStringToFile(configText, path + "test.ini");  

// 저장 됐는지 확인.
if (FileUtils::getInstance()->isFileExist(path + sourceFileName) ) 
{
    log("FileSystem | [%s] exist",(path + sourceFileName).c_str() );
}
else
{
    log("FileSystem | %s not exist",(path + sourceFileName).c_str() );
}

// 저장된 파일의 경로 저장.
m_fullSourceFileName = path + sourceName; 

위의 과정 적용 후, 설정 파일에 접근할 수 있습니다.

사용 예

ConfigINI::getInstance()->init(m_fullSourceFileName);
ConfigINI::getInstance()->getString("USER_MONEY");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cocos2d-x Android에서 설정 파일 사용하기

cocos2d-x 3.17.1

ios에서 읽을 수 있었던 .txt파일이 Android에서는 약간의 작업이 필요했습니다.

  • 설정 파일의 사용

게임 시작시 아래의 test.ini(txt와 같음)를 불러들여 정보를 초기화하고 있습니다.
1.PNG

Android에서의 사용 방법입니다.


// 설정 파일에서 스트링 불러오기.
auto configText = FileUtils::getInstance()->getStringFromFile("test.ini"); 

 // 쓰기 가능한 경로 얻기
auto path = FileUtils::getInstance()->getWritablePath();  

// 스트링을 쓰기 가능한 경로에 파일로 저장.
FileUtils::getInstance()->writeStringToFile(configText, path + "test.ini");  

// 저장 됐는지 확인.
if (FileUtils::getInstance()->isFileExist(path + sourceFileName) ) 
{
    log("FileSystem | [%s] exist",(path + sourceFileName).c_str() );
}
else
{
    log("FileSystem | %s not exist",(path + sourceFileName).c_str() );
}

// 저장된 파일의 경로 저장.
m_fullSourceFileName = path + sourceName; 

위의 과정 적용 후, 설정 파일에 접근할 수 있습니다.

사용 예

ConfigINI::getInstance()->init(m_fullSourceFileName);
ConfigINI::getInstance()->getString("USER_MONEY");
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CameraXについて

CameraX概要

Jetpackライブラリの1つで、既存のカメラAPIであるCamera2を元に作られている。
CameraXはCamera2の難点であった様々な端末のカメラの機能へのアクセスの複雑さを解消し、
シンプルに実装できるようになった。
※Android5.0(API21以上)で利用可能

CameraXの構成

CameraXは以下の3つのユースケースを実装し、カメラ機能にアクセスできる。
この3つのユースケースはLifeCyleにbindすることにより使用でき、
bindしたLifeCycleにより破棄、再生成がされる。

プレビュー

カメラ入力に対するプレビュー機能を提供する

SampleFragment.kt
val previewConfig = PreviewConfig.Builder().apply {
  //どのカメラを使うか
  setLensFacing(CameraX.LensFacing.BACK)
  //解像度の設定
  setTargetResolution(Size(1920, 1080))
  //アスペクト比の設定
  setTargetAsoectRatio(Rational(1920, 1080))
  ....
}.build()
val preview = Preview(previewConfig)
//ライフサイクルにbind
CameraX.bindToLifecycle(this as LifecycleOwner, preview)

解像度やアスペクト比を設定などが端末のスペックの範囲外の時は、
その端末で設定できる一番近しい値が自動で設定される。

画像解析

画像解析用のバッファの設定をする。
画像解析用に必要なピクセルデータを送信することで画像解析のスピードを早くしている。

SampleFragment.kt
val imageAnalysisConfig = ImageAnalysisConfig.Builder().apply {
  //どのカメラを使うか
  setLensFacing(CameraX.LensFacing.BACK)
  //解像度の設定
  setTargetResolution(Size(1920, 1080))
  //アスペクト比の設定
  setTargetAsoectRatio(Rational(1920, 1080))
}.build()

val imageAnalyzer = ImageAnalysis(imageAnalysisConfig).apply {
  //insert your analyze code
}

//ライフサイクルにbind
CameraX.bindToLifecycle(this, imageAnalyzer)

画像キャプチャ

画像の保存の設定をする。

SampleFragment.kt
val imageCaptureConfig = ImageCaptureConfig.Builder().apply {
  //どのカメラを使うか
  setLensFacing(CameraX.LensFacing.BACK)
  //解像度の設定
  setTargetResolution(Size(1920, 1080))
  //アスペクト比の設定
  setTargetAsoectRatio(Rational(1920, 1080))
  //or MAX_QUALITY 速さ優先かクオリティ優先か
  setCaptureMode(CaptureMode.MIN_LATENCY)
  ....
}.build()
val imageCapture = ImageCaputure(imageCaptureConfig)
//ライフサイクルにbind
CameraX.bindToLifecycle(this, imageCapture)

....
//撮影ボタンが押された時
captureBtn.setOnClickListener {
  val fileName = System.currentTimeMillis().toString()
  val fileFormat = ".jpg"
  val imageFile = createTempFile(fileName, fileFormat)

  // Store captured image in the temporary file
  imageCapture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener {
    override fun onImageSaved(file: File) {
      // handle success
    }

    override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
      // handle error
    }
  })
}

撮影する画像の設定を実装したImageCaptureConfigに基づいてImageCaptureクラスを作成し、
ImageCapture内のtakeCapture関数を呼んで撮影する。

まとめ

プレビュー、キャプチャ、解析の3つのユースケースをライブサイクルにbindすることで、
指定したライフサイクルに沿ってカメラを有効にするタイミング、データの生成、破棄を行うことができる。
また、デバイス間の違いを吸収し、同様の機能を備えたカメラアプリを簡潔に実装することができる。

参考

CameraX - Android Developers

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

Androidアプリ(Kotlin)でメニューを作成する方法

Androidアプリ(Kotlin)でメニューを作成する方法

右上にあるメニューの作成方法がよくわからなかったので忘れないようにまとめたメモ

下記みたいなメニューの作成
Screenshot_1561941566.png

メニューリソースの作成

メニューのリソース用xmlを作成
無題.png

main_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:id="@+id/create"
              android:title="@string/create" />
        <item android:id="@+id/delete"
              android:title="@string/delete" />
</menu>

デザインとしては下記の感じになる。
image.png

メニューの埋め込み

Activityに埋め込む

MainActivity.kt
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.widget.AdapterView.AdapterContextMenuInfo


class MainActivity : AppCompatActivity() {

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

    //メニュー表示の為の関数
    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        super.onCreateOptionsMenu(menu)

        val inflater = menuInflater
        //メニューのリソース選択
        inflater.inflate(R.menu.main_menu, menu)
        return true
    }

    //メニューのアイテムを押下した時の処理の関数
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.getItemId()) {
            //作成ボタンを押したとき
            R.id.create -> {
                return true
            }
            //削除ボタンを押したとき
            R.id.delete -> {
                return true
            }
            else -> return super.onOptionsItemSelected(item)
        }
    }
}

↑関数名に一部ミスがあったため修正しました。

完成

右上にメニューボタンがあればOK
Screenshot_1561941570.png

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