热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

AppWidget的使用及原理分析

一AppWidget的使用:1、首先在reslayout文件夹下定义一个布局文件reslayoutapp_widget.xml<?xmlversion1.0

一 AppWidget的使用:
1、首先在res/layout文件夹下定义一个 布局文件
res/layout/app_widget.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">


<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/iv"
android:src="@mipmap/ic_launcher"
/>

LinearLayout>

2、在 res/xml文件夹中新建一个 appwidgetProvider的配置文件
res/xml/appwidget-provider-info.xml


<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/app_widget"
android:minHeight="110dp"
android:minWidth="110dp"
android:updatePeriodMillis="8640000"
>

appwidget-provider>

其中initialLayout 代表该AppWidget引用的资源文件
minHeight ,minWidth代表appWidget的大小 这里写110dp代表 站 2x2个方格,
这里有一个公式: n X 70 -30 = ? n代表占多少格。
这里写图片描述
updatePeriodMillis 代表该appWidget多长时间更新一次,这里单位为毫秒。

3、编写AppWidgetProvider

public class MyAppWidget extends AppWidgetProvider {

private static final String TAG = "MyAppWidget";

public static final String CLICK_ACTION = "com.blueberry.sample.appwidget_CLICK";

@Override
public void onReceive(final Context context, final Intent intent) {
super.onReceive(context, intent);

Log.i(TAG, "onReceiver: action = " + intent.getAction());

if (intent.getAction().equals(CLICK_ACTION)) {
Toast.makeText(context, "click it", Toast.LENGTH_SHORT).show();

new Thread(new Runnable() {
@Override
public void run() {
Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(),
R.mipmap.ic_launcher);
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.app_widget);
remoteViews.setImageViewBitmap(R.id.iv,
rotateBitmap(srcBitmap, degree));
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context,
MyAppWidget.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 int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}

private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
Log.i(TAG, "appWidgetId= " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.app_widget);
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
}

private static Bitmap rotateBitmap(Bitmap bitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

}

4、在清单文件中注册

 <receiver android:name=".widgets.MyAppWidget">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info">
meta-data>

<intent-filter>
<action android:name="com.blueberry.sample.appwidget_CLICK">action>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE">action>
intent-filter>
receiver>

其中com.blueberry.sample.appwidget_CLICK 是我自己定义的一个ACTION.

其实AppWidgetProvider 是一个广播接收器,查看源码可知:
AppWidgetProvider类中在它的onReceiver()方法中,通过判断ACTION,然后执行了几个钩子方法

 // BEGIN_INCLUDE(onReceive)
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds != null && oldIds.length > 0) {
this.onRestored(context, oldIds, newIds);
this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
}
}
}
}

根据文档可知:
onEnable:当该窗口小部件第一次被添加到桌面时调用该方法,可添加多次但只在第一次调用
onUpdate: 小部件添加时或者每次小部件跟新时都会调用此方法,小部件的跟新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。
onDeleted:每删除一次桌面小部件就会被调用一次。
onDisable: 当最后一个该类型的桌面小部件被删除时调用该方法。

所以在我们自定义小部件时 需要重写public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) 来更新小部件。

这里在更新的时候我们根据布局文件创建出来一个removeViews ,并且我们给我们imageView设置了一个监听事件,点击它然后会发出一个广播,这个广播的action是我们自己定义的。
然后收到这个广播之后,旋转bitmap 然后重新设置给ImageView,并更新小部件。

这里需要注意的是:
1、小部件只支持RemoteView
2、我们使用appWidgetManager 来更新的小部件。
支持RemoteView的控件有:

FrameLayout
LinearLayout
RelativeLayout
GridLayout

AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

具体请参考:https://developer.android.com/guide/topics/appwidgets/index.html

二、原理分析
上段我们讲到,appWidget只支持RemoteViews 和他需要使用mAppWidgetManager来更新小部件,这是为什么呢?
原因是,小部件跟我们的应用并非在同一个进程。所以我们要夸进程来更新小部件。而RemoteView支持夸进程,而mAppWidgetManager底层正是Binder。它对应的是AppWidgetServiceImpl。

