app widget添加流程

先看一下app widget的添加代码,此处直接引用http://www.cnblogs.com/wanqieddy/archive/2012/05/05/2484533.html的代码

package com.qin.addappwidget;  
  
  
import android.app.Activity;  
import android.appwidget.AppWidgetHost;  
import android.appwidget.AppWidgetHostView;  
import android.appwidget.AppWidgetManager;  
import android.appwidget.AppWidgetProviderInfo;  
import android.content.Intent;  
import android.os.Bundle;  
import android.util.Log;  
import android.view.View;  
import android.widget.Button;  
import android.widget.ImageView;  
import android.widget.LinearLayout;  
import android.widget.TextView;  
import android.widget.Toast;  
  
public class MainActivity extends Activity  
{  
  
    private static String TAG = "AddAppWidget" ;  
      
    private Button btAddShortCut;  
    private LinearLayout linearLayout ;  // 装载Appwidget的父视图  
      
    private static final int MY_REQUEST_APPWIDGET = 1;  
    private static final int MY_CREATE_APPWIDGET = 2;  
      
    private static final int HOST_ID = 1024 ;  
      
    private AppWidgetHost  mAppWidgetHost = null ;  
    AppWidgetManager appWidgetManager = null;  
      
    /** Called when the activity is first created. */  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {   
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
  
        btAddShortCut = (Button) findViewById(R.id.bt_addShortcut);  
  
        linearLayout = (LinearLayout)findViewById(R.id.linearLayout) ;  
        
        //其参数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() ;  
                 Log.i(TAG, "The new allocate appWidgetId is ----> " + newAppWidgetId) ;  
                   
                 //作为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)  
    {  
        //直接返回,没有选择任何一项 ,例如按Back键  
        if(resultCode == RESULT_CANCELED)  
           return ;  
          
        switch(requestCode){  
            case MY_REQUEST_APPWIDGET :  
                Log.i(TAG, "MY_REQUEST_APPWIDGET intent info is -----> "+data ) ;  
                int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ;  
                  
                Log.i(TAG, "MY_REQUEST_APPWIDGET : appWidgetId is ----> " + appWidgetId) ;  
                  
                //得到的为有效的id  
                if(appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID){  
                    //查询指定appWidgetId的 AppWidgetProviderInfo对象 , 即在xml文件配置的<appwidget-provider />节点信息  
                    AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ;  
                      
                    //如果配置了configure属性 , 即android:configure = "" ,需要再次启动该configure指定的类文件,通常为一个Activity  
                    if(appWidgetProviderInfo.configure != null){  
                          
                        Log.i(TAG, "The AppWidgetProviderInfo configure info -----> " + appWidgetProviderInfo.configure ) ;  
                          
                        //配置此Action  
                        Intent intent  = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE) ;  
                        intent.setComponent(appWidgetProviderInfo.configure) ;  
                        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);  
                          
                          
                        startActivityForResult(intent , MY_CREATE_APPWIDGET) ;                 
                    }  
                    else  //直接创建一个AppWidget  
                        onActivityResult(MY_CREATE_APPWIDGET , RESULT_OK , data) ;  //参数不同,简单回调而已                         
                }             
                break ;  
            case  MY_CREATE_APPWIDGET:  
                completeAddAppWidget(data) ;  
                break ;  
        }  
          
    }  
     
    //向当前视图添加一个用户选择的  
    private void completeAddAppWidget(Intent data){  
        Bundle extra = data.getExtras() ;  
        int appWidgetId = extra.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID , -1) ;  
        //等同于上面的获取方式  
        //int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID , AppWidgetManager.INVALID_APPWIDGET_ID) ;  
          
        Log.i(TAG, "completeAddAppWidget : appWidgetId is ----> " + appWidgetId) ;  
          
        if(appWidgetId == -1){  
            Toast.makeText(MainActivity.this, "添加窗口小部件有误", Toast.LENGTH_SHORT) ;  
            return ;  
        }  
          
        AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ;  
          
        AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo);  
                  
        //linearLayout.addView(hostView) ;   
          
        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) ;         
    }  
} 

大致流程如下:

1. 实例化AppWidgetHost, 具体实例化代码如下:

    public AppWidgetHost(Context context, int hostId) {
        this(context, hostId, null, context.getMainLooper());
    }

    /**
     * @hide
     */
    public AppWidgetHost(Context context, int hostId, OnClickHandler handler, Looper looper) {
        mContext = context;
        mHostId = hostId;
        mOnClickHandler = handler;
        mHandler = new UpdateHandler(looper);
        mDisplayMetrics = context.getResources().getDisplayMetrics();
        bindService();
    }

该AppWidgetHost保存应用的Context对象,hostId,同时实例化一个Handler(和Launcher主线程共用一个MessageQueue),实例化一个IAppWidgetServer对象用于与AppWidgetService服务通信。

2. 通知AppWidgetService自身可以接收消息

    /**
     * Start receiving onAppWidgetChanged calls for your AppWidgets.  Call this when your activity
     * becomes visible, i.e. from onStart() in your Activity.
     */
    public void startListening() {
        Log.d(TAG, "startListening");
        int[] updatedIds;
        ArrayList<RemoteViews> updatedViews = new ArrayList<RemoteViews>();

        final int userId = mContext.getUserId();
        try {
            if (mPackageName == null) {
                mPackageName = mContext.getPackageName();
            }
            updatedIds = sService.startListening(
                    mCallbacks, mPackageName, mHostId, updatedViews, userId);
        }
        catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }

        final int N = updatedIds.length;
        for (int i=0; i<N; i++) {
            if (updatedViews.get(i) != null) {
                updatedViews.get(i).setUser(new UserHandle(userId));
            }
            updateAppWidgetView(updatedIds[i], updatedViews.get(i), userId);
        }
    }

