美文网首页
自定义Widget小组件及原理分析

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

作者: 浪里_个郎 | 来源:发表于2021-08-20 14:24 被阅读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;
    }
    

    相关文章

      网友评论

          本文标题:自定义Widget小组件及原理分析

          本文链接:https://www.haomeiwen.com/subject/yjadbltx.html