一、因原生系统支持的RemoteView控件很少,特别是在动画实现的控件里面,所以需要自定义RemoteView到系统框架中,编译之后再在工程中引用。
自定义RemoteView的规范示例如下:
@RemoteView // 此处添加RemoteView标签,表示支持RemoteView调用
publicclass WidgetProgressbar extends ProgressBar {
/**
* setPlayingState: TODO<br/>
*
* @param pIsPlaying boolean
* @hide // 此处声明方法为hide类型,否则在编译整个Rom工程的时候会报错
*/
@RemotableViewMethod // 此处添加标签声明,表示支持RemoteView方法调用
public void setPlayingState(boolean pIsPlaying) {
Log.v(TAG, "------->>setPlayingState(),pIsPlaying:" + pIsPlaying);
}
}
初始音乐插件播放进度条显示的实现是在MediaAppWidgetProvider中开启一个线程,通过持续刷新的方式将当前的播放进度通过RemoteView的方法将播放进度刷新到小工具上,这种方法会导致RemoteView的Action数组在刷新较长的一段时间之后溢出(因为framework的RemoteView 并没有对Action数组做清除的逻辑处理)进而导致整个音乐应用运行缓慢,最终导致ANR。所以自定义一个WidgetProgressbar(extends ProgressBar)类,将进度条的刷新放在这个类中自己处理。在MediaAppWidgetProvider的更新只需要设置WidgetProgressbar中的播放状态值,及当前的播放进度。
类中还要包含对屏幕亮暗和桌面是否处于前台的广播监听处理,如果桌面处于后台或者屏幕处于暗屏状态则停止进度条的刷新,避免多余资源的耗费。
在onAttachedToWindow()中注册广播监听,在onDetachedFromWindow()取消广播监听并且移除Handler中的刷新进度条的Message(避免在控件onDetachedFromWindow之后这个Handler的刷新还在循环进行,使得Handler消息队列中的消息过多导致消息队列的堵塞进而引发其他的异常,如反复切换字体大小或语言,若没有对Handler中的消息队列进行清理,会导致Launcher堆内存一直上涨。所以桌面小插件的刷新逻辑实现要特别注意Handler消息的及时清理。
我们看到很多桌面小插件都没有进度条,就是因为其刷新机制问题,所以如果不是做系统开发,只是做一般的应用开发,不要在桌面小插件上面做进度条刷新显示的实现。
而小米手机的音乐插件其实并不是一个插件,因为其实现是在Launcher上面,并不是在音乐播放器上面。因小米手机framework改造的原因得以将实现放在Launcher上面,其他平台的手机暂没注意。
2、小工具点击事件的绑定
原生音乐小工具绑定按钮点击事件是在每次更新Widget内容的时候,因为要根据当前不同的播放状态跳转到不同的页面,RemoteView的OnClickPending数组也是没有做清理的处理逻辑,这样会导致在多次绑定控件的pendingIntent之后导致音乐OOM异常。这个是安卓framework中RemoteView自身机制的缺陷。所以建议只在初始化Widget控件的时候一次性绑定所有View的PendingIntent。
if (playerActive) {
intent = new Intent(context, MediaPlaybackActivity.class);
pendingIntent = PendingIntent.getActivity(context,
0 /* no requestCode */, intent, 0 /* no flag */);
views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent);
} else {
intent = new Intent(context, MusicBrowserActivity.class);
pendingIntent = PendingIntent.getActivity(context,
0 /* no requestCode */, intent, 0 /* no flag */);
views.setOnClickPendingIntent(R.id.album_appwidget, pendingIntent);
}
3、小工具左右切换动画实现
如果是在XML布局中直接用ViewFlipper控件则只能设置一个方向的动画,只能配置inAnimation与outAnimation,且无法通过RemoteView来重新设置参数,没有相应的接口可以调用,而通过LayoutAnimation则只能在每次移除或者加载布局的时候触发一次动画,这种实现更不靠谱。
解决方法是在framework中自定义View,再在布局中引用此自定义控件。
@RemoteView
public class WidgetViewFlipper extends ViewFlipper {
/**
* switchAnimation: TODO<br/>
*
* @param pStrDirection String
* @hide
*/
@android.view.RemotableViewMethod
public void setAnimationMode(String pStrDirection) {
if (pStrDirection.equals(LEFTTORIGHT)) {
setInAnimation(slideLeftIn());
setOutAnimation(slideRightOut());
} else if (pStrDirection.equals(RIGHTTOLEFT)) {
setInAnimation(slideRightIn());
setOutAnimation(slideLeftOut());
}
}
}
在调用的时候通过RemoteView调用函数设置动画方向:
views.setString(R.id.customViewFlipper_appwidget_textShow,"setAnimationMode", WidgetViewFlipper.RIGHTTOLEFT);
其他自定义动画的实现原理同此。