智能卡夹

一、需求:

对接第三方sdk将服务短信转为卡片并储存,按照display_time和dead_time在负一屏显示前两个卡片的remoteView

二、流程:

1、数据存储
在Application里就启动Service,并初始化第三方sdk。Service里注册ContentObserver,用来监听短信数据库变化,并读取短信cursor,将字段构造成sdk所需的entity并解析,将解析出来的结果存入智能卡夹intelcards的数据库并通过AlarmManager设置展示在负一屏时间的定时器。

//application里启动Service并初始化sdk
Intent updateWidgetService = new Intent(getApplicationContext(), IntelcardService.class);
getApplicationContext().startService(updateWidgetService);
initCardHolder(this);
//监听短信数据库
smsContentObserver = new SmsContentObserver(mHandler, this, SmsContentObserver.MSG_SMS_WHAT);
getContentResolver().registerContentObserver(Uri.parse("content://sms"), true, smsContentObserver);
//设置卡片在负一屏显示的定时器
 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
 Intent intent = new Intent(context, IntelcardService.class);
 intent.putExtra(FROM, SET_SHOW_ALARM);

 Bundle bundle = new Bundle();
 bundle.putInt(IntelCardContract.IntelCard.SOURCE_ID, source_id);
 intent.putExtras(bundle);

 PendingIntent serviceIntent = PendingIntent.getService(context, source_id, intent,PendingIntent.FLAG_UPDATE_CURRENT);
 am.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, serviceIntent);

2、定时显示
显示卡片的定时器到时间会触发pendingIntent让Service处理显示卡片流程:修改db里show字段为1,表示这行数据是需要显示的,同时设置显示数据的deadTime定时器,到时间会修改db的show字段值为0。最后通过发送ACTION_APPWIDGET_UPDATE广播通知AppWidgetProvider根据db里面show的字段显示widget。

//修改db show字段的值
    public static int updateWidget(Context context, int source_id, int showValue) {
        ContentValues contentValues = new ContentValues();
        contentValues.put(IntelCardContract.IntelCard.SHOW, showValue);
        return DBUtils.setShowValue(context, source_id, contentValues);
    }
//发送广播通知AppWidgetProvider更新
Intent updateWidgetIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
this.sendBroadcast(updateWidgetIntent);

3、AppWidgetProvider
智能卡片在负一屏主要是通过widget桌面小部件显示,显示的view是remoteView。桌面小部件是通过AppWidgetProvider实现的,它本质是一个广播。前面的service就是通过发送广播通知桌面小部件来更新。首先通过创建RemoteViews设置小部件的布局,移除之前RemoteViews里的所有view;然后查询字段show是1的数据,根据source_id获取sdk里的RemoteViews,并设置click事件的pendingIntent,将其add到RemoteViews里;最后通过AppWidgetManager.updateAppWidget更新widget。

//Manifest里定义AppWidgetProvider
        <receiver android:name=".provider.CardWidgetProvider">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>

            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/card_sms_provider" />
        </receiver>
//设置appWidget属性(更新频率,最小宽度高度等)
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="312dp"
    android:minHeight="280dp"
    android:updatePeriodMillis="100000"
    android:previewImage="@mipmap/ic_intelcards"
    android:initialLayout="@layout/widget_layout"
    android:resizeMode="horizontal|vertical"
    android:widgetCategory="home_screen"/>
    @Override
    public void onReceive(Context context, Intent intent) {
        if(mServiceHandler==null) {
            HandlerThread thread = new HandlerThread("getRemoteView");
            thread.start();
            Looper mHandlerLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mHandlerLooper);
        }

        //如果两次更新请求时间间隔小于30s,第二次更新就不处理
        long nowtime = System.currentTimeMillis();
        if (nowtime - lastPullTime < PERIOD) {
            Log.i(TAG, "onReceive update return");
            return;
        }
        Log.i(TAG, "onReceive update");
        lastPullTime = nowtime;
        if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
            AppWidgetManager awm = AppWidgetManager.getInstance(context);
            int[] mAppWidgetIds = awm.getAppWidgetIds(new ComponentName(context,
                    CardWidgetProvider.class));
            for (int appWidgetId : mAppWidgetIds) {
                //updateWidgets(context, appWidgetId);
                Message msg = mServiceHandler.obtainMessage();
                msg.obj=appWidgetId;
                mContext=context;
                mServiceHandler.sendMessage(msg);
            }
        }
    }
    public void updateWidgets(Context context, int appWidgetId) {
        AppWidgetManager awm = AppWidgetManager.getInstance(context);
        //设置remoteView的layout布局
        RemoteViews widgetViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout);

		//查询需要显示(show==1)的数据
        Cursor cursor = DBUtils.queryForShow(context);
        if (cursor != null && cursor.moveToFirst()) {

            widgetViews.removeAllViews(R.id.card1);
            widgetViews.removeAllViews(R.id.card2);
            widgetViews.setViewVisibility(R.id.cardll1, View.GONE);
            widgetViews.setViewVisibility(R.id.cardll2, View.GONE);
            widgetViews.setViewVisibility(R.id.diliver, View.GONE);
            Log.i(TAG, "cursor.getCount:" + cursor.getCount());
            if (cursor.getCount() == 1) {
                widgetViews.setViewVisibility(R.id.cardll1, View.VISIBLE);
                //将sdk返回的remoteviews add到布局的R.id.card1的位置
                addItemView(context, cursor, widgetViews, R.id.card1, R.id.card_menu1);
            }
            if (cursor.getCount() >= 2) {
                widgetViews.setViewVisibility(R.id.cardll1, View.VISIBLE);
                widgetViews.setViewVisibility(R.id.cardll2, View.VISIBLE);
                widgetViews.setViewVisibility(R.id.diliver, View.VISIBLE);
                boolean flag = false;
                do {
                    if (flag == false) {
                        flag = addItemView(context, cursor, widgetViews, R.id.card1, R.id.card_menu1);
                        Log.i(TAG, "flag:" + flag);
                    } else {
                        addItemView(context, cursor, widgetViews, R.id.card2, R.id.card_menu2);
                        cursor.close();
                        break;
                    }
                } while (cursor.moveToNext());
            }
            awm.updateAppWidget(appWidgetId, widgetViews);
        }
    }
