#はじめに
前回の記事と対になるものです。 Delphi 10.3 CommunityEditionにて確認しています。
##暗黙的インテントを受け取る
参考:
Delphiサンプル FMX.Android Intents Sample
Delphi 10 SeattleのAndroidアプリケーションでIntentの送受信をするには
###インテントフィルタ
最初にAndroidManifest.template.xml
を編集し、受け取りたいインテントの種類に応じた内容を追加します。
例えば、Googleドライブでファイルを選択し自アプリで開く場合は、VIEWというアクションになります。(拡張子がTxtなどのドキュメント形式の場合、EDITというアクションになるので注意)
<activity android:name="com.embarcadero.firemonkey.FMXNativeActivity" (省略)>
(中略)
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/octet-stream" />
</intent-filter>
</activity>
###コード
インテントの受信の処理は、アプリケーション動作前(停止中)と、アプリケーション動作中でインテントの取得方法が違うため、コードが分かれます。先に、アプリケーション動作中について書きます。
####アプリケーション動作中のインテント処理
interface節のusesに次を加えます。
System.Messaging, Androidapi.JNI.GraphicsContentViewText
メインフォーム(TForm1)のメソッドに次を追加します。
procedure HandleActivityMessage(const Sender: TObject; const AMessage: TMessage);
procedure HandleIntentAction(const intent: JIntent);
implementation節のusesに次を加えます。
FMX.Platform.Android, Androidapi.Helpers
FormのOnCreate
イベントに以下を記述します。
procedure TForm1.FormCreate(Sender: TObject);
begin
MainActivity.registerIntentAction(TJIntent.JavaClass.ACTION_VIEW);
TMessageManager.DefaultManager.SubscribeToMessage
(TMessageReceivedNotification, HandleActivityMessage);
end;
MainActivity.registerIntentAction
の引数は、インテントフィルタに設定したものと合わせます。
複数種類のインテントを受ける場合、複数行に分けて書きます。
次にHandleActivityMessageを実装します。
procedure TForm1.HandleActivityMessage(const Sender: TObject; const AMessage: TMessage);
var
intent: JIntent;
begin
if AMessage is TMessageReceivedNotification then
begin
intent := TMessageReceivedNotification(AMessage).Value;
if intent <> nil then
HandleIntentAction(intent);
end;
end;
HandleIntentActionを実装します。(ここでは動作確認のみ。ファイル処理は後半で扱います。)
procedure TForm1.HandleIntentAction(const intent: JIntent);
begin
ShowMessage(JStringToString(intent.toString));
end;
一旦動作確認してみましょう。(GoogleドライブにTest.datというファイルを置いて開いてみました)
AndroidManifest.xml が古い場合や、内容が違う場合、ファイルを選択した際に候補として出てこないことがあります。その場合はプロジェクトのクリーンアップ後に再度実行してみてください。
####アプリケーション起動時のインテント処理
メインフォームに次のメソッドを追加します。
procedure HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject);
FormのOnCreateイベントに以下を追加します。
procedure TForm1.FormCreate(Sender: TObject);
var
AppEventService: IFMXApplicationEventService;
begin
if TPlatformServices.Current.SupportsPlatformService
(IFMXApplicationEventService, AppEventService) then
AppEventService.SetApplicationEventHandler(HandleAppEvent);
(上で書いた処理)
end;
HandleAppEventを実装します。
function TForm1.HandleAppEvent(AAppEvent: TApplicationEvent; AContext: TObject): Boolean;
var
StartupIntent: JIntent;
begin
result := False; //(ここちょっとよく理解できていません)
if AAppEvent = TApplicationEvent.FinishedLaunching then //起動時イベントの場合
begin
StartupIntent := MainActivity.getIntent;
if StartupIntent <> nil then
begin
HandleIntentAction(StartupIntent);
end;
end;
end;
デバッグ実行し、一度アプリを終了してからインテントを受け取れるか確認してみましょう!
##インテント内のファイルの抽出
先ほどのHandleIntentAction を書き変えていきますが、まずimplementation節のusesに以下を追加します。
Androidapi.JNI.Net, //Jnet_Url
Androidapi.JNI.JavaTypes, //JInputStream
Androidapi.JNIBridge, //TJavaArray
Androidapi.JNI.Provider; //TJOpenableColumns
###ファイルの有無
インテントにファイルが含まれているかどうかは、intent.getData
がnil
かどうかで判定できます。
var
uri:JNet_Uri;
begin
uri := intent.getData;
if uri <> nil then
begin
(ファイルがある場合の処理)
end;
end;
(ファイル以外の情報をとる場合、intent.getExtras(StrintgToJString('キー名'))
を使用します。今回は割愛します。)
###ファイルデータの取得
URIが取得できれば、ファイルの中身はJInputStream
にてとることができます。それをTMemoryStream
に転写します。
var
inputStream: JInputStream;
jbytes:TJavaArray<Byte>;
memoryStream:TMemoryStream;
begin
inputStream := TAndroidHelper.ContentResolver.openInputStream(uri);
jbytes := TJavaArray<Byte>.Create(inputStream.available);
inputStream.read(jbytes);
memoryStream := TMemoryStream.Create;
memoryStream.Write(jbytes.Data^,jbytes.Length);
inputStream.close;
end;
そこから先は普段のDelphiと変わりません。
###ファイル名の取得
URI の内容が file://~ から始まっている場合 ファイル名はURI内に含まれていますが、content:// から始まっている場合は Android OSの仕組みである、ContentResolverを使用する必要があります。
参考:【Android】「Uri」から「ファイル名」を取得する
var
cursor:JCursor;
filename:String;
begin
if uri.getScheme.equals(TJContentResolver.JavaClass.SCHEME_FILE) then
begin
filename := JStringToString( Uri.getLastPathSegment );
end
else if uri.getScheme.equals(TJContentResolver.JavaClass.SCHEME_CONTENT) then
begin
cursor := TAndroidHelper.ContentResolver.query(uri, nil, nil, nil, nil);
try
if (cursor <> nil) and (cursor.moveToFirst) then
begin
filename := JStringToString(
cursor.getString(cursor.getColumnIndex(TJOpenableColumns.JavaClass.DISPLAY_NAME) ));
end;
finally
cursor.close;
end;
end;
//filenameが空だった時はデフォルト名か何かをつける
end;
(projectionを指定しなくてもDisplayNameとSizeはとれるからいいのかなーという雑な認識)
###アプリケーションローカルにファイルのコピーを保存する場合
上記を通しで書くとこんな感じです
procedure TForm1.HandleIntentAction(const intent: JIntent);
var
uri:JNet_Uri;
cursor:JCursor;
filename:String;
inputStream: JInputStream;
jbytes:TJavaArray<Byte>;
memoryStream:TMemoryStream;
begin
uri := intent.getData;
if uri <> nil then
begin
if uri.getScheme.equals(TJContentResolver.JavaClass.SCHEME_FILE) then
begin
filename := JStringToString( Uri.getLastPathSegment );
end
else if uri.getScheme.equals(TJContentResolver.JavaClass.SCHEME_CONTENT) then
begin
cursor := TAndroidHelper.ContentResolver.query(uri, nil, nil, nil, nil);
try
if (cursor <> nil) and (cursor.moveToFirst) then
begin
filename := JStringToString(
cursor.getString(cursor.getColumnIndex(TJOpenableColumns.JavaClass.DISPLAY_NAME) ));
end;
finally
cursor.close;
end;
end;
if filename.isEmpty then fileName := TPath.GetTempFileName;
inputStream := TAndroidHelper.ContentResolver.openInputStream(uri);
jbytes := TJavaArray<Byte>.Create(inputStream.available);
inputStream.read(jbytes);
memoryStream := TMemoryStream.Create;
memoryStream.Write(jbytes.Data^,jbytes.Length);
inputStream.close;
memoryStream.SaveToFile(TPath.Combine(TPath.GetDocumentPath, filename));
memoryStream.DisposeOf;
end;
end;
以上です。