一、需求:
对接第三方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;
}