sService.startListening最终调用在AppWidgetServiceImpl中,
    public int[] startListening(IAppWidgetHost callbacks, String packageName, int hostId,
            List<RemoteViews> updatedViews) {
        log("startListening");
        if (!mHasFeature) {
            return new int[0];
        }
        int callingUid = enforceCallingUid(packageName);
        synchronized (mAppWidgetIds) {
            ensureStateLoadedLocked();
            Host host = lookupOrAddHostLocked(callingUid, packageName, hostId);
            host.callbacks = callbacks;

            updatedViews.clear();

            ArrayList<AppWidgetId> instances = host.instances;
            int N = instances.size();
            int[] updatedIds = new int[N];
            for (int i = 0; i < N; i++) {
                AppWidgetId id = instances.get(i);
                updatedIds[i] = id.appWidgetId;
                updatedViews.add(cloneIfLocalBinder(id.views));
            }
            return updatedIds;
        }
    }

在AppWidgetService中mHost保存所有已注册的Host,此处会查找已经存在的或者添加一个新Host对象,同时记录AppWidgetHost的callback,更新updateViews列表为所有已添加到Host的插件的RemoteViews,最后返回该Host中所有插件的id。

appWidgetHost在取得自身所有已经添加的插件 id后,会先为对应的RemoteViews设定UserHandle,然后更新对应的AppWidgetHostView。

3. 获取新分配的appWidget id

    /**
     * Get a appWidgetId for a host in the calling process.
     *
     * @return a appWidgetId
     */
    public int allocateAppWidgetId() {
        try {
            if (mPackageName == null) {
                mPackageName = mContext.getPackageName();
            }
            return sService.allocateAppWidgetId(mPackageName, mHostId, mContext.getUserId());
        }
        catch (RemoteException e) {
            throw new RuntimeException("system server dead?", e);
        }
    }
AppWidgetServiceImpl会分配一个appWidget id,然后实例化一个AppWidgetId对象,保存到该Host和mAppWidgetIds中,同时也保存到xml文件中(缺少provider元素)。

4. bind appwidgetid

在上例代码中是通过发送intent的方式完成,当用户选择需要添加的widget后则会调用AppWidgetManager.bindAppWidgetIdIfAllowed(int appWidgetId, ComponentName provider, Bundle options) 方法,具体的实现在AppWidgetServiceImpl中

    public boolean bindAppWidgetIdIfAllowed(
            String packageName, int appWidgetId, ComponentName provider, Bundle options) {
        if (!mHasFeature) {
            return false;
        }
        try {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BIND_APPWIDGET, null);
        } catch (SecurityException se) {
            if (!callerHasBindAppWidgetPermission(packageName)) {
                return false;
            }
        }
        bindAppWidgetIdImpl(appWidgetId, provider, options);
        return true;
    }

    private void bindAppWidgetIdImpl(int appWidgetId, ComponentName provider, Bundle options) {
        if (DBG) log("bindAppWidgetIdImpl appwid=" + appWidgetId
                + " provider=" + provider);
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mAppWidgetIds) {
                AppWidgetId id = lookupAppWidgetIdLocked(appWidgetId);
                Provider p = lookupProviderLocked(provider);
		// ...检查数据
                id.provider = p;
                id.options = options;

                p.instances.add(id);
                int instancesSize = p.instances.size();
                if (instancesSize == 1) {
                    // tell the provider that it's ready
                    sendEnableIntentLocked(p);
                }

                // send an update now -- We need this update now, and just for this appWidgetId.
                // It's less critical when the next one happens, so when we schedule the next one,
                // we add updatePeriodMillis to its start time. That time will have some slop,
                // but that's okay.
                sendUpdateIntentLocked(p, new int[] { appWidgetId });

                // schedule the future updates
                registerForBroadcastsLocked(p, getAppWidgetIds(p));
                saveStateAsync();
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

可以看到bindAppWidgetId主要是保存新Provider信息,同时发送enable和update广播通知应用Provider处理,最后将插件信息写入磁盘。

5. configure appWidget

此处略过

6. 显示appWidget

bind和configure成功后就可以显示appWidget到AppWidgetHostView中,整个添加过程结束。

        AppWidgetProviderInfo appWidgetProviderInfo = appWidgetManager.getAppWidgetInfo(appWidgetId) ;  
          
        AppWidgetHostView hostView = mAppWidgetHost.createView(MainActivity.this, appWidgetId, appWidgetProviderInfo);  
                  
        //linearLayout.addView(hostView) ;   
          
        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) ; 

下一篇再介绍createView的具体流程以及应用如何通知桌面来刷新数据。


前面的步骤3中有一个疑问:在AppWidgetServiceImpl中分配了一个widget id,同时保存了数据,但是如果后期用户后悔,取消添加插件会如何?基本做法应该为在接收到RESULT_CANCELED结果时通过AppWidgetHost删除已分配但是未添加的插件。



猜你喜欢

转载自blog.csdn.net/w_xue/article/details/43309973