我们在使用mAppWidgetManger来更新小部件时调用:

  public void updateAppWidget(ComponentName provider, RemoteViews views) {
if (mService == null) {
return;
}
try {
mService.updateAppWidgetProvider(provider, views);
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}

它调用了 mService.updateAppWidgetProvider(provider, views);
而mService实现是 AppWidgetServiceImpl
也就是说,它调用了 AppWidgetServiceImpl 的

public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
final int userId = UserHandle.getCallingUserId();

if (DEBUG) {
Slog.i(TAG, "updateAppWidgetProvider() " + userId);
}

// Make sure the package runs under the caller uid.
mSecurityPolicy.enforceCallFromPackage(componentName.getPackageName());

synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);

// NOTE: The lookup is enforcing security across users by making
// sure the caller can access only its providers.
ProviderId providerId = new ProviderId(Binder.getCallingUid(), componentName);
Provider provider = lookupProviderLocked(providerId);

if (provider == null) {
Slog.w(TAG, "Provider doesn't exist " + providerId);
return;
}

ArrayList instances = provider.widgets;
final int N = instances.size();
for (int i = 0; i Widget widget = instances.get(i);
updateAppWidgetInstanceLocked(widget, views, false);
}
}
}

继续看 updateAppWidgetInstanceLocked(widget, views, false);

    private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
boolean isPartialUpdate) {
if (widget != null && widget.provider != null
&& !widget.provider.zombie && !widget.host.zombie) {

if (isPartialUpdate && widget.views != null) {
// For a partial update, we merge the new RemoteViews with the old.
widget.views.mergeRemoteViews(views);
} else {
// For a full update we replace the RemoteViews completely.
widget.views = views;
}

scheduleNotifyUpdateAppWidgetLocked(widget, views);
}
}
 private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
if (widget == null || widget.provider == null || widget.provider.zombie
|| widget.host.callbacks == null || widget.host.zombie) {
return;
}

SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
args.arg3 = updateViews;
args.argi1 = widget.appWidgetId;

mCallbackHandler.obtainMessage(
CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,
args).sendToTarget();
}

这里使用了一个Handler发送了一个消息,它的接收代码:

  @Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_NOTIFY_UPDATE_APP_WIDGET: {
SomeArgs args = (SomeArgs) message.obj;
Host host = (Host) args.arg1;
IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;
RemoteViews views = (RemoteViews) args.arg3;
final int appWidgetId = args.argi1;
args.recycle();

handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views);
} break;
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views) {
try {
callbacks.updateAppWidget(appWidgetId, views);
} catch (RemoteException re) {
synchronized (mLock) {
Slog.e(TAG, "Widget host dead: " + host.id, re);
host.callbacks = null;
}
}
}

这里调用了 callbacks.updateAppWidget(appWidgetId, views);
这里的callback实际又是个binder,它的实现是AppWidgetHost.Callbacks

public class AppWidgetHost {


class Callbacks extends IAppWidgetHost.Stub {
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
}
Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
msg.sendToTarget();

可以看到它的updateAppWidget ,发送了一个消息来更新小部件,它的接收程序为:

