きっとラボ

プログラミングや自然科学などの話題がメイン

Android 7.0 (API 24) への対応 ~(外部)ストレージ内ファイルの共有~

今年最初の技術記事は、開発するアプリを「Android N (API 24) 」へ対応させる過程で苦労したことについて書こうと思います。このバージョンで発生したAndroid側の仕様変更の把握を正確にしていかないと既存のアプリが最新バージョンで安定した動作をできなくなります。弊記事の内容の場合、ビルドのターゲットAPIレベルを「24」としたときに問題が発生します。今回、そのあたりでハマって時間を取られるこを防ぐためにも、備忘録を兼ねて共有していきます。

単純に仕様変更点について知りたいだけなら以下を見ていただければすぐに把握できると思いますが、恐らくそれだけだと対応する際ちょっとした問題にハマる可能性があります。(それとも、そんなのは未熟な私だけですかね?)

Android 7.0 の動作の変更点 | Android Developers

FileProvider | Android Developers

 

追記:基本的にはGoogleの技術ブログに載っていた方法を参考にしています。URLは......忘れました _(┐「ε:)_

概要

Android 7.0 は Android N とも呼ばれ、Android の現在の最新バージョンです。ちなみに「N」とはコードネーム「Nougat(ヌガー) 」のことです。砂糖菓子です。

このバージョンになってから、ファイルシステム関連のアプリの権限が一段と厳しい仕様となりました。ドキュメントを読んでいくと、その仕様が技術者にとっては全然甘くないことがわかります。弊記事では、このあたりに絞ってまとめていき、どう対処すればいいのか、まとめていきたいと思います。

 

ファイル共有に関する変更点について

最新のAPIでは、他のアプリとファイルを共有する場合コンテンツプロバイダを介さなければなりません。プライベートファイルのセキュリティを強化するための変更だそうです。では、どのような変更になったのでしょうか。

file://URI の禁止

これまで、URIを使用するときは file://URI を使って処理を実装していました。他のアプリに共有するときなんかはこれをIntentにのせて送信していたわけです。

しかし、これからはこの方法が使えなくなります。

これを使って他のアプリにURIを渡そうとすると FileUriExposedException がトリガーされます。権限を超えてファイルにアクセスしようとしています~と言うような。

なぜそうなるかというと、「URIを受け取る側がそのパスにアクセスする権限を持たない可能性」があるからです。「越権行為は良くない」ということですね!

これからは content://URI

では、どうするか?結論はコンテンツプロバイダのスキーム content:// を使用するということです。実際には ContentProvider のサブクラスである FileProvider を使用します。

今までは Uri.fromFile(File) で取得できるURIを使用していれば良かったのですが、これからは FileProvider.getUriForFile(Context, String, File) でURIを取得します。これにより、当該URIへの一時的なアクセス権限を付与できます。

こうすることにより、共有をされる側のアプリからもそのファイルにアクセスできるわけです。

 

実装するにはどうするのか

ドキュメント側の記述をよく確認せず実装を試みたことにより、悩みました。

FileProvider を使用してファイルを他のアプリへ提供する場合にはいくつかやらなければならない手順があります。とりあえず、内部ストレージ(getFilesDir()で取得できるディレクトリ内の files/ 以下)のファイルを共有する場合を考えます。

1.FileProvider で共有するファイルのディレクトリを事前に指定

事前に指定されたディレクトリを共有することができます。ですから、まず共有するファイルのディレクトリを指定する必要があります。そのための xmlファイルを作成します。

nameには共有するFileProviderの名前を指定します。好きな名前を指定すれば良いと思います。

ここで使えるタグは以下の3種のみです!それぞれで、設定した path を指定できます。

files-path

これは Context.getFilesDir() で取得できる値 (data/data/{applicationId}/files) を指定する場合に使用します。

cache-path

これは getCacheDir() で取得できる値 (data/data/{applicationId}/cache) を指定する場合に使用します。

external-path

これは Environment.getExternalStorageDirectory() で取得できる値 () を指定する場合に使用します。

生成されるURIはどうなっている?

例えば上記の設定の場合、アプリのドメインが com.kit_lab.android とでも仮定すると

URI

content://com.kit_lab.android.fileprovider/share_name/{ファイル名}

物理パスは

com.kit_lab.android/files/directory_name/{ファイル名}

となります!

2.AndroidManifest に FileProviderタグを追加

