Android桌面小控件appwidget的故事Ⅰ

Android版本:8.1


appwidget桌面小控件是以广播接收器的方式,通过远程调用view,实现app在桌面显示view的控件。

APP要实现小控件功能,需要实现一下步骤:

第一步:新建一个类继承AppWidgetProvider,然后实现其中的方法,onEnabled,onReceive,onUpdate,onDeleted,onDisabled,。。
第二步:在app的AndroidManifast.xml里声明这个receiver,并且要给receiver添加meta-data
例如:

      <meta-data
        android:name="android.appwidget.provider"
        android:resource="@xml/dialer_calllog_widget_xml">
      </meta-data>

在resource里,添加xml资源文件,格式如下

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout="@layout/widget_dialer_calllog_layout"   //初始化时加载的布局
    android:minHeight="160dip"		//最小显示高度
    android:minWidth="100dip"		//最小显示宽度
    android:resizeMode="horizontal|vertical" //拖动拉伸时可以拖动的方向
    android:widgetCategory="home_screen|keyguard" 
    android:previewImage="@drawable/empty_call_log"   //小控件在launcher添加时的图标
    android:updatePeriodMillis="86400000"> 			//默认更新频率
  <!-- 70*n-30   width = 180  height = 250-->     
</appwidget-provider>

经过这样的设置,我们就可以在launcher桌面添加小控件的时候看到我们自定义的小控件了。


Appwidget是怎么通过广播接收并更新桌面上的控件view的?

当我们发送广播之后,AppwidgetProvider的onreceive就会收到广播。

public void onReceive(Context context, Intent intent) {
        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);
                }
            }
        } 
    }

由于我们重载了onupdate方法,所以会调用子类的onupdate,
三个参数分别是context,appwidgetmanager,和appwidgetIDs,
我们要更新我们的小控件view,需要创建remoteview,也就是把我们指定布局里的view给发送出去,让appwidgetmanager去更新远程桌面上的view。

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        mRemoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_dialer_calllog_layout);
        appWidgetManager.updateAppWidget(appWidgetIds,mRemoteViews);
    }

在AppWidgetManager里

    public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
        if (mService == null) {
            return;
        }
        try {
            mService.updateAppWidgetIds(mPackageName, appWidgetIds, views);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
---
private final IAppWidgetService mService;

调用的是远程AIDL, 追踪可以发现,

AppWidgetServiceImpl extends IAppWidgetService.Stub

在前面AppwidgetProvider的update里第二个参数,我们传递了一个AppWidgetManager.getInstance(context);
AppWidgetManager就是在这里做的初始化,用的单例模式。源码是这样的

    public static AppWidgetManager getInstance(Context context) {
        return (AppWidgetManager) context.getSystemService(Context.APPWIDGET_SERVICE);
    }

获得的是系统服务,

public class AppWidgetService extends SystemService

这个服务是在Systemserver里启动的,和其他系统服务一起启动。

   if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS)
       || context.getResources().getBoolean(R.bool.config_enableAppWidgetService)) {
       traceBeginAndSlog("StartAppWidgerService");
       mSystemServiceManager.startService(APPWIDGET_SERVICE_CLASS);
       traceEnd();
   }

具体启动流程,不做跟踪。
而AppWidgetService的实现类是

AppWidgetServiceImpl extends IAppWidgetService.Stub

于是,我们的update更新则是在AppWidgetServiceImpl里实现的,

    @Override
    public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
            RemoteViews views) {
        if (DEBUG) {
            Slog.i(TAG, "updateAppWidgetIds() " + UserHandle.getCallingUserId());
        }

        updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
    }

---
private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
            RemoteViews views, boolean partially) {
            ...
        synchronized (mLock) {
            ensureGroupStateLoadedLocked(userId);
            final int N = appWidgetIds.length;
            for (int i = 0; i < N; i++) {
                final int appWidgetId = appWidgetIds[i];
                Widget widget = lookupWidgetLocked(appWidgetId,
                        Binder.getCallingUid(), callingPackage);

                if (widget != null) {
                    updateAppWidgetInstanceLocked(widget, views, partially);
                }
            }
        }
    }
---
private void updateAppWidgetInstanceLocked(Widget widget, RemoteViews views,
            boolean isPartialUpdate) {
        if (widget != null && widget.provider != null
                && !widget.provider.zombie && !widget.host.zombie) {
			...
            scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
        }
    }
---
    private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = widget.host;
        args.arg2 = widget.host.callbacks;
        args.arg3 = (updateViews != null) ? updateViews.clone() : null;
        args.arg4 = requestId;
        args.argi1 = widget.appWidgetId;
        mCallbackHandler.obtainMessage(
                CallbackHandler.MSG_NOTIFY_UPDATE_APP_WIDGET,   //发送message给handler
                args).sendToTarget();
    }

在CallbackHandler里

       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;
           long requestId = (Long) args.arg4;
           final int appWidgetId = args.argi1;
           args.recycle();

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

最后,调用了IAppWidgetHost的远程对象,来更新对应id的view,
就是前面传递的widget的host,

IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2;

message的第二个参数就是它,本地对象就是AppWidgetHost,在AppWidgetHost里我们继续进行,

static class Callbacks extends IAppWidgetHost.Stub {
        private final WeakReference<Handler> mWeakHandler;

        public Callbacks(Handler handler) {
            mWeakHandler = new WeakReference<>(handler);
        }

        public void updateAppWidget(int appWidgetId, RemoteViews views) {
            if (isLocalBinder() && views != null) {
                views = views.clone();
            }
            Handler handler = mWeakHandler.get();
            if (handler == null) {
                return;
            }
            Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
            msg.sendToTarget();
        }
    }
---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);
        }
    }

最后调用了AppWidgetHostView里的update

    public void updateAppWidget(RemoteViews remoteViews) {
        applyRemoteViews(remoteViews, true);
    }

也就是把我们最开始要传递更新的remoteview给替换成新的view,
接下去就是进行view的绘画更新阶段,AppWidgetHostView 其实是FrameLayout

public class AppWidgetHostView extends FrameLayout

所以,当我们要更新我们的view时,就在最开始的接收广播那里,把Remoteview给创建好,最后调用
appWidgetManager.updateAppWidget(appWidgetIds,mRemoteViews) 就会更新小控件的显示内容了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值