您的位置:首页 > 移动开发

AppWidget的使用及原理分析

2016-08-05 17:11 435 查看
一 AppWidget的使用:

1、首先在res/layout文件夹下定义一个 布局文件

res/layout/app_widget.xml

<?xml version="1.0" encoding="utf-8"?>
<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

<?xml version="1.0" encoding="utf-8"?>
<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 < counter; 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<Widget> instances = provider.widgets;
final int N = instances.size();
for (int i = 0; i < N; 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 < count; 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() 也是同理,这里不再叙述了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android