private boolean addItemView(Context context, Cursor cursor, RemoteViews remoteViews, int cardId, int menuId) {
        //从数据库中获取source_id
        int source_id1 = cursor.getInt(cursor.getColumnIndex(SOURCE_ID));
        //从sdk中获取remoteViews
        RemoteViews views = getItemView(context, String.valueOf(source_id1));
        if (views == null) return false;
        //获取的remoteViews添加到cardId的位置
        remoteViews.addView(cardId, views);
        //设置pendingIntent:点击remoteViews的cardId区域显示这个卡片的详情
        Intent showDetail1 = new Intent(context, IntelcardService.class);
        showDetail1.setAction("showDetailIntent");
        showDetail1.putExtra(FROM, SHOW_DETAIL);
        Bundle detailBundle = new Bundle();
        detailBundle.putInt(IntelCardContract.IntelCard.SOURCE_ID, source_id1);
        showDetail1.putExtras(detailBundle);
        PendingIntent clickshowDetailIntent1 = PendingIntent.getService(context, source_id1, showDetail1, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(cardId, clickshowDetailIntent1);
        //设置pendingIntent:点击remoteViews的menuId区域显示这个卡片的详情
        Intent removeIntent1 = new Intent(context, IntelcardService.class);
        removeIntent1.setAction("removeIntent");
        removeIntent1.putExtra(FROM, SET_IGNORE_ALARM);
        Bundle bundle1 = new Bundle();
        bundle1.putInt(IntelCardContract.IntelCard.SOURCE_ID, source_id1);
        removeIntent1.putExtras(bundle1);
        PendingIntent removeService1 = PendingIntent.getService(context, source_id1, removeIntent1, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(menuId, removeService1);
        
        //设置PendingIntent:点击remoteViews的show_more_cards区域进入app
        Intent showMoreIntent = new Intent(context, IntelCardListActivity.class
        PendingIntent showMorePending = PendingIntent.getActivity(context, source_id1, showMoreIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.show_more_cards, showMorePending);
        return true;
    }

效果如图:
在这里插入图片描述
4、Launcher管理widget显示
负一屏属于Launcher,自定义控件RGKWidgetsRecyclerView继承自Linearlayout,设置contentObserver用来监听智能卡夹intelcards的数据库,根据show字段判断,如果数据库中没有需要显示的数据,那么intelcards的widget从负一屏移除,否则显示在负一屏上。
WidgetHost(比如launcher)在添加widget时会需要widgetId。所以如果要remove这个widget则同时要delete这个widgetId,不然wigetProvider里面getAppWidgetIds的数量越来越多,AppWidgetProvider里面的onReceive里面循环就越来越多了。(因为这个问题之前出现过ANR!!)

//监听intelcards应用的数据库变化
public static final Uri CONTENT_URI=Uri.parse("content://com.ragentek.intelcards/intelcards");
public ContentObserver cob = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            isIntelcardsShow();
        }
    };
public void isIntelcardsShow(){
		//获取show=1的数据,判断个数,count等于0,remove,大于0,show
        Cursor cursor = getContext().getContentResolver().query(CONTENT_URI, null,
                "show = 1", null, null);
        if(cursor==null){
            return;
        }
        Log.i(TAG,"intelcards db change,counts="+cursor.getCount());
        if(cursor.getCount()==0){
            isRemoveIntelcards=true;
            //launcher可由任意一个非空view转换而来
            Launcher mLauncher=(Launcher)mRecyclerView.getContext();
            final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost();
            DataBinder dataBinder=mListAdapterImpl.getDataBinder(VIEW_ITEM_INTELCARDS_WIDGET);
            if(dataBinder!=null&&dataBinder.getItemViewType()==ViewItemType.VIEW_ITEM_INTELCARDS_WIDGET){
                IntelcardsBinder intelcardsBinder= (IntelcardsBinder) dataBinder;
                if(intelcardsBinder!=null){
                	//获取当前intelcardsBinder的widgetID
                    int appWidgetId=intelcardsBinder.getmWidgetId();
                    Log.i(TAG,"appWidgetId:"+appWidgetId);
                    new AsyncTask<Void, Void, Void>() {
                        public Void doInBackground(Void... args) {
                        	//删除widgetID
                            appWidgetHost.deleteAppWidgetId(appWidgetId);
                            return null;
                        }
                    }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null);
                }
            }
            //remove widget from launcher
            removeViewItem(VIEW_ITEM_INTELCARDS_WIDGET);
        }else {
            isRemoveIntelcards=false;
            if(mUpdateView == null){
                mUpdateView = new UpdateView();
            }
            //移除之前的runnable任务
            removeCallbacks(mUpdateView);
            //重新执行runnable任务
            postDelayed(mUpdateView, 3000);
        }
        cursor.close();
    }