まず、アプリケーションタグの中に FileProvider の定義を追加します。

内容について説明していきます。

android:authorities属性

ここで、アプリのドメインを元にしたURI権限を設定しています。この値はユニークな値であることが望ましいため、大抵は「{アプリのドメイン}.fileprovider」の様な形を設定していますし、Googleの技術ブログでもその様に紹介されていました。

android:exported属性

FileProvider の公開設定です。これは false としておきます。これをパブリックにする必要はないとのこと。

android:grantUriPermissions属性

ファイルへの一時的なアクセス権限を許可するために true を設定します。

meta-data

ここには、前述の xmlファイルを指定します。

3.FileProviderを使用してURIの取得する

ここでようやくURIの取得です。今までは

としていました。しかしこれからは

こうします。なお、先程説明した ContentProvider に指定できるパスの設定時のタグ3種ですが、あそこで指定できるパス以外や存在しないパスでURIを取得しようとすると、

java.lang.IllegalArgumentException: Failed to find configured root that contains {問題のパス}

という様な例外がトリガーされます。

getUriForFile の第二引数には先のマニフェストで設定した「android:authorities」の値を指定します。

 

ここに潜む罠

さて、以上の方法で得たURIインテントで送信すれば、無事ファイルの共有ができるのですが、SDカード内のファイルを使いたい場合はどうするのでしょうか?

FileProvider に指定するパスの設定時のタグは前述のドキュメントのリンクより、

external-files-path

を指定すれば良さそうです。しかし、これが実際には使えないのです!

 

qiita.com

では、SDカード内のファイルのURIを取得したくなったらどうすれば良いのでしょうか。

 

SDカード内のファイルのURIを取得するには

この場合、キャッシュを作成して共有させることにより、例外を発生させることなくファイルを共有できました。

 

最後に

今回は Android 7.0 での仕様変更の内、ファイルアクセスに関する実装方法について、紹介しましたが、この他にも多くの変更点があり、それにより既存のアプリが影響を受ける可能背が大きいとのことですので、まだちゃんと確認していないという方は一度目を通されたほうが良いです。

また、SDカード内のファイルURI 取得については、使えるディレクトリにキャッシュという方法で対応可能なのがわかりましたが、もっとマシな方法や適切な方法をご存じの方がいらっしゃったら教えていただけたら嬉しいです!

記事に対する意見や質問等もお待ちしております!

とりあえず、これでファイルをSNSやメールで共有できそうな感じです。

 

では次回投稿をお楽しみに!

 

 

 

改定:04/02/2017

年始の挨拶

新年おめでとうございます

 2017年になりました。挨拶が遅れましたが、あけましておめでとうございます。昨年秋からこのブログを始めて、5ヶ月位になりましたが、諸事情によりほとんど更新していませんでした。

 今年は、個人的に興味のあったことについての研究開発を沢山進めていきたいですし、そのあたりについても可能な範囲で紹介をしていこうと考えております。

 科学技術分野をメインに、今年はしっかりと更新をしていきたいと思っておりますので、よろしくお願い致します。

iOS 10の不具合か? カメラ・スクショの撮影時、シャッター音を消す設定ができる件

  • まえがき
  • 早速設定をしてみる
  • 結果は
  • まとめ

まえがき

 既にご存じの方も多いかと思いますが、最近、iOS 10においてカメラやスクショ撮影時のシャッター音が消せることが話題に上がっていました。そこで、弊ブログの管理人も実際に手元のiPhoneのOSを10にアップデートして確認してみました。

 なお、先に確認されていた動作不良の不具合については解決しているとのことだったので安心して作業に入れました。

 また、OSのアップデートは問題なく30分程で完了できました。

続きを読む

Unityで覚えるデザインパターン 1 Compositeパターン

趣旨

 Unityとは数あるゲームエンジンの中の1つです。

 ここ数年、Unityという単語を目にしない日が無いくらいに世に浸透してきましたね。このUnityのおかげで、今までゲーム開発をしたことのなかった人でも、あるいはプログラミング自体未経験の人でも、気軽に挑戦できるようになりました。

 そういった中で、私も二年前よりUnityを使うようになったのですが、その頃の私はクラス設計について大して学んだことが無く、仕事でも大変な思いをしました。元々プログラミングを独学で学んでいた上に、基礎をしっかり身につけずにやっていたのがいけなかったです。

 Unityを使ってごく小規模なアプリを作りつつ、デザインパターンに関する私自身の備忘録代わりにするのが主軸になりそうですが、Unityをきっかけにプログラミングをはじめた人にもクラス設計を考える手助けになれば幸いです。

