一、RemoteViews的应用
RemoteViews在实际开发中,主要用在通知栏和桌面小部件的开发过程中。通知栏主要是通过NotificationManager的notify方法来实现的,图除了默认效果外,还可以另外定义布局。桌面小部件是通过AppWidgetProvider来实现的,AppWidgetProvider本质上是一个广播。通知栏和桌面小部件的开发过程中都会用到RemoteViews,他们在更新界面时无法像在activity中那样直接去更新view,这是因为二者的界面都运行在其他进程中,确切来说是在系统的SyestemServer进程。为了跨进程更新界面,RemoteViews提供了一系列的set方法并且这些方法只是view全部方法的子集,另外RemoteViews支持的view类型是有限的。
1.RemoteViews在通知栏中的应用
Notification notification = new Notification();
notification.icon = R.mipmap.ic_launcher_round;
notification.tickerText = "hello world";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCEL;
Intent intent = new Intent(this, LoginActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
System.out.println(pendingIntent);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.remote);
remoteViews.setTextViewText(R.id.msg, "chapter_5: ");
remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this,
0, new Intent(this, LoginActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.msg, openActivity2PendingIntent);
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, notification);
RemoteViews的使用很简单,重要提供相应的包名和布局文件的id即可以创建一个RemoteViews对象,但是如何更新RemoteViews呢?这一点和更新view有很大不同,更新RemoteViews时,无法直接访问里面的view,必须通过RemoteViews提供的一系列方法来更新view:
- remoteViews.setTextViewText(R.id.msg, “chapter_5: “);//更新文字
- remoteViews.setImageViewResource(R.id.icon, R.mipmap.ic_launcher);//更新图片
- remoteViews.setOnClickPendingIntent(R.id.msg, openActivity2PendingIntent);设置点击事件。
2.RemoteViews在桌面小部件上的应用
桌面小控件的开发步骤:
1、定义小部件界面:
在res/layout下新建XML文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon1" />
</LinearLayout>
2.定义小部件配置信息
在res/xml下创建xml文件
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"//初始化的布局
android:minHeight="84dp"
android:minWidth="84dp"
android:updatePeriodMillis="86400000" >//自动更新周期,毫秒为单位
</appwidget-provider>
3.定义小部件的实现类
public class MyAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "MyAppWidgetProvider";
public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";
public MyAppWidgetProvider() {
super();
}
@Override
public void onReceive(final Context context, Intent intent) {
super.onReceive(context, intent);
Log.i(TAG, "onReceive : action = " + intent.getAction());
// 这里判断是自己的action,做自己的事情,比如小工具被点击了要干啥,这里是做一个动画效果
if (intent.getAction().equals(CLICK_ACTION)) {
Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
Bitmap srcbBitmap = BitmapFactory.decodeResource(
context.getResources(), R.drawable.icon1);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
for (int i = 0; i < 37; i++) {
float degree = (i * 10) % 360;
RemoteViews remoteViews = new RemoteViews(context
.getPackageName(), R.layout.widget);
remoteViews.setImageViewBitmap(R.id.imageView1,
rotateBitmap(context, srcbBitmap, degree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent
.getBroadcast(context, 0, intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(
context, MyAppWidgetProvider.class),remoteViews);
SystemClock.sleep(30);
}
}
}).start();
}
}
/**
* 每次窗口小部件被点击更新都调用一次该方法
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate");
final int counter = appWidgetIds.length;
Log.i(TAG, "counter = " + counter);
for (int i = 0; i < counter; i++) {
int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}
/**
* 窗口小部件更新
*
* @param context
* @param appWidgeManger
* @param appWidgetId
*/
private void onWidgetUpdate(Context context,
AppWidgetManager appWidgeManger, int appWidgetId) {
Log.i(TAG, "appWidgetId = " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget);
// "窗口小部件"点击事件发送的Intent广播
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
}
private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0,
srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true);
return tmpBitmap;
}
}
4.在AndroidManifest.xml中声明小部件
<receiver android:name=".MyAppWidgetProvider" >
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info" >
</meta-data>
<intent-filter>
<action android:name="com.ryg.chapter_5.action.CLICK" />//小部件的单击行为
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />//系统规范,必须有
</intent-filter>
</receiver>
AppWidgetProvider 中几个方法的调用时机
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
//当该窗口小部件第一次添加到桌面时调用该方法,
//可添加多次但只在地题词调用
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
//小部件被添加时或者每次小部件更新时都会调用一个该方法
//小部件更新时机由updatePeriodMillis来指定
//每个周期小部件都会自动更新一次
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
//每删除一次桌面小部件就调用一次
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
//当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
//这是广播的内置方法,用于分发具体的事件给其他方法
}
3. PendingIntent概述
顾名思义:PendingIntent表示一种处于Pending状态的意图,而Pending状态表示的是一种待定,等待,即将发生的意思,就是说接下来有一个意图将在某个待定的事件发生。可以看出PendingIntent和Intent的区别在于,PendingIntent是在将来某个不确定的时刻发生,而Intent是立即发生。PendingIntent典型的使用场景是给RemoteViews添加点击事件,因为RemoteViews运行在远程进程中,因此RemoteViews不同于普通的view,所以无法直接向view那样通过setOnClickListener方法来设置单击事件。要给RemoteViews设置单击事件,就必须使用PendingIntent,PendingIntent通过send和cancel放来发送和取消特定的待定的Intent。
PendingIntent支持三种特定意图:启动Activity,启动Service和发送广播。
- getActivity(Context context,int requestCode,Intent intent,int flags)//获得一个PendingIntent,该待定意图发送时,相当于context.startActivity(Intent);
- getService(Context context,int requestCode,Intent intent,int flags);//获得一个PendingIntent,该待定意图发送时,相当于context.startService(Intent);
- getBroadcast(Context context,int requestCode,Intent intent,int flags));
- //获得一个PendingIntent,该待定意图发送时,相当于context.sendBroadcast(Intent);
flags 的类型:
- FLAG_ONE_SHOT: PendingIntent只能使用一次
- FLAG_NO_CREATE:不怎么用
- FLAG_CANCEL_CURRENT:PendingIntent如果已经存在,那么他们都会被cancel,然后系统会创建一个新的PendingIntent
- FLAG_UPDATE_CURRENT:PendingIntent如果已经存在,那么会被更新,即它们intent中的Extras会被替换成最新的。
二、RemoteViews的内部机制
RemoteViews的只主要作用是在其他进程中显示并更新View界面,其构造方法中有两个参数,一个是当前包名,一个是布局文件
支持的Layout:
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
支持的View:
- AnalogClock,Botton,Chronometer,ImageBottom,ImageView,ProgressBar
- TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper,ViewStub
三、RemoteViews的意义
public void onButtonClick(View v) {
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
remoteViews.setTextViewText(R.id.msg, "msg from process:" + Process.myPid());
remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, new Intent(this, DemoActivity_1.class), PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);
Intent intent = new Intent(MyConstants.REMOTE_ACTION);
intent.putExtra(MyConstants.EXTRA_REMOTE_VIEWS, remoteViews);
sendBroadcast(intent);
}
public class MainActivity extends Activity {
private static final String TAG = "MainActivity";
private LinearLayout mRemoteViewsContent;
private BroadcastReceiver mRemoteViewsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
RemoteViews remoteViews = intent
.getParcelableExtra(MyConstants.EXTRA_REMOTE_VIEWS);
if (remoteViews != null) {
updateUI(remoteViews);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mRemoteViewsContent = (LinearLayout) findViewById(R.id.remote_views_content);
IntentFilter filter = new IntentFilter(MyConstants.REMOTE_ACTION);
registerReceiver(mRemoteViewsReceiver, filter);
}
private void updateUI(RemoteViews remoteViews) {
// View view = remoteViews.apply(this, mRemoteViewsContent);
int layoutId = getResources().getIdentifier("layout_simulated_notification", "layout", getPackageName());
View view = getLayoutInflater().inflate(layoutId, mRemoteViewsContent, false);
remoteViews.reapply(this, view);
mRemoteViewsContent.addView(view);
}