AppWidget源码分析---接口类

最近项目中接触到AppWidget,相对来说这部分比较简单,所以趁着空余时间详细阅读了AppWidget的源码。这篇文章主要是从源码上分析AppWidget中API类的相关原理,相关类的简单功能介绍和实现原理。关于使用,建议看指导文档

简述

AppWidget相关的API类(供我们应用开发者使用的类)主要有:

AppWidgetProvider:继承这个类,来提供Appwidget。

AppWidgetManager:提供了AppWidget的管理接口,比如更新,绑定AppWidget id,根据Component获取AppWidget id等等。

RemoteView:能够跨进程更新的View。

AppWidgetHost:与AppWidgt 服务交互的类,获取App widget,显示出来

AppwidgetHostView:实际上显示出来的View

他们之间的关系简略来讲如下:

AppWidget Service 是一些类的集合,是AppWidget的核心服务,在之后的文章会介绍,这篇就不介绍了。下面详细介绍每个类的功能以及实现原理。

AppWidgetProvider

这是我们在建立AppWidget的时候,需要去实现的类,它有几个重要的方法:


public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
       int appWidgetId, Bundle newOptions) // 当Widget的布局到新的大小的时候会被调用

public void onDeleted(Context context, int[] appWidgetIds) // 当某个Widget被移除的时候回调

public void onDisabled(Context context) // 当最后一个Widget被移除的时候回调

public void onEnabled(Context context) // 当第一个Widget被添加的时候回调

public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) // 当Widget从缓存的Widget恢复时

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) //当Widget需要被提供RemoteView的时候,每次添加Widget的时候会调用

public void onReceive(Context context, Intent intent) // 后面介绍

这几个方法是我们在继承AppWidgetProvider的时候,可以根据自己的需要来实现的方法。注释里面是每个方法被回调的时机。

实际上如果去查看AppWidgetProvider的源码,你会发现AppWidgetProvider是继承自BroadcastReceiver的,也就是说它是一种广播。所以它也有一个onReceive方法,这个方法我前面特意没有说明什么时候调用,其实就是收到广播的时候回调。看看onReceive方法的实现,你会发现前面的那几个方法都是在onReceiver中调用的,每一种方法都对应着一种广播Action:


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);
           }
       }
   }
}

AppWidgetProvider是一个BroadcastReceiver,所以它的onUpdate, onEnabled等方法每次被调用前,都会调用onReceive方法。我们继承AppWidgetProvider,也可以重载onReceiver方法,修改onUpdate等方法被调用的时机,当然并不推荐这么做,破坏接口的语义会非常危险。另外一方面它是一个BroadcastReceiver,所以它就具备BroadcastReceiver的各种特性(需要说明的是AppWidgetProvider要在manifest文件中静态注册,因为系统在安装apk时需要知道应用有哪些方法桌面Widget),它能够注册自定义的ACTION,每次执行的时候是会创建一个新的AppWidgetProvider实例,Context不能绑定服务,执行时间不能超过10秒等等。

AppWidgetProvider是一个BroadcastReceiver,明白这一点其实也就是理解AppWidget实现原理了。在onUpdate中提供了AppWidgetManager参数,这个AppWidgetMananger又是什么呢?下面来介绍一下。

AppWidgetManager

名如其义,AppWidgetManager你可以理解为AppWidget的管理接口(实际上它只提供了管理AppWidget的部分接口,后面的文章会介绍真正管理AppWidget的接口,也就是AppWidget 的client)。我们可以用AppWidgetManager来更新AppWidget的内容,给AppWidget绑定id。AppWidgetManager是在实现AppWidgetProvider的时候,经常会用到的类,可以用AppWidgetManager.updateAppWidget来更新AppWidget。如果是有AdapterView的AppWidget,可以用AppWidgetManager.notifyAppWidgetViewDataChanged来通知Adapter的变化,用partiallyUpdateAppWidget部分更新Widget。这几个方法是经常在AppWidgetProvider的onUpdate方法中使用的。这几个方法的原型如下:



public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId)

public void partiallyUpdateAppWidget(int appWidgetId, RemoteViews views)

