- 投稿日:2021-05-16T20:09:14+09:00
FTS(全文検索)についてググったので、KotlinでNgram法の文字列分割を実装してみた
AndroidのSearchViewを使った検索機能について調べていると、出くわす「FTS(全文検索)」という単語。 この単語についてあまり理解していなかったので、調べてみました。 先人の方々が詳しくまとめてくれていました。特に参考になったのは、以下の記事です。 ・Wikipedia「全文検索」 上記のページを見れば、全文検索の概要は把握することができます。ありがたい限りです。 FTS(全文検索)とは 「Full text search」の頭文字を取った語。 複数の文書にまたがって、文書に含まれる単語の検索を行う。 FTS(全文検索)はLIKEのようなパターンベースの検索とは異なり、単語ベースの検索を行う。 全文検索技術の分類 大きく「grep型」と「索引型」の2つの方法に分かれるようです。 grep型 複数のテキストファイルの内容を順次走査していくことで、検索対象となる文字列を探し出す方法。 この方法は事前にインデックスを作成しないため、検索対象が多くなるほど検索速度が低下する。 索引型 上記のgrep型に対して、こちらは予め検索対象にインデックスを作成しておくことで、検索速度の向上を図る。 AndroidでSQLiteによる全文検索を行う場合、こちらの方法を使用する。 索引文字列の抽出方法としては、「形態素解析」や「N-Gram」という方法がよく使われるようです。 N-Gram法とは 検索対象を単語単位ではなく文字単位で分解し、後続の N-1 文字を含めた状態で出現頻度を求める方法をN-Gram法という。 たとえば、Nの値が2の時、「私は太郎です。」という文字列は「私は」 「は太」 「太郎」 「郎で」 「です」 「す。」と分割されます。 形態素解析を実施するには、文脈や単語の解析をするための辞書が必要なため、お手軽に試すのは難しそうですが、N-Gram法であれば、一定の規則で文字列を分割するだけなのでお手軽に試せそうです。 ということで、今回はこのN-Gram法による文字列分割をKotlinで実装してみました。 以下がその実装です。 ソースコード object FtsUtils { const val DEFAULT_SEPARATOR: String = " " const val DEFAULT_GRAM: Int = 2 fun ngram(src: String, n: Int = DEFAULT_GRAM) : List<String> { return if (n <= src.length) { listOf(src.take(n)) + ngram(src.drop(1), n) } else { emptyList() } } fun toNgramText(src: String, n: Int = DEFAULT_GRAM, separatedBy: String = DEFAULT_SEPARATOR) : String = ngram(src, n).foldRight(initial = "", operation = {s, acc -> s + separatedBy + acc }) } fun main() { println(FtsUtils.toNgramText("私は太郎です。")) } 上記のコードを実行した結果は以下です。 実行結果 私は は太 太郎 郎で です す。 ちゃんと動いていそうです。 また、時間のあるときにAndroidアプリでFTSを使った検索機能を実装してみたいです。
- 投稿日:2021-05-16T20:09:14+09:00
FTS(全文検索)について調べたので、KotlinでNgram法の文字列分割を実装してみた
AndroidのSearchViewを使った検索機能について調べていると、出くわす「FTS(全文検索)」という単語。 この単語についてあまり理解していなかったので、調べてみました。 先人の方々が詳しくまとめてくれていました。特に参考になったのは、以下の記事です。 ・Wikipedia「全文検索」 上記のページを見れば、全文検索の概要は把握することができます。ありがたい限りです。 FTS(全文検索)とは 「Full text search」の頭文字を取った語。 複数の文書にまたがって、文書に含まれる単語の検索を行う。 FTS(全文検索)はLIKEのようなパターンベースの検索とは異なり、単語ベースの検索を行う。 全文検索技術の分類 大きく「grep型」と「索引型」の2つの方法に分かれるようです。 grep型 複数のテキストファイルの内容を順次走査していくことで、検索対象となる文字列を探し出す方法。 この方法は事前にインデックスを作成しないため、検索対象が多くなるほど検索速度が低下する。 索引型 上記のgrep型に対して、こちらは予め検索対象にインデックスを作成しておくことで、検索速度の向上を図る。 AndroidでSQLiteによる全文検索を行う場合、こちらの方法を使用する。 索引文字列の抽出方法としては、「形態素解析」や「N-Gram」という方法がよく使われるようです。 N-Gram法とは 検索対象を単語単位ではなく文字単位で分解し、後続の N-1 文字を含めた状態で出現頻度を求める方法をN-Gram法という。 たとえば、Nの値が2の時、「私は太郎です。」という文字列は「私は」 「は太」 「太郎」 「郎で」 「です」 「す。」と分割されます。 形態素解析を実施するには、文脈や単語の解析をするための辞書が必要なため、お手軽に試すのは難しそうですが、N-Gram法であれば、一定の規則で文字列を分割するだけなのでお手軽に試せそうです。 ということで、今回はこのN-Gram法による文字列分割をKotlinで実装してみました。 以下がその実装です。 ソースコード object FtsUtils { const val DEFAULT_SEPARATOR: String = " " const val DEFAULT_GRAM: Int = 2 fun ngram(src: String, n: Int = DEFAULT_GRAM) : List<String> { return if (n <= src.length) { listOf(src.take(n)) + ngram(src.drop(1), n) } else { emptyList() } } fun toNgramText(src: String, n: Int = DEFAULT_GRAM, separatedBy: String = DEFAULT_SEPARATOR) : String = ngram(src, n).foldRight(initial = "", operation = {s, acc -> s + separatedBy + acc }) } fun main() { println(FtsUtils.toNgramText("私は太郎です。")) } 上記のコードを実行した結果は以下です。 実行結果 私は は太 太郎 郎で です す。 ちゃんと動いていそうです。 また、時間のあるときにAndroidアプリでFTSを使った検索機能を実装してみようと思います。
- 投稿日:2021-05-16T14:51:01+09:00
PicassoとGoogleAPIでfaviconを取得する
はじめに URLを取得したときにわかりやすくアイコンを取得したかった WebViewを使って取得するのは難しかった。 かんたんに完結にfaviconを取得したかった。 というわけで、PicassoとGoogleAPIを組み合わせてfaviconを取得しよう!! 準備(Gradle モジュール) Picasaを使用するために、Gradleに以下の文を加筆する。 dependencies { implementation 'com.squareup.picasso:picasso:2.71828' } 準備(AndroidManifest) インターネットに接続して、画像を取得するため、ユーザーパーミッションの以下の文も加筆しておく。 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> URLからGoogleAPIを使用して、faviconを取得する。 正式名称がわからないため、GoogleAPIであっているかもわかりませんが、このURLを使用すれば、faviconを取得できるらしい。 https://www.google.com/s2/favicons?domain=youtube.com https://www.google.com/s2/favicons?domain={ドメイン} ドメインの部分を変えればそのサイトのfaviconがPNG形式で取得できる Picasaを使って画像を取得する。 picasaを使用して、画像を取得することができる。 //MainActivity.kt //OnCreate内 val domein = youtube.com Picasso.get() //画像が乗っているURL .load("https://www.google.com/s2/favicons?domain=$domein") .resize(300, 300) //表示サイズ指定 .centerCrop() //resizeで指定した範囲になるよう中央から切り出し .into(findViewById<ImageView>(R.id.imageView)) //imageViewに流し込み URLからドメインを取得する方法。 今回色々調べてみて、ドメインを取得するだけなのに、色々とめんどくさそうなので、自分で作ってみた。 クラス自体は長くなってしまっているところもあるので、そのあたりはご愛嬌ということで。 詳しくは下のURLをご参照ください。 ソースコード package それぞれ合わせてください。 import android.os.Bundle import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.squareup.picasso.Picasso class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //URLの部分 var text = "https://twitter.com/home" findViewById<TextView>(R.id.textView).text = text //URLのドメイン部分の抽出。 (自作クラス) val domein = UrlDomein().hen(text) findViewById<TextView>(R.id.textView2).text = domein //ドメインから、faviconを取得 Picasso.get() //画像が乗っているURL .load("https://www.google.com/s2/favicons?domain=$domein") .resize(300, 300) //表示サイズ指定 .centerCrop() //resizeで指定した範囲になるよう中央から切り出し .into(findViewById<ImageView>(R.id.imageView)) //imageViewに流し込み } } まとめ 今回faviconのAndroidでの取得の仕方をまとめてみました。 利点としてはとても簡潔なプログラムで完成してよかったです。 難点としては取得した画像の画質が悪いことですかね。 参考文献 github 今回部分部分で紹介していましたのでしっかりとプロジェクトとして作られているものも作りました。 ぜひ使ってみてください。
- 投稿日:2021-05-16T14:29:35+09:00
URLからドメイン部分を取得する方法。(手作り)
はじめに 制作途中のメモとして投稿してます。間違っているところ等ありましたらご指摘等よろしくおねがいします。m(._.)m オネガイ 今回色々調べてみて、ドメインを取得するだけなのに、色々とめんどくさそうなので、自分で作ってみた。 クラス自体は長くなってしまっているところもあるので、そのあたりはご愛嬌ということで。 使い方 val text = "https://twitter.com/home" //中に入ったURL(String)をドメインのみの文字型に変更する。 val domein = UrlDomein().hen(text) //出力「twitter.com」 //中に入ったURL(String)がURLかどうか判断をする。 val check = UrlDomein().check(text) //出力「true」 中身のソース package 各個人で合わせてください。 class UrlDomein{ fun hen(moto:String): String { var httpsCheck = "" var domein = "" var surassyu = "" var charAry = moto.toCharArray() for (ch in charAry){ when (ch){ 'h' -> { if(httpsCheck == ""){ httpsCheck += ch }else{ if(surassyu == "//"){ domein += ch } } } 't' ->{ if(httpsCheck == "h"){ httpsCheck += ch }else if(httpsCheck == "ht") { httpsCheck += ch }else{ if(surassyu == "//"){ domein += ch } } } 'p' ->{ if(httpsCheck == "htt"){ httpsCheck += ch }else{ if(surassyu == "//"){ domein += ch } } } 's' ->{ if(httpsCheck == "http" && surassyu ==""){ httpsCheck += ch }else{ if(surassyu == "//"){ domein += ch } } } '/' ->{ if((httpsCheck == "https" || httpsCheck == "http") && (surassyu == "" || surassyu == "/")){ surassyu += ch }else{ if(surassyu == "//"){ break } } } ':' ->{} else ->{ if(httpsCheck == "https" || httpsCheck == "http"){ domein += ch }else{ } } } // https://www.google.com/s2/favicons?domain= } return domein } fun check(moto: String): Boolean { var httpsCheck = "" var domein = "" var surassyu = "" val charAry = moto.toCharArray() for (ch in charAry){ when (ch){ 'h' -> { if(httpsCheck == ""){ httpsCheck += ch } } 't' ->{ if(httpsCheck == "h"){ httpsCheck += ch }else if(httpsCheck == "ht") { httpsCheck += ch } } 'p' ->{ if(httpsCheck == "htt"){ httpsCheck += ch } } 's' ->{ if(httpsCheck == "http" && surassyu ==""){ httpsCheck += ch } } '/' ->{ if((httpsCheck == "https" || httpsCheck == "http") && (surassyu == "" )){ surassyu += ch }else if(surassyu == "/"){ surassyu += ch break } } ':' ->{} else ->{ } } // https://www.google.com/s2/favicons?domain= } return (httpsCheck == "https" || httpsCheck == "http") && (surassyu == "//" ) } } github Androidで作成しました、もしよろしければ使ってみてください。
- 投稿日:2021-05-16T13:56:21+09:00
備忘録 flutter開発で音声入力したいけど speech_to_text がうまく作動しない。
課題 下記リファレンスを参考にspeech_to_text でマイクから音声を拾って文章に文字起こしをしたいがうまく作動しない。 下記のようなコードを組んで問題点を調査したところ"The user has denied the use of speech recognition."が呼ばれた。どうやらspeech_to_textをインスタンス化するところで失敗している模様。 var hasSpeech = await speech.initialize( onError: errorListener, onStatus: statusListener, debugLogging: true, finalTimeout: Duration(milliseconds: 0)); if (hasSpeech) { _localeNames = await speech.locales(); print('_localeNames'); print(_localeNames); var systemLocale = await speech.systemLocale(); _currentLocaleId = systemLocale.localeId; speech.listen( onResult: resultListener ); } else { print("The user has denied the use of speech recognition."); } 解決策 app/src/main の Android Manifest に uses-permissionのタグ二つとqueriesを追加する。 これによってアプリ起動時にマイクへのアクセスが求められる様になり、音声入力が可能となる。 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.higuuu.job_interview"> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.INTERNET" /> <queries> <intent> <action android:name="android.speech.RecognitionService" /> </intent> </queries>
- 投稿日:2021-05-16T12:02:25+09:00
CircleCIのSlack orb 4.4の一番シンプルな使い方
以下の記事にて、AndroidアプリのCIにCircleCIを使った例の記事を書きました。 https://qiita.com/kasa_le/items/fab191326b42f5adb82d Slack orbがかなりバージョンアップしていて変わっていたので対応してみました。 環境など ビルドしているのはAndroidアプリですが、Slack orbを使っている部分はどんなプロジェクトでも参考になるかと思います。 ツールなど バージョンなど Circle CI 2.0 Docker size Medium Slack orb 4.4 注意事項 本記事の内容は、Slack Orb 4.4で動きます。 4.1や4.1.1では動かないので注意してください。理由は分かりませんが、私も動かなくてハマっていました。 Slack Appの作り方 基本的には以下のページの通りです。 Create a Context on CircleCIのセクションにある内容は、Organizationで環境を共有したい場合以外は、Project SettingsのEnvironment variablesで追加すればOKです。 Slack orbの使い方 コマンドの設定 一番シンプルな使い方は、テンプレートをそのまま使うことだと思います。 テンプレートを使ったときの見た目は、以下のページで確認出来ます。 config.yaml version: 2.1 orbs: android: circleci/android@0.2.0 slack: circleci/slack@4.4 commands: notify_slack_pass: steps: - slack/notify: event: pass template: basic_success_1 notify_slack_fail: steps: - slack/notify: event: fail template: basic_fail_1 成功時はテンプレートbasic_success_1を使い、失敗時はbasic_fail_1を使うコマンドをこんな風に用意しておくと、後でカスタマイズしたときにも1箇所に固まっていて見やすいです。 Jobの最後に使う 各ジョブの最後にそれぞれ呼びます。 複数のジョブを実行する場合で順列に実行し、途中の成功は通知が不要の場合の例になります。 config.yaml jobs: debug-build-test: executor: android/android steps: - checkout - run: name: Debug build command: ./gradlew assembleDebug - notify_slack_fail release-build: executor: android/android steps: - checkout - run: name: Release build command: ./gradlew assembleRelease - notify_slack_fail - notify_slack_pass この後のワークフロー設定で出てきますが、debug-buildのあとにrelease-buildジョブを呼ぶので、debug-buildは失敗したときだけ通知しています。成功したときは通知しません。 release-buildは、失敗または成功したときどちらかが通知されます。 ワークフローの設定 config.yaml workflows: version: 2 build-and-test: jobs: - debug-build-test - release-build: requires: - debug-build-test 始めにdebug-buildジョブを実行し、成功したらrelase-buildが実行されます。 通知結果 それぞれの実際の通知です。 成功時 失敗時 パラメータmentionsを指定してないのですが、項目がなくなるわけじゃ無くて空白になるだけみたいですね。 configファイルの全容 以下で、実際のconfigファイルの全容が見られます。 感想など 正直以前使っていたバージョンよりずっと使いやすくなった気がします。 が、orbの公式ページのexampleがorb4.1だったりして、そのままコピペしていると動かないという罠があり、何時間も費やしてしまいました・・・ 最終的に、以下のIssuesで同じ現象の方のコメントを読み、4.4なら動くと書いてあってそれで上げてみたら何のことは無く動作したというオチでした。
- 投稿日:2021-05-16T11:34:43+09:00
FlutterでAdMobを表示(iOS/Android)
はじめに 今回はAdMobの公式パッケージを使って広告を表示できるようにします。 更新履歴 2021.5.16 初回投稿 環境 macOS Big Sur(11.2.3) Flutter (Channel stable, 2.0.6, on macOS 11.2.3 20D91 darwin-x64, locale ja-JP) Android toolchain - develop for Android devices (Android SDK version 30.0.3) Xcode - develop for iOS and macOS Chrome - develop for the web Android Studio (version 4.1) VS Code (version 1.56.0) 参考にしたサイト flutterアプリでfirebase_admobからgoogle_mobile_adsに切り替えてみた - Qiita package google_mobile_ads | Flutter Package やりたいこと iOS/AndroidでAdMobの広告を表示させたい。 WebではAdMob表示できないので一旦、無視 AdMobテスト用ID アプリID Android: ca-app-pub-3940256099942544~3347511713 iOS: ca-app-pub-3940256099942544~1458002511 広告ユニットID Android: ca-app-pub-3940256099942544/6300978111 iOS: ca-app-pub-3940256099942544/2934735716 なお、admobのプラグインにて、定数定義がありますので、 FirebaseAdMob.testAppIdやBannerAd.testAdUnitIdのように利用することも可能なようです。(試してません) 実装手順 Flutterでプロジェクト作成 null safetyにするため、pubspec.yamlのsdkを以下のように変更 pubspec.yaml environment: sdk: ">=2.12.0 <3.0.0" パッケージをpubspec.yamlに追加してインストール pubspec.yaml google_mobile_ads: ^0.12.2 iOSの設定 iOS/Runner/info.plistに以下を追加 info.plist <key>GADApplicationIdentifier</key> <string>ca-app-pub-3940256099942544~1458002511</string> 「ca-app-pub-3940256099942544~1458002511」はテスト用アプリIDとなりますので、本番の際はこちらを自分のアプリのアプリIDに変更してください。 Androidの設定 最小SDKバージョンの変更 android/app/build.gradle minSdkVersion 19 //19以上に変更 android/app/src/main/AndroidManifest.xmlに以下を追加 AndroidManifest.xml <meta-data android:name="flutterEmbedding" android:value="2" /> <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713" /> 「ca-app-pub-3940256099942544~3347511713」はテスト用アプリIDとなりますので、本番の際はこちらを自分のアプリのアプリIDに変更してください。 5.管理クラスの作成(参考サイトのソースコードのままですthx) ad_banner.dart import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; import 'dart:io'; class AdBanner extends StatelessWidget { const AdBanner({ required this.size, }); final AdSize size; @override Widget build(BuildContext context) { final banner = BannerAd( size: size, adUnitId: BannerAd.testAdUnitId, listener: AdListener( onAdLoaded: (Ad ad) => print('Ad loaded.'), onAdFailedToLoad: (Ad ad, LoadAdError error) { print('Ad failed to load: $error'); }, onAdOpened: (Ad ad) => print('Ad opened.'), onAdClosed: (Ad ad) => print('Ad closed.'), onApplicationExit: (Ad ad) => print('Left application.'), ), request: AdRequest()) ..load(); return Container( width: banner.size.width.toDouble(), height: banner.size.height.toDouble(), child: AdWidget(ad: banner)); } // 広告ID static String get bannerAdUnitId { if (Platform.isAndroid) { return "ca-app-pub-3940256099942544/6300978111"; } else if (Platform.isIOS) { return "ca-app-pub-3940256099942544/2934735716"; } else { //どちらでもない場合は、テスト用を返す return BannerAd.testAdUnitId; } } } 6.実装 main.dart import 'package:flutter/material.dart'; import 'ad_banner.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); MobileAds.instance.initialize(); runApp(MyApp()); } class MyApp extends StatelessWidget { // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Admob Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('AdMob Demo'), ), body: Center( child: AdBanner(size: AdSize.banner), ), ); } } 7.動作確認 広告のサイズ AdSize width height banner 320 50 largeBanner 320 100 mediumRectangle 300 250 fullBanner 468 60 leaderboard 728 90 以上
- 投稿日:2021-05-16T04:05:44+09:00
[Flutter] Gitリポジトリからダウンロードしたプログラムでエラー
はじめに Windowsで作成したFlutterアプリをGitリポジトリを通してMacにダウンロード。 それを起動させようとしたらエラーが出てきた。 Firebase関連のプラグインのバージョンを変えたりして、試行錯誤していたら余計にハマった。 バージョンがあってないやら、ファイルがみつからないやら、、、、、 あまりにも適当にやりすぎてエラーを再現できないが、備忘録としてエラー回避できた方法をいくつか残す。 エラー内容 Error output from CocoaPods: ↳ [!] Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`. Exception: Error running pod install 対策① 1.ダウンロードしたプログラムファイルのプログラム名/ios/Runner.xcworkspaceをxcodeで開く 2.赤枠で囲んだ部分のバージョンをiOS 13.0に変更 3.xcodeを保存して閉じる 4.flutter run 対策② 1.flutter clean 2.flutter build 対策③ 1.flutter clean 2.flutter pub get 3.cd ios 4.pod install 5.pod repo update 6.pod update 7.cd .. 8.flutter run 対策④ 1.対策①の3までやる 2.対策③を全部やる 参考URL https://github.com/FirebaseExtended/flutterfire/issues/1979 https://dolphinetech.com/flutter/error-running-pod-install/ https://fluttercorner.com/solved-error-could-not-find-included-file-generated-xcconfig-in-search-paths-in-target-runner/ 最後に エラーが出るとどうしてもあわわなる、、、 落ち着いてエラー内容をちゃんと読めば何とかなるさ! 最初からMacで作ればいいじゃない
- 投稿日:2021-05-16T00:45:34+09:00
Termuxでpkg(apt)が使えない問題を解決する(2021年5月)
初投稿です。Android端末にTermuxを入れて遊んでいるのですが,最近pkg updateやpkg installを実行すると403 Forbiddenと表示され,パッケージのアップデートやインストールができなくなっていることに気づきました。調べてみたところ解決できたので記事にしてみることにしました。 問題 Termuxでpkg updateを実行すると下記のようにエラーが発生する。 $ pkg update Checking availability of current mirror: ok Hit:1 https://grimler.se/termux-packages-24 stable InRelease Ign:2 https://dl.bintray.com/grimler/game-packages-24 games InRelease Ign:3 https://dl.bintray.com/grimler/science-packages-24 science InRelease Err:4 https://dl.bintray.com/grimler/game-packages-24 games Release 403 Forbidden Err:5 https://dl.bintray.com/grimler/science-packages-24 science Release 403 Forbidden Reading package lists... Done E: The repository 'https://dl.bintray.com/grimler/game-packages-24 games Release' does not have a Release file. N: Metadata integrity can't be verified, repository is disabled now. N: Possible cause: repository is under maintenance or down (wrong sources.list URL?). E: The repository 'https://dl.bintray.com/grimler/science-packages-24 science Release' does not have a Release file. N: Metadata integrity can't be verified, repository is disabled now. N: Possible cause: repository is under maintenance or down (wrong sources.list URL?). なお,pkg install ○○などでも同様でした。 原因 色々と調べていると,以下の記事を発見しました。 どうやら,5月1日から今までのリポジトリが使えなくなってしまったのが原因のようです。 解決方法 上記のサイトに書かれているとおりです。 ①termux-change-repoを実行する。 ②Main repository,Game repository,Science repositoryをすべて選択する。 ③Mirrors by Grimlerを選択する。 これでpkgが使えるようになりました。 補足 これについて調べているときに知ったのですが,Google Playで配布されているTermuxは技術的背景の問題で2020年9月29日を最後に更新されていないようです(詳細は以下のサイトに書かれています)。 どうやら,以下のF-Droidというサイトで配布されているのが最新版のようです。 この記事を執筆した時点での最新版は0.112で,2021年4月26日に更新されたものでした。 最新版をインストールしてみたところ,pkgコマンドは最初から問題なく動いたので,上記の問題が発生するのはGoogle Playで配布されている古いバージョンのみのようです。