  class UpdateHandler extends Handler {
public UpdateHandler(Looper looper) {
super(looper);
}

public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_UPDATE: {
updateAppWidgetView(msg.arg1, (RemoteViews)msg.obj);
break;
}
 void updateAppWidgetView(int appWidgetId, RemoteViews views) {
AppWidgetHostView v;
synchronized (mViews) {
v = mViews.get(appWidgetId);
}
if (v != null) {
v.updateAppWidget(views);
}
}
public void updateAppWidget(RemoteViews remoteViews) {

......
int layoutId = remoteViews.getLayoutId();


if (cOntent== null && layoutId == mLayoutId) {
try {
// 调用这里
remoteViews.reapply(mContext, mView, mOnClickHandler);
cOntent= mView;
recycled = true;
if (LOGD) Log.d(TAG, "was able to recycled existing layout");
} catch (RuntimeException e) {
exception = e;
}
}

if (cOntent== null) {
try {
//调用这里
cOntent= remoteViews.apply(mContext, this, mOnClickHandler);
if (LOGD) Log.d(TAG, "had to inflate new layout");
} catch (RuntimeException e) {
exception = e;
}
}

....
}

之类可以看到 如果 cOntent==null 的时候他调用 remoteViews.apply(…)方法
如果content!=null 它调用remoteViews.reApply(…)方法

那我们就接着看RemoteViews;

public class RemoteViews implements Parcelable, Filter {

可以看到它实现了Parcelable接口,所以可以序列化在进程间传递。
我们直接看它的apply方法

public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);

View result;

final Context cOntextForResources= getContextForResources(context);
Context inflatiOnContext= new ContextWrapper(context) {
@Override
public Resources getResources() {
return contextForResources.getResources();
}
@Override
public Resources.Theme getTheme() {
return contextForResources.getTheme();
}
};

LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);


inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);

rvToApply.performApply(result, parent, handler);

return result;
}
   private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}

可以看到它遍历一个 mApcitions结合然后依次 调用他们的 apply方法。

那么这些action是怎么被添加到这个集合中的呢?

我们在给RemoteViews中的TextView、ImageView设置属性的时候都使用remoteViews中类似这样的方法: setImageViewBitmap(int viewId, Bitmap bitmap),这种方法实际就创建出了action来添加到 mAction中
我们就看一下这个方法:

 public void setImageViewBitmap(int viewId, Bitmap bitmap) {
setBitmap(viewId, "setImageBitmap", bitmap);
}
   public void setBitmap(int viewId, String methodName, Bitmap value) {
addAction(new BitmapReflectionAction(viewId, methodName, value));
}

BitmapReflectionAction 实际就是一个Action

 private class BitmapReflectionAction extends Action {
....
@Override
public void apply(View root, ViewGroup rootParent,
OnClickHandler handler) throws ActionException {
ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
bitmap);
ra.apply(root, rootParent, handler);
}
.....

}

这个又new 出了一个ReflectionAction 来调用它的apply(),我们追踪到这个类可以看到:

  @Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View view = root.findViewById(viewId);
if (view == null) return;

Class param = getParameterType();
if (param == null) {
throw new ActionException("bad type: " + this.type);
}

try {
getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
} catch (ActionException e) {
throw e;
} catch (Exception ex) {
throw new ActionException(ex);
}
}

它使用了反射,最后终执行了setImageView方法。这样就完成了更新view操作。

RemoteViews.reapply() 也是同理,这里不再叙述了。


推荐阅读
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • 在一对一直播源码使用过程中,有时会出现软键盘切换闪屏问题,就是当切换表情的时候屏幕会跳动,因此要对一对一直播源码表情面板无缝切换进行优化。 ... [详细]
  • 2021年最详细的Android屏幕适配方案汇总
    1Android屏幕适配的度量单位和相关概念建议在阅读本文章之前,可以先阅读快乐李同学写的文章《Android屏幕适配的度量单位和相关概念》,这篇文章 ... [详细]
  • 这两天用到了ListView,写下遇到的一些问题。首先是ListView本身与子控件的焦点问题,比如我这里子控件用到了Button,在需要ListView中的根布局属性上加上下面的这一个属性:and ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • Android开发实现的计时器功能示例
    本文分享了Android开发实现的计时器功能示例,包括效果图、布局和按钮的使用。通过使用Chronometer控件,可以实现计时器功能。该示例适用于Android平台,供开发者参考。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 移动端常用单位——rem的使用方法和注意事项
    本文介绍了移动端常用的单位rem的使用方法和注意事项,包括px、%、em、vw、vh等其他常用单位的比较。同时还介绍了如何通过JS获取视口宽度并动态调整rem的值,以适应不同设备的屏幕大小。此外,还提到了rem目前在移动端的主流地位。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 今天终于成功使用ReactNative打包APK成功,IOS暂时没有 ... [详细]
author-avatar
起五贪黑_719
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有