public void updateAppWidget(int[] appWidgetIds, RemoteViews views)

另外可以通过getAppWidgetIds获取AppWidgetProvider组件对应的AppWidget的Id数组,我们经常通过这个方法获取所有的id,然后来更新所有的跟AppWidgetProvider相关的AppWidget。



public int[] getAppWidgetIds(ComponentName provider)

另外还有bindAppWidgetIdIfAllowed方法和getInstalledProviders,这两个方法主要是在Launcher应用当中使用,分配AppWidget的id,然后用bindAppWidgetIdIfAllowed绑定AppWidgetProvider与id。用getInstalledProviders方法获取已经插入的AppWidgetProvider,可以用来供用户选择添加到桌面。


public boolean bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider,
       Bundle options)

public List<AppWidgetProviderInfo> getInstalledProviders()

关于launcher可以参考aosp的源码:http://arnab.ch/blog/2013/08/how-to-write-custom-launcher-app-in-android/。我们也可以自己开发launcher应用。

RemoteView

RemoteView顾名思义就是指远程View,通过本地进程修改RemoteView,能够使这些修改通过RemoteView为载体传递到远程进程。RemoteView不仅仅在AppWidget中有使用,Notification中也是使用了RemoteView。下面简单介绍一下RemoteView的实现原理。

RemoteView继承自Parcelable,所以RemoteView本身就是可以跨进程传递的。RemoteView有个内部类叫做Action,它也是继承自Parcelable,对于不同的操作,RemoteView内部实现了不同的Action。

比如点击事件,RemoteView内部有一个SetOnClickPendingIntent,它也是继承自Action,设置点击事件就是将点击事件保存在这里面,把点击事件作为一个Action。可以看看它的源码:


private class SetOnClickPendingIntent extends Action {
   public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
       this.viewId = id;
       this.pendingIntent = pendingIntent;
   }

   public SetOnClickPendingIntent(Parcel parcel) {
       viewId = parcel.readInt();

       // We check a flag to determine if the parcel contains a PendingIntent.
       if (parcel.readInt() != 0) {
           pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
       }
   }

   public void writeToParcel(Parcel dest, int flags) {
       dest.writeInt(TAG);
       dest.writeInt(viewId);

       // We use a flag to indicate whether the parcel contains a valid object.
       dest.writeInt(pendingIntent != null ? 1 : 0);
       if (pendingIntent != null) {
           pendingIntent.writeToParcel(dest, 0 /* no flags */);
       }
   }

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

       // If the view is an AdapterView, setting a PendingIntent on click doesn‘t make much
       // sense, do they mean to set a PendingIntent template for the AdapterView‘s children?
       if (mIsWidgetCollectionChild) {
           Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
                   "(id: " + viewId + ")");
           ApplicationInfo appInfo = root.getContext().getApplicationInfo();

           // We let this slide for HC and ICS so as to not break compatibility. It should have
           // been disabled from the outset, but was left open by accident.
           if (appInfo != null &&
                   appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
               return;
           }
       }

       // If the pendingIntent is null, we clear the onClickListener
       OnClickListener listener = null;
       if (pendingIntent != null) {
           listener = new OnClickListener() {
               public void onClick(View v) {
                   // Find target view location in screen coordinates and
                   // fill into PendingIntent before sending.
                   final Rect rect = getSourceBounds(v);

                   final Intent intent = new Intent();
                   intent.setSourceBounds(rect);
                   handler.onClickHandler(v, pendingIntent, intent);
               }
           };
       }
       target.setOnClickListener(listener);
   }
...

}

其中以Parcel为参数的构造函数相当于是从Parcel当中读取内容,而writeToParcel是将Action写入到Parcel。这两个函数是用来传递Action用的。而apply方法则是将Action解析出来,设置监听(这种监听是远程监听,在onClick方法里面发送一个PendingIntent),具体实现在RemoteView的OnClickHandler中。其他的Action也是类似的。关于Parcel实现原理可以借鉴我之前写的两篇关于Bitmap传输的文章:Android4.0 Bitmap Parcel传输源码分析,Android6.0 Bitmap存储以及Parcel传输源码分析