デザインパターンとは

 オブジェクト指向での設計における定石をパターンとして一般化したものです。コードを再利用できる資産として残したり、仕様変更を容易にする上でとても大事な考え方です。

 大抵は「Gofの23のデザインパターン」というのを指しますが、ここではそれに限らず、幅広く見ていきたいと思います。(とはいえ、最初は主にGofパターンとなるかと思います)

 

 Composite パターン

 初めてとなる本記事では、Compositeパターンについて触れたいと思います。

 概要

  構造に関するパターンの1つ。ディレクトリとファイルの関係のような、階層構造をなす再帰的なデータ構造を設計する際に用いられます。

 ファイルはディレクトリの中に格納されますが、ディレクトリもまた別のディレクトリの中に格納できます。つまり、フィルとディレクトリはどちらもディレクトリの子要素になれるのです。このように、ディレクトリとファイルを「同一視」してデータ構造を扱うのがCompositeパターンです。

今回作るもの

 簡易的なインベントリ。アイテムディレクトリがあって、その中に消費アイテムと装備アイテムが入っている構造を作ってみます。

作るクラス

  • Entry.cs
  • GameItemDirectory.cs
  • GameItemFile.cs
  • CompositePatternTest.cs

クラス説明

Entry.cs

 アイテムとアイテムディレクトリの抽象クラスです。

gist.github.com

GameItemDirectory.cs

 アイテムディレクトリを表すクラスです。Entry.csを継承しています。ディレクトリには子要素が入りますのでそのためのListをフィールドに持っています。また、ディレクトリには要素を追加する機能が必要ですので、16行目にそのためのメソッドを実装しています。

gist.github.com

 GameItemFile.cs

 アイテムを表すクラスです。Entry.csを継承しています。今回は特に機能をつけておりません。

gist.github.com

CompositePatternTest.cs

 動作確認用のクラスです。今回はただデータ構造のログを出すだけにとどまっています。

gist.github.com

 動作の確認

 以上のクラスを作成後、Hierarchy上に空のGameObjectを作成しそこにCompositePatternTest.csをアタッチします。

 

f:id:krita_sakutai:20160908011200p:plain

 これを実行すると以下の様なログが得られると思います。

f:id:krita_sakutai:20160908010307p:plain

 

 最後に

 今回、アイテムディレクトリとその中のアイテムを例にCompositeパターンについて紹介してみましたが、ではこれでゲームのインベントリが実装できるかといえばできます。しかし、その場合は更に他のデザイパターンを実装するのが良さそうです。それは「Visiterパターン」というものです。

 特にゲームのインベントリというものは、大抵その構造は決まっていますので、このパターンが有用なのではないかと思っています。

 このパターンは、データ構造とそれらに対する処理を分けて実装するというものです。これについては、また別の機会に紹介したいと思います。

注意事項

 この記事の内容は、私が学習したことを自分なりに理解して実装したものです。そのため一部正確性に欠けるかもしれません。何かお気づきの点や気になる点がありましたら、コメントいただけると嬉しいです。

 

参考書籍

 こちらの書籍は、Java言語でのデザインパターンの解説をされていますが、とてもわかり易くまとめられていますので、大変参考になりました。

 

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

 

 

このブログについて

このブログについて

 このブログでは、プログラミング(主にUnity?)関連や自然科学系のコンテンツがメインとなります。時々別な話題も取り上げることがあるかもしれません。

 

免責事項

弊ブログの閲覧、利用および当ブログ上のコンテンツからリンクされている第三者のサイトの閲覧、サービスの利用については利用者の責任において行ってくだ さい。弊ブログ及び第三者のサイトを利用したことにより発生した、いかなる損失・損害についても弊ブログの管理人は一切の責任や義務を負いません。 ご注意ください。

ブログの始まり

はじめまして

この度、ブログ「きっとラボ」を始めてみました。

プログラミング関連(主にUnityだと思います)や自然科学などについて連々と投稿していくつもりです。時々脱線することもあると思います。

1日1記事できれば良いんですが、なんか既に挫折しそうな気がしています。

こんな者ですが、これから、どうぞよろしくお願いします!