Unity Android で FileProvider を使用する
Android では、FileProvider を使って他のアプリから自分のアプリ内のファイルへ読み取り権限を与えることができる。それを Unity でやる。
概要
- Twitter アプリにスクリーンショット画像ファイルの読み取り権限を与えるために、FileProvider を使う
- res ディレクトリをビルドに含めるために、project.properties ファイルを使って Unity に Android Library として認識させる
環境
- Android 6.0.1
- Unity 5.3.1p3
手順
スクリーンショットを撮る
Application.CaptureScreenshot
で撮る。
FileProvider を定義する
に従って定義すれば良い。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mygame">
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.mygame.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
res/xml/filepaths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
<files-path path="shared_images/" name="images" />
</paths>
FileProvider を使う
FileProvider を使って、共有可能な URI を取得して、それを Intent に渡す。さらに、URI に読み取り権限を与える必要がある。
詳しくは、FileProvider ドキュメントの「Granting Temporary Permissions to a URI」の項を読む。今回は Intent.setFlags
することにした。
DDDTwitter.java
package com.kakeragames.dddtwitter;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.content.FileProvider;
import com.unity3d.player.UnityPlayer;
import java.io.File;
import java.io.IOException;
public class DDDTwitter {
private static final String SHARED_IMAGES_PATH = "shared_images";
private static final String SHARED_IMAGES_AUTHORITY = "com.example.mygame.fileprovider";
public void post(String message, String imagePath) {
Activity currentActivity = UnityPlayer.currentActivity;
IntentBuilder intentBuilder = new IntentBuilder();
intentBuilder.setMessage(message);
if (imagePath != null && !imagePath.isEmpty()) {
FileSharer sharer = new FileSharer(currentActivity);
try {
File sharedImage = sharer.share(new File(imagePath), SHARED_IMAGES_PATH);
Uri sharedImageUri = FileProvider.getUriForFile(currentActivity, SHARED_IMAGES_AUTHORITY, sharedImage);
intentBuilder.setImage(sharedImageUri);
} catch (IOException e) {
e.printStackTrace();
}
}
Intent tweet = intentBuilder.build();
tweet.setPackage("com.twitter.android");
if (tweet.resolveActivity(currentActivity.getPackageManager()) != null) {
currentActivity.startActivity(tweet);
return;
}
Intent share = intentBuilder.build();
currentActivity.startActivity(share);
}
}
IntentBuilder
Intent は Builder のクラスに作らせてみた。setXxxxx
するものがそんなに無いので、あんまり効いた感じがしなくもない。
IntentBuilder.java
package com.kakeragames.dddtwitter;
import android.content.Intent;
import android.net.Uri;
public class IntentBuilder {
private String mMessage;
private Uri mImageUri;
public IntentBuilder setMessage(String message) {
mMessage = message;
return this;
}
public IntentBuilder setImage(Uri imageUri) {
mImageUri = imageUri;
return this;
}
public Intent build() {
Intent intent = new Intent(Intent.ACTION_SEND);
if (mImageUri != null) {
intent.setType("image/png");
intent.putExtra(Intent.EXTRA_STREAM, mImageUri);
// For images shared with FileProvider
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setType("text/plain");
}
intent.putExtra(Intent.EXTRA_TEXT, mMessage);
return intent;
}
}
共有ディレクトリ以外のファイルへの対応
DDDTwitter.post
の imagePath
に、共有ディレクトリ(shared_images
)以外のファイルが投げられても良いように、
ファイルを共有ディレクトリ内にコピーするようにした。
ファイル操作するなら Commons IO - Commons IO Overview を使いたいところではあるけど、 依存する jar が増えるのと、そもそもコード量が少ないことから、今回は使わなかった。
FileSharer.java
package com.kakeragames.dddtwitter;
import android.content.Context;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class FileSharer {
private Context mContext;
public FileSharer(Context context) {
mContext = context;
}
public File share(File original, String sharedPath) throws IOException {
File sharedDirectory = new File(mContext.getFilesDir(), sharedPath);
sharedDirectory.mkdirs();
File sharedFile = new File(sharedDirectory, original.getName());
FileSharer.copy(original, sharedFile);
return sharedFile;
}
private static void copy(File from, File to) throws IOException {
FileChannel input = null;
FileChannel output = null;
try {
input = new FileInputStream(from).getChannel();
output = new FileOutputStream(to).getChannel();
output.transferFrom(input, 0, input.size());
} finally {
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
}
Unity に取り込む
Assets/Plugins/Android/DDDTwitter/AndroidManifest.xml
Assets/Plugins/Android/DDDTwitter/libs/dddtwitter.jar
Assets/Plugins/Android/DDDTwitter/project.properties
Assets/Plugins/Android/DDDTwitter/res/xml/filepaths.xml
Assets/Plugins/Android/android-support-v4.jar
という構成で取り込む。
注意点 1: Android Library の定義ファイルを追加する
project.properties
android.library=true
というファイルを追加する必要がある。これがないと、Unity が DDDTwitter を android-libraries として認識してくれない。
- Unity - Manual: Building Plugins for Android
- Where can .jar files be placed under Assets/Plugins/Android? - Unity Answers
Android Studio では、project.properties が生成されないので、自分でファイルを作る。
注意点 2: Assets/Plugins/Android 内に置かないとだめ
jar ファイル単体の場合、他の場所(例えば、Assets/DDDTwitter/Plugins/Android
とか)に置いても、plugins として認識されてビルドに含まれる。
しかし res や AndroidManifest をビルドに含めるためには、Assets/Plugins/Android
直下に置くか、ディレクトリを切って project.properties と一緒に配置する必要がある。
注意点 3: android-support-v4.jar が必要
FileProvider を使うために必要。Android SDK から持ってくればいい。
android-support-v4.jar とか google-play-services.jar とかが複数含まれていてビルドに失敗することはままあるので、
こういった、プラグインの枠を超えて使われるライブラリは、Assets/Plugins/Android
直下など分かりやすいところに置いた方が良いと思う。
まとめ・感想
Unity Android でも、FileProvider を使うことができた。 FileProvider の使い方自体はまったく難しくなかったが、Unity が res/xml をビルドに含めてくれなくって、かなりハマった。