AppWidget的使用及原理分析

一 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() 也是同理,这里不再叙述了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android AppWidget 可以通过 AlarmManager 进行实时更新。AlarmManager 是一个系统级别的服务,用于在指定时间触发指定的操作,包括启动服务、广播等。 以下是使用 AlarmManager 进行 AppWidget 实时更新的步骤: 1. 在 AppWidgetProvider 类中创建一个 PendingIntent 对象: ```java Intent intent = new Intent(context, AppWidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); ``` 2. 使用 AlarmManager 设置定时任务: ```java AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), intervalMillis, pendingIntent); ``` 其中,`intervalMillis` 是更新间隔时间,以毫秒为单位。 3. 在 AppWidgetProvider 类中重写 onUpdate() 方法,并在其中处理更新操作。注意,需要在 onEnabled() 方法中注册 AlarmManager,同时在 onDisabled() 方法中取消注册。 ```java @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 处理更新操作 } @Override public void onEnabled(Context context) { super.onEnabled(context); // 注册 AlarmManager } @Override public void onDisabled(Context context) { super.onDisabled(context); // 取消注册 AlarmManager } ``` 这样就可以通过 AlarmManager 实现 AppWidget 的实时更新了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值