• スポンサードリンク

Android用にタイムシフト録画クラスを作ってみた〜その3〜

Android MediaCodec アプリ

前回はタイムシフトバッファリング用に4.1.1_r1のDiskLruCache.javaを改造してみました。
名前は載せてなかったけど、TimeShiftDiskCacheにしました。そのまんまやん(*´ڡ`●)

今回はTimeShiftDiskCacheを使って常時タイムシフトバッファリングするためのサービスを作ってみます。
なんでサービスにせなあかんかはその1を見てな。

では早速と言うところなんやけど、その前に少し寄り道をします。
プログラムに限らず人にはそれぞれポリシーがあります。企業勤めな人であればそれぞれの企業のローカルルールもあります。
自分の場合は、後で流用できそうなもの・共通で繰り返し使えそうなものは別クラスに入れるようにしています。
まぁこれは人それぞれ好き好きですけど。

という事で今回作るサービスもSDKのServiceクラスを直接使う代わりに間に1つ別のクラスを挟んでいます。

ドカン^^

昔作ったプログラムから使いそうな部分を抜粋してきました。まぁこれぐらいのボリュームで1つしか継承しないのであればクラスを分けずに直接アプリのロジックが入っているクラスに入れてしまってもいいですけど。
本当のところを言えばJavaもC++の様に多重継承出来れば、Activity・Fragment・Service…色んなクラスで共通の処理のクラスを1つ作れば済むんですけど。
閑話休題

TimeShiftRecServiceを実装する

TimeShiftRecServiceの実装・実行形態を決める

今回サービス内で実行する処理は、本来はMediaCodecのエンコーダーとMediaMuxerだけで出来るはずのことです。それをわざわざサービスにしている理由は、単にエンコード・ファイルへの書き出し処理に時間がかかるために通常のアプリ/Activity/Fragmentのライフサイクル内では全ての処理を終了出来ない、という所にあります。一方、映像データそのものは通常のアプリ側から供給してもらうことになります。
別な言い方をすればActivity/Fragmentとは異なるライフサイクルを持たせるためだけにサービス化しています。

御存知の通りAndroidにおけるServiceにはいくつか実装・実行方法があり、それによってServiceのライフサイクルも異なります。
1つはstartService/stopServiceを使う方法ですが、一旦サービスを起動するとActivity/Fragmentとから簡単にサービスを制御することはできませんし、今回のように同じサービスへ継続して映像データを送り込むというのも簡単にはできません。

でもできるだけMediaCodecのエンコーダー+MediaMuxerを使うのと近い感覚で使えたほうが便利ですよね。とすると映像データをActivity/Fragmentから供給する&任意のタイミングでファイルの書き出しを開始/停止する必要があります。

という事で今回のServiceはbindService/unbindServiceを使ってServiceとのコネクションを確立するという形、それも別プロセスで起動する必要もAIDLを介してアクセスする必要もなく、一番簡単なローカルサービスとして実装・利用したいと思います。

忘れちゃならん事

Androidでサービスを作っていざ動かそうとしても原因不明でまったく動かん? そんな経験ありませんか?
その原因の1つはAndroidManifest.xmlにサービスの記述を行っていないことかもしれません。
ということで、兎にも角にもAndroidManifest.xmlにサービスの記述をすること!

今回はタイムシフト関係の実装をtimeshiftモジュールに入れましたので、timeshiftモジュールのAndroidManifest.xmlにサービスの記述を行います。こうすればtimeshiftモジュールを使う他のモジュールでは特に何もせずに自動的にAndroidManifest.xmlがマージされサービスの記述が追加されます。例えばこんな感じになります。

serviceに限らずActivityでもuses-featureでもpermissionでもそのモジュールを使う時に必要となるAndroidManifest.xmlの項目はなんでも書いておけます…が必要最小限にしましょうね。どこぞのライブラリのように使ってもいないREAD_PHONE_STATEを問答無用でマージさせるのはやめましょう。マージしたAndroidライブラリが勝手に付与するパーミッションを取り除く

TimeShiftRecServiceの取りうる状態を決める

もっと細かくももっと粗くもできるけど、今回は次の7つの状態を取ることにしました。まぁSTATE_UNINITIALIZEDとかは要らんなぁとは思うけど^^;

  • STATE_UNINITIALIZED
  • STATE_INITIALIZED
  • STATE_PREPARING
  • STATE_READY
  • STATE_BUFFERING
  • STATE_RECORDING
  • STATE_RELEASING

ローカルサービスとしても体裁を整える

さっきのAndroidManifest.xmlの記述もそうやけど、ローカルサービスとして実装する上での最小限要る部分を書いてしまいます。

たぶんこれだけでServiceとしてビルドして実行できるようになります。多分。

フォアグラウンドサービス

もう1つ大事なことがあります。Android6以前だとこのままでも大抵大丈夫なのですが、Android6以降では従来よりもアグレッシブにアプリを停止させようとします。サービスも例外ではなく、特にbindのみを提供するアクティブなActivityと関係していないサービスは停止させられてしまいます。
でも、Activityのライフサイクルに合わせてサービスを止められてしまうとそもそもの今回の目的が達成されなくなってしまうおそれがあります。

Android Developersには次のような記述があります。

コンポーネントが startService() を呼び出してサービスを開始すると(結果的に onStartCommand() が呼び出される)、stopSelf() を使ってサービス自身が停止するか、他のコンポーネントが stopService() を呼び出して停止するまで、サービスは実行し続けます。

コンポーネントが bindService() を呼び出してサービスを作成した(そして onStartCommand() が呼び出されていない)場合、サービスはコンポーネントにバインドされている間のみ実行します。 すべてのクライアントからアンバインドされると、サービスはシステムによって破棄されます。

Android システムは メモリが少なくなって、ユーザーが使用しているアクティビティ用のシステムリソースを回復させる必要が生じた場合のみ、サービスを強制的に停止させます。 サービスがユーザーが使用しているアクティビティにバインドされている場合は、 それが強制終了される可能性は低く、フォアグラウンドで実行(後で説明)するように宣言されている場合は、強制終了されることはほとんどありません。 一方で、サービスが開始されてから長時間実行している場合は、システムはバックグラウンド タスクのリストにおけるその位置付けを徐々に低くし、そのサービスが強制終了される確率が高くなります。 開始されたサービスを作成する際は、システムによる再起動を円滑に処理するようデザインする必要があります。 システムがサービスを強制終了すると、リソースが回復次第そのサービスが再起動します(後述の onStartCommand() から返される値にもよります)。 システムがサービスを破棄するタイミングについては、プロセスとスレッドのドキュメントをご覧ください。

まぁこれが公式見解っちゅうことだと思いますが、実際には即行で止めてくる端末もあればunbindされてからもずっと動き続けていられる端末もあります。

でも端末によって挙動が変わるというのは困るので、次のようにします。

  1. startServiceも併用してサービスを起動します
    ただし、サービスが必要なくなった時に自力でstoSelfを呼び出すようにする必要があります。
  2. フォアグラウンドサービスとして実行されるようにします
    ただし、サービスが必要となくなった時にフォアグラウンドサービスとしての登録を解除する必要があります。

先程のコードで#onStartCommandが実装されてあったのはそういうわけだったんですよね。こうするとまぁよっぽどメモリが足りんとかにならん限りは好きなだけ動いていられます。

例えばメインActivityの#onCreateで

のように呼び出しておきます。
(その代わり不要になった時にはちゃんとサービス内でstopSelfを呼ばにゃならんからな)

もう1つのフォアグラウンドサービスとしても実行…これも今はAndroid Developersに記述があります。なので省略(^o^)vってなわけにはいかんやろうから概略だけ。
サービスをフォアグラウンドサービスとして実行するには、次の2つの事を実行する必要があります。

  1. ステータスバーに通知を表示する
  2. startForegroundを呼び出す

またフォアグラウンドから除去するには次の2つを実行する必要があります。

  1. ステータスバーから通知を除去する
  2. stopForegroundを呼び出す

上の内容をみて鋭い人は実装するものが増えた事に気づいたはず。さぁていったいなんでしょうか?
おややぁ、思っていたところまでたどりつけんかったけど、長くなってきたので今日はここまで。

今日はあんまりタイムシフト録画自体とはあんまり関係なことばっかりやった(汗)
お疲れ様でした。
(^.^)/~~~

« »

  • スポンサードリンク