天天看点

Android 之 AppWidget

之前在项目上碰到了一个问题:刚开机的时候点击主屏幕的全局搜索框没反应,需要等几十秒之后才会正常反应。

刚开始猜测是在等待什么初始化条件,但从应用栏里启动全局搜索却很快,因此可以排除这种情况,于是打算从源码层去看看。

在launcher里并未找到这个全局搜索的相关代码,因此怀疑这是一个AppWidget,在android代码中全局搜索“android.appwidget.action.APPWIDGET_UPDATE”,搜索这个的原因是按照的AppWidget的实现标准都需要在AndroidManifest.xml里进行声明,否则不会在AppWidget列表里显示,果然在QuickSearchBox里发现了有提供搜索的AppWidget。看下实现的类

public class SearchWidgetProvider extends BroadcastReceiver 
{
......
}
           

直接继承的BroadcastReceiver ?  一般来讲,实现一个AppWidget都是继承至AppWidgetProvider,然后重写里面的一些函数,这里却直接继承了BroadcastReceiver,去看看AppWidgetProvider都干了什么。

public class AppWidgetProvider extends BroadcastReceiver {
    /**
     * Constructor to initialize AppWidgetProvider.
     */
    public AppWidgetProvider() {
    }

    /**
     * Implements {@link BroadcastReceiver#onReceive} to dispatch calls to the various
     * other methods on AppWidgetProvider.
     *
     * @param context The Context in which the receiver is running.
     * @param intent The Intent being received.
     */
    // 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);
                }
            }
        }
    }
    // END_INCLUDE(onReceive)

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
    }

    public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
            int appWidgetId, Bundle newOptions) {
    }

    public void onDeleted(Context context, int[] appWidgetIds) {
    }

    public void onEnabled(Context context) {
    }

    public void onDisabled(Context context) {
    }

    public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds) {
    }
           

原来AppWidgetProvider也是继承至BroadcastReceiver,实现了onReceive方法,定义了一些自定义的函数。在onReceive方法中接收到不同的广播调用不同的函数。因此AppWidgetProvider并不是重点,直接跳过它也是可行的。

接下来看看AppWidget是如何生效的。

@Override
    public void onReceive(Context context, Intent intent) {
        if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0) + ")");
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            // nothing needs doing
        } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            Log.d(TAG, "update received");
            updateSearchWidgets(context);
        }else {
            if (DBG) Log.d(TAG, "Unhandled intent action=" + action);
        }
    }
           

收到ACTION_APPWIDGET_UPDATE广播后更新widget。

public void updateWidget(Context context,AppWidgetManager appWidgetMgr) {
            if (DBG) Log.d(TAG, "Updating appwidget " + mAppWidgetId);
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget);

            setOnClickActivityIntent(context, views, R.id.search_widget_text,
                    mQueryTextViewIntent);
            // Voice Search button
            if (mVoiceSearchIntent != null) {
                setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn,
                        mVoiceSearchIntent);
                views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE);
            } else {
                views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE);
            }

            appWidgetMgr.updateAppWidget(mAppWidgetId, views);
        }
           

通过layout生成AppWidget的显示画面,设置显示画面上的交互事件,这里有一个输入框和一个语音按钮,因此分比对两个控件设置的响应的Intent,最后调用AppWidgetManager对widget进行更新。可以看出appWidgetMgr.updateAppWidget(mAppWidgetId, views)这里才是真正让AppWidget生效的部分。

那AndroidMainefest中声明android.appwidget.action.APPWIDGET_UPDATE是干什么的呢?

<receiver android:name=".SearchWidgetProvider"
                  android:label="@string/app_name">
            <intent-filter android:priority="1000">
                    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                    <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
            <meta-data android:name="android.appwidget.provider" android:resource="@xml/search_widget_info" />
</receiver>
           

有两个用途:

1. 接收AppWidget相关的几个广播

2. 将其声明成AppWidget,如果不按照这种方式,那AppWidget列表里将不会显示。

现在回到问题,为什么刚开机时,AppWidget不响应呢?其实就是刚开机时,还没有收到广播,未设定响应它的Intent。如果要解决它,有两种办法。

1. 将广播“android.appwidget.action.APPWIDGET_UPDATE”改为前台广播,提升广播的处理速度。

android/framework/base/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java

private void sendUpdateIntentLocked(Provider provider, int[] appWidgetIds) {
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);
        intent.setComponent(provider.info.provider);
        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);    //设置为前台广播
        sendBroadcastAsUser(intent, provider.info.getProfile());
    }
           

2. 通过其他手段来启动AppWidget设置流程。在其他流程不变的情况下,可以同时注册开机广播,在开机广播中做AppWidget设定的流程。当然也可以通过其它应用,比如launcher等启动比较早的应用里,主动调起提供AppWidget的应用并执行此流程。

@Override
    public void onReceive(Context context, Intent intent) {
        if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0) + ")");
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            // nothing needs doing
        } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            updateSearchWidgets(context);
        } else if (ACTION_BOOT.equals(action)) {
            updateSearchWidgets(context);        //收到开机广播,执行appwidget更新流程
        }else {
            if (DBG) Log.d(TAG, "Unhandled intent action=" + action);
        }
    }
           

注:以上为个人分析心得,如有错误,欢迎指正。

原文地址: https://blog.csdn.net/chengchaooppo/article/details/97259706