自定義Widget小組件及原理分析

1.使用

1.1 自定義Widget

創(chuàng)建一個可以被其他進程加載的Widget,就是要把創(chuàng)建的RemoteViews傳入AppWidgetManager。
RemoteViews并不是真正的View,它儲存著構(gòu)建View所需的信息,使用Widget的進程獲取到RemoteViews后就可以構(gòu)建Widget了。

public class AlarmWidget extends AppWidgetProvider {
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // widget更新時觸發(fā)。因為可能有好幾個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可用時觸發(fā)
    }
    @Override
    public void onDisabled(Context context) {
        // widget不可用時觸發(fā)
    }
    @Override
    public void onDeleted(Context context, int[] appWidgetIds) {
        // widget被刪除時觸發(fā)
    }
    @Override
    public void onReceive(Context context, Intent intent) {
        // 收到指定的廣播時觸發(fā)。可以啟動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支持的布局和控件有限,所以創(chuàng)建布局時需要注意。
然后,在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 調(diào)用自定義Widget

類圖

加載widget
        //其參數(shù)hostid大意是指定該AppWidgetHost 即本Activity的標(biāo)記Id, 直接設(shè)置為一個整數(shù)值吧 。  
        mAppWidgetHost = new AppWidgetHost(MainActivity.this, HOST_ID) ;             
        //為了保證AppWidget的及時更新 , 必須在Activity的onCreate/onStar方法調(diào)用該方法  
        // 當(dāng)然可以在onStop方法中,調(diào)用mAppWidgetHost.stopListenering() 停止AppWidget更新  
        mAppWidgetHost.startListening() ;            
        //獲得AppWidgetManager對象  
        appWidgetManager = AppWidgetManager.getInstance(MainActivity.this) ;  
        btAddShortCut.setOnClickListener(new View.OnClickListener()  
        {  
            @Override  
            public void onClick(View v)  
            {  
                 //顯示所有能創(chuàng)建AppWidget的列表 發(fā)送此 ACTION_APPWIDGET_PICK 的Action  
                 Intent  pickIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK) ;                     
                 //向系統(tǒng)申請一個新的appWidgetId ,該appWidgetId與我們發(fā)送Action為ACTION_APPWIDGET_PICK  
                 //  后所選擇的AppWidget綁定 。 因此,我們可以通過這個appWidgetId獲取該AppWidget的信息了                     
                 //為當(dāng)前所在進程申請一個新的appWidgetId   
                 int newAppWidgetId = mAppWidgetHost.allocateAppWidgetId() ;                     
                 //作為Intent附加值 , 該appWidgetId將會與選定的AppWidget綁定                 
                 pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, newAppWidgetId) ;                     
                 //選擇某項AppWidget后,立即返回,即回調(diào)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 ;  
            //設(shè)置長寬  appWidgetProviderInfo 對象的 minWidth 和  minHeight 屬性  
            LinearLayout.LayoutParams linearLayoutParams = new LinearLayout.LayoutParams(widget_minWidht, widget_minHeight);  
            //添加至LinearLayout父視圖中  
            linearLayout.addView(hostView,linearLayoutParams) ;  
        }
}

2.原理分析

2.1 RemoteViews的序列化

RemoteViews要實現(xiàn)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;

就可以在服務(wù)中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

從上面的序列圖可以看到,調(diào)用AppWidgetHost的createView方法來獲取AppWidgetHostView,是先通過AppWidgetService在注冊的所有Widget中,識別到對應(yīng)的Widget類,然后轉(zhuǎn)為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,返回給調(diào)用者:

//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;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容