RemoteView里面有一个mActions变量,是Action的列表。通过Parcel传递RemoteView的时候,RemoteView会将mActions里面的内容都写入到Parcel中。在readParcel的时候,使用Action的带Parcel参数的构造函数从Parcel里面读取Action,进行设置。Action其实就是一种模板方法模式。RemoteView很多设置是跟普通的View不一样的,RemoteView是一个能够跨进程设置相关内容的,如果需要设置监听函数之类的,只能设置PendingIntent。可以看看RemoteView里面的CREATOR和RemoteView对应的构造方法:


/**
* Parcelable.Creator that instantiates RemoteViews objects
*/
public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
   public RemoteViews createFromParcel(Parcel parcel) {
       return new RemoteViews(parcel);
   }

   public RemoteViews[] newArray(int size) {
       return new RemoteViews[size];
   }
};

...

public RemoteViews(Parcel parcel) {
   this(parcel, null);
}

private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {//实际上在构造函数中读取Parcel的内容,重新创建一个mActions
   int mode = parcel.readInt();

   // We only store a bitmap cache in the root of the RemoteViews.
   if (bitmapCache == null) {
       mBitmapCache = new BitmapCache(parcel);
   } else {
       setBitmapCache(bitmapCache);
       setNotRoot();
   }

   if (mode == MODE_NORMAL) {
       mApplication = parcel.readParcelable(null);
       mLayoutId = parcel.readInt();
       mIsWidgetCollectionChild = parcel.readInt() == 1;

       int count = parcel.readInt();
       if (count > 0) {
           mActions = new ArrayList<Action>(count);
           for (int i=0; i<count; i++) {
               int tag = parcel.readInt();
               switch (tag) {
                   case SetOnClickPendingIntent.TAG:
                       mActions.add(new SetOnClickPendingIntent(parcel));
                       break;
                   case SetDrawableParameters.TAG:
                       mActions.add(new SetDrawableParameters(parcel));
                       break;
                    ...
                   default:
                       throw new ActionException("Tag " + tag + " not found");
               }
           }
       }
   } else {
       // MODE_HAS_LANDSCAPE_AND_PORTRAIT
       mLandscape = new RemoteViews(parcel, mBitmapCache);
       mPortrait = new RemoteViews(parcel, mBitmapCache);
       mApplication = mPortrait.mApplication;
       mLayoutId = mPortrait.getLayoutId();
   }

   // setup the memory usage statistics
   mMemoryUsageCounter = new MemoryUsageCounter();
   recalculateMemoryUsage();
}

最后调用RemoteView的apply方法就在远程进程设置了相关内容了。我们AppWidget的远程进程是Launcher应用所在的进程。由AppWidgetHost管理这些。

AppWidgetHost

这个类是Android提供的供应用与AppWidget service交互的类,我们的AppWidgetProvider提供了Widget,而AppWidgetHost则是读取Widget,将它显示出来。一般在home screen中使用,也就是我们的桌面,Launcher。与之相关的还有个AppWidgetHostView,由AppWidgetHost创建,它与AppWidgetProvider对应。可以看一下AppWidgetHost的createView方法:


public final AppWidgetHostView createView(Context context, int appWidgetId,
       AppWidgetProviderInfo appWidget) {
   AppWidgetHostView view = onCreateView(context, appWidgetId, appWidget);
   view.setOnClickHandler(mOnClickHandler);
   view.setAppWidget(appWidgetId, appWidget);
   synchronized (mViews) {
       mViews.put(appWidgetId, view);
   }
   RemoteViews views;
   try {
       views = sService.getAppWidgetViews(mContextOpPackageName, appWidgetId);
   } catch (RemoteException e) {
       throw new RuntimeException("system server dead?", e);
   }
   view.updateAppWidget(views);

   return view;
}

这个主要是在launcher应用使用,就不详细介绍了。

总结

这一篇主要是介绍AppWidget相关的一些类,分析里面的源码功能,以及实现方式。通过深入了解它的实现方式才能够更好地使用它,分析遇到的问题。下一篇将从源码角度上介绍一些方法的处理流程。

