このブログのコピーです。
概要
iOS10ではリッチ通知が実装され、動画も通知領域に表示(ただし音声のみ)できるようになったのに、Androidができないだなんて不公平だ!と思って、人類解放戦線を戦うことを決意しました。
人類を支配しているのはRemoteViews。支配からの開放なるか…!
…とか言いながら実は既に先人の知恵が。主にApp Widgets界の話なのだけど、RemoteViewsと戦うことには変わりなし。
Animating Android home-screen widget
環境
- macOS Sierra 10.12.3
- Android Studio 2.2(2.3にするとGradleがタヒぬので…
- Android 6.0
構成
描画部分はAnimating Android home-screen widgetのとおりRemoteViews#setImageViewBitmapにBitmapを指定して、フレーム毎描画するという力技。
描画リソースはアニメーションGifとしてそのパーサを以下から拝借。
AndroidでGIFアニメーションが動いたんだもんね(バグフィックス)
redraw部分はWidgetsのようにイベントがとんでこないのでループするためだけのServiceを実装しました。
リモートPushするのがめんどかったので、ローカルプッシュで実装しました。
コードは以下の通りです。
コード
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.xxxxx.animationgifnotificationsample">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
...
</activity>
<service android:name=".ImageProcService"/>
</application>
</manifest>
MainActivity.java
package com.xxxx.animationgifnotificationsample;
...
public class MainActivity extends AppCompatActivity implements View.OnClickListener, IRedrawable {
public static final String IMAGE_PROC_ACTION = "com.xxxx.animationgifnotificationsample.IMAGE_PROC_ACTION";
public static final String IMAGE_FINISH_ACTION = "com.xxxx.animationgifnotificationsample.IMAGE_FINISH_ACTION";
private static final int TIME_PERIOD = 30000;
private static final int TIME_DELAY = 100;
private static final int NOTIFICATION_ID = 0;
private Context mContext;
private BroadcastReceiver mReceiver;
private IntentFilter mFilter;
private NotificationCompat.Builder mBuilder;
private GifDecoder mDecoder;
private RemoteViews mRemoteViews;
private NotificationManagerCompat mManager;
private Bitmap mCanvas;
private int mFrameCount;
....
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.button_notification:
this.sendNotification();
break;
}
}
private void sendNotification() {
try {
this.mDecoder = new GifDecoder();
this.mDecoder.read(this.mContext.getResources().openRawResource(R.raw.nyancat));
} catch (Throwable e) {
// TODO
Log.d("xxxx", e.toString());
return;
}
this.mBuilder = new NotificationCompat.Builder(mContext);
this.mBuilder.setSmallIcon(R.mipmap.ic_launcher);
this.redrawImage();
this.setRedrawSchedule();
}
/**
* IRedrawable
*/
synchronized public void redrawImage() {
this.mCanvas = this.mDecoder.getFrame(this.mFrameCount);
this.mRemoteViews = new RemoteViews(this.mContext.getPackageName(), R.layout.activity_notification);
this.mRemoteViews.setImageViewBitmap(R.id.gifView, this.mCanvas);
Notification notification = this.mBuilder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
notification.bigContentView = this.mRemoteViews;
}
this.mManager = NotificationManagerCompat.from(mContext.getApplicationContext());
this.mManager.notify(NOTIFICATION_ID, notification);
this.mFrameCount = ++this.mFrameCount % this.mDecoder.getFrameCount();
}
private void setRedrawSchedule() {
Intent intent = new Intent(this, ImageProcService.class);
this.mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final IRedrawable redrawable = (IRedrawable) mContext;
switch(intent.getAction()) {
case MainActivity.IMAGE_PROC_ACTION:
redrawable.redrawImage();
break;
case MainActivity.IMAGE_FINISH_ACTION:
redrawable.finish();
}
}
};
this.mFilter = new IntentFilter();
this.mFilter.addAction(IMAGE_PROC_ACTION);
this.mFilter.addAction(IMAGE_FINISH_ACTION);
this.registerReceiver(this.mReceiver, this.mFilter);
intent.putExtra("loop_period", TIME_PERIOD);
intent.putExtra("loop_interval", TIME_DELAY);
this.startService(intent);
}
/**
* IRedrawable
*/
public void finish() {
this.unregisterReceiver();
}
synchronized private void unregisterReceiver() {
if(this.mReceiver != null) {
this.unregisterReceiver(this.mReceiver);
this.mReceiver = null;
}
}
}
IRedrawable.java
package com.xxxx.animationgifnotificationsample;
public interface IRedrawable {
void redrawImage();
void finish();
}
ImageProcService.java
package com.xxxx.animationgifnotificationsample;
...
public class ImageProcService extends IntentService {
static final private int DEFAULT_LOOP_PERIOD = 10000;
static final private int DEFAULT_LOOP_INTERVAL = 100;
public ImageProcService(String name) {
super(name);
}
public ImageProcService() {
super("ImageProcService");
}
@Override
protected void onHandleIntent(Intent intent) {
final long now = System.currentTimeMillis();
long endtime = now + DEFAULT_LOOP_PERIOD;
long interval = DEFAULT_LOOP_INTERVAL;
final Bundle extras = intent.getExtras();
final Integer _loop_period_buf = (Integer)extras.get("loop_period");
if(_loop_period_buf != null) {
endtime = now + _loop_period_buf;
}
final Integer _loop_interval_buf = (Integer)extras.get("loop_interval");
if(_loop_interval_buf != null) {
interval = _loop_interval_buf;
}
while(System.currentTimeMillis() < endtime) {
try {
Thread.sleep(interval);
final Intent broadcastintent = new Intent();
broadcastintent.setAction(MainActivity.IMAGE_PROC_ACTION);
this.getBaseContext().sendBroadcast(broadcastintent);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
結果
できたー。
RemoteViewsの支配から人類が自由を勝ち取った瞬間の映像です #絶対rejectされる pic.twitter.com/bzEKKvbiLA
— あおれい - 月が綺麗かどうかおいていま (@bhind13) 2017年3月30日
ハマったこと・課題など
- Android Engineerには常識だとはおもうのだけど、RemoteViewsに自分で作ったViewクラス派生のGifAnimationView的なのをRemoteViewsに何回も何回もCouldn't Expand RemoteViews 言われたのがきっかけ
- ViewFlipperでもできるらしいけどやってない
- ちなみにリモートPushでやっても反映の際にローカルNotifyしないとだめだから非現実的