class UpdateView implements Runnable{
        @Override
        public void run() {
            Cursor cursor = getContext().getContentResolver().query(CONTENT_URI, null,
                    "show = 1", null, null);
            if(cursor!=null&&cursor.getCount()>0){
                Log.i(TAG,"intelcards db change,counts="+cursor.getCount());
                if (sp.getBoolean(INTELCARDS_SP, true)==true) {
                	//负一屏显示该widget
                    showViewItem(ViewItemType.VIEW_ITEM_INTELCARDS_WIDGET);
                }
            }
        }
    }

5、Intelcards的viewholder
定义IntelcardsBinder,用来设置这个widget的布局,并add到appWidgetHostView上
在这里插入图片描述

 public ViewHolder(View view) {
            super(view);
            mWidgetContainer = (LinearLayout) view.findViewById(R.id.intelcard_widget_container);

            Context context = view.getContext();
            boolean isProject = context.getSharedPreferences(LauncherAppState.NAME_CUSTOM_SHARE, Context.MODE_PRIVATE).getBoolean(LauncherAppState.KEY_IS_PROJECTION_WALLPAPER, true);
            if (isProject) {
                ((CardView)view.findViewById(R.id.custom_card)).setCardBackgroundColor(context.getColor(R.color.card_bg_project));
                view.findViewById(R.id.custom_ll_title).setBackgroundColor(context.getColor(R.color.widget_title_bg_project));
                ((TextView)view.findViewById(R.id.widget_books_title)).setTextColor(context.getColor(R.color.widget_title_textcolor_project));
            } else {
                ((CardView)view.findViewById(R.id.custom_card)).setCardBackgroundColor(context.getColor(R.color.card_bg));
                view.findViewById(R.id.custom_ll_title).setBackgroundColor(context.getColor(R.color.widget_title_bg));
                ((TextView)view.findViewById(R.id.widget_books_title)).setTextColor(context.getColor(R.color.widget_title_textcolor));
            }

       }
private void addWidgetView(ViewHolder holder) {
        appWidgetManager = AppWidgetManager.getInstance(mContext);
        ComponentName componentName = new ComponentName("com.ragentek.intelcards", "com.ragentek.intelcards.provider.CardWidgetProvider");
        //根据componentName获取widget信息
        AppWidgetProviderInfo appWidgetProviderInfo = findAppWidgetProviderInfoWithComponent(mContext, componentName);
        //分配widgetId并作为成员变量,方便后面将该wigetID从appWidgetHost中删除
        mWidgetId = ((Launcher)mContext).getAppWidgetHost().allocateAppWidgetId();
        Bundle options = null;
        //绑定widgetid
        boolean success = appWidgetManager.bindAppWidgetIdIfAllowed(
                mWidgetId, componentName, options);
        hostView = ((Launcher)mContext).getAppWidgetHost().createView(mContext, mWidgetId, appWidgetProviderInfo);
        //设置长宽  appWidgetProviderInfo 对象的 minWidth 和  minHeight 属性
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        //添加至LinearLayout父视图中
        holder.mWidgetContainer.addView(hostView, layoutParams);
    }


    /**
     * Attempts to find an AppWidgetProviderInfo that matches the given component.
     */
    static AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context,ComponentName component) {
        List<AppWidgetProviderInfo> widgets = AppWidgetManager.getInstance(context).getInstalledProviders();
        for (AppWidgetProviderInfo info : widgets) {
            if (info.provider.equals(component)) {
                return info;
            }
        }
        return null;
    }

猜你喜欢

转载自blog.csdn.net/u014050788/article/details/88930298