自定义Widget小组件及原理分析

2021-08-20  本文已影响0人  浪里_个郎

1.使用

1.1 自定义Widget

创建一个可以被其他进程加载的Widget,就是要把创建的RemoteViews传入AppWidgetManager。
RemoteViews并不是真正的View,它储存着构建View所需的信息,使用Widget的进程获取到RemoteViews后就可以构建Widget了。

public class AlarmWidget extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // widget更新时触发。因为可能有好几个widget实例,所以是appWidgetIds
        for (int appWidgetId : appWidgetIds) {
            CharSequence widgetText = context.getString(R.string.appwidget_text);
            // Construct the RemoteViews object
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.alarm_widget);
            views.setTextViewText(R.id.appwidget_text, widgetText);
            // Instruct the widget manager to update the widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }
    }
    @Override
    public void onEnabled(Context context) {
        // widget可用时触发
    }
    @Override
    public void onDisabled(Context context) {
        // widget不可用时触发
    }
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // widget被删除时触发
    }
    @Override
    public void onReceive(Context context, Intent intent) {
        // 收到指定的广播时触发。可以启动Service
    }
}

别忘记注册到AndroidManifest.xml,需要带上xml定义,里面指定了widget的layout:

        <receiver android:name=".widget.AlarmWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

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

RemoteViews支持的布局和控件有限,所以创建布局时需要注意。
然后,在Service中,把自定义Widget注册进AppWidgetManager。如果自定义Widget存在Activity,记得将Service和Activity进行进程分离,降低Service在后台时被kill的概率。

appWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
provider = new ComponentName(getApplicationContext(), AlarmWidget.class);
appWidgetManager.updateAppWidget(provider, remoteViews);

1.2 调用自定义Widget

类图
加载widget
        //其参数hostid大意是指定该AppWidgetHost 即本Activity的标记Id, 直接设置为一个整数值吧 。  
        mAppWidgetHost = new AppWidgetHost(MainActivity.this, HOST_ID) ;             
        //为了保证AppWidget的及时更新 , 必须在Activity的onCreate/onStar方法调用该方法  
        // 当然可以在onStop方法中,调用mAppWidgetHost.stopListenering() 停止AppWidget更新  
        mAppWidgetHost.startListening() ;            
        //获得AppWidgetManager对象  
        appWidgetManager = AppWidgetManager.getInstance(MainActivity.this) ;  
        btAddShortCut.setOnClickListener(new View.OnClickListener()  
        {  
            @Override  
            public void onClick(View v)  
            {  
                 //显示所有能创建AppWidget的列表 发送此 ACTION_APPWIDGET_PICK 的Action  
                 Intent  pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK) ;                     
                 //向系统申请一个新的appWidgetId ,该appWidgetId与我们发送Action为ACTION_APPWIDGET_PICK  
                 //  后所选择的AppWidget绑定 。 因此,我们可以通过这个appWidgetId获取该AppWidget的信息了                     
                 //为当前所在进程申请一个新的appWidgetId   
                 int newAppWidgetId = mAppWidgetHost.allocateAppWidgetId() ;                     
                 //作为Intent附加值 , 该appWidgetId将会与选定的AppWidget绑定                 
                 pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, newAppWidgetId) ;                     
                 //选择某项AppWidget后,立即返回,即回调onActivityResult()方法   
                 startActivityForResult(pickIntent , MY_REQUEST_APPWIDGET) ;            
            }  
        });  
    }  

    protected void onActivityResult(int requestCode, int resultCode, Intent data)  
    {  
        if (requestCode == MY_REQUEST_APPWIDGET) {
            int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ;  
            AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ;  
            AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo);  
            int widget_minWidht = appWidgetProviderInfo.minWidth ;  
            int widget_minHeight = appWidgetProviderInfo.minHeight ;  
            //设置长宽  appWidgetProviderInfo 对象的 minWidth 和  minHeight 属性  
            LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(widget_minWidht, widget_minHeight);  
            //添加至LinearLayout父视图中  
            linearLayout.addView(hostView,linearLayoutParams) ;  
        }
}

2.原理分析

2.1 RemoteViews的序列化

RemoteViews要实现IPC传递,必然是可序列化的:

public class RemoteViews implements Parcelable, Filter {

aidl中定义(/home/ecarx/E02_BOXX1/frameworks/base/core/java/android/widget/RemoteViews.aidl)

package android.widget;

parcelable RemoteViews;

就可以在服务中IPC传递啦:
/home/ecarx/E02_BOXX1/frameworks/base/core/java/com/android/internal/appwidget/IAppWidgetService.aidl

void updateAppWidgetIds(String callingPackage, in int[] appWidgetIds, in RemoteViews views);

2.2 RmoteViews变身AppWidgetHostView

从上面的序列图可以看到,调用AppWidgetHost的createView方法来获取AppWidgetHostView,是先通过AppWidgetService在注册的所有Widget中,识别到对应的Widget类,然后转为RemoteViews返回给AppWidgetHost:

// AppWidgetServiceImpl
    private Widget lookupWidgetLocked(int appWidgetId, int uid, String packageName) {
        final int N = mWidgets.size();
        for (int i = 0; i < N; i++) {
            Widget widget = mWidgets.get(i);
            if (widget.appWidgetId == appWidgetId
                    && mSecurityPolicy.canAccessAppWidget(widget, uid, packageName)) {
                return widget;
            }
        }
        return null;
    }

然后AppWidgetHost在将RemoteViews组装成AppWidgetHostView,返回给调用者:

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

        return view;
}
上一篇下一篇

猜你喜欢

热点阅读