时间: 2024-08-01 10:34:01

AppWidget源码分析---接口类的相关文章

Struts2 源码分析——Result类实例

本章简言 上一章笔者讲到关于DefaultActionInvocation类执行action的相关知识.我们清楚的知道在执行action类实例之后会相关处理返回的结果.而这章笔者将对处理结果相关的内容进行讲解.笔者叫他们为Result类实例.如果还记得在上一章最后笔者说可以把处理action执行的结果简单的理解处理网页.而且还用红色标识.实际是处理跟表现层有关的内容.而不是页面上的内容.如HTML.即是MVC里面的C到V的内容.当然这还关系到配置文件里面的result元素节点信息.如strtus

AppWidget源码分析(2)---updateAppWidget过程分析.md

前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程.由远程进程解析RemoteView,然后

AppWidget源码分析---updateAppWidget过程分析

转[原文] 前面一篇文章,分析了AppWidgetProvider和RemoteView的源码,从中我们可以知道它们的实现原理,AppWidgetProvider是一个BroadcastReceiver,所以它是通过广播接收通知的,收到更新通知后,AppWidgetProvider需要去提供View供远程进程显示,而提供的View则是使用RemoteView来代替,通过RemoteView(是一个Parcelable,可跨进程传输数据类型)来作为媒介去传递给远程进程.由远程进程解析RemoteV

tornado框架源码分析---Application类之debug参数

先贴上Application这个类的源码. class Application(httputil.HTTPServerConnectionDelegate): """A collection of request handlers that make up a web application. Instances of this class are callable and can be passed directly to HTTPServer to serve the a

[转]springSecurity源码分析—DelegatingFilterProxy类的作用

使用过springSecurity的朋友都知道,首先需要在web.xml进行以下配置, <filter>  <filter-name>springSecurityFilterChain</filter-name>  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param>         <para

yii2 源码分析 model类分析 (五)

模型类是数据模型的基类.此类继承了组件类,实现了3个接口 先介绍一下模型类前面的大量注释说了什么: * 模型类是数据模型的基类.此类继承了组件类,实现了3个接口 * 实现了IteratorAggregate(聚合式迭代器)接口,实现了ArrayAccess接口,可以像数组一样访问对象,这两个接口是php自带 * Arrayable接口是yii2框架自带 * 模型实现了以下常用功能: * * - 属性声明: 默认情况下,每个公共类成员都被认为是模型属性 * - 属性标签: 每个属性可以与用于显示目

yii2 源码分析 Action类分析 (六)

Action类是控制器的基类, <?php namespace yii\base; use Yii; /** * Action是所有控制器动作类的基类,它继承组件类 * * 动作提供了重用动作方法代码的方法, * Action类中的动作方法可以用于多个控制器或不同的项目中. * * 派生类必须实现一个方法叫` run() ` * 当请求操作时,该方法将由控制器调用 * `run()` 方法的参数由用户根据他们的名字自动输入的值确定 * 例如, `run()`方法以以下形式定义 * * ```ph

yii2 源码分析Action类分析 (六)

Action类是控制器的基类, <?php namespace yii\base; use Yii; /** * Action是所有控制器动作类的基类,它继承组件类 * * 动作提供了重用动作方法代码的方法, * Action类中的动作方法可以用于多个控制器或不同的项目中. * * 派生类必须实现一个方法叫` run() ` * 当请求操作时,该方法将由控制器调用 * `run()` 方法的参数由用户根据他们的名字自动输入的值确定 * 例如, `run()`方法以以下形式定义 * * ```ph

yii2 源码分析Behavior类分析 (四)

Behavior类是所有事件类的基类,它继承自object类 Behavior类的前面注释描述大概意思: * Behavior类是所有事件类的基类 * * 一个行为可以用来增强现有组件的功能,而不需要修改它的代码. * 用来增强现有组件的功能而不修改它的代码.它可以添加自己的方法和属性组件 * 使他们可以直接通过组件访问.还可以响应组件触发的事件,拦截正常的代码执行. class Behavior extends Object { /** * 要附加行为对象的组件 */ public $owne