在咱們平時的開發中,使用RemoteViews的機會並非很對,可能多數仍是在自定義通知界面時,但RemoteViews憑藉能夠跨進程更新的特色,能夠幫助咱們實現不一樣的產品效果,Android中官方的使用就是通知和桌面小部件,今天就一塊兒來看看它是如和使用和如何跨進程傳輸的;java
簡介android
RemoteViews雖然能夠很容易的實現跨進程的控制視圖,但並不是全部的View都支持跨進程使用,根據GooGle官方文檔指出只支持如下ViewGroup和View,不知持他們的子類和自定義View,因此在寫RemoteViews的佈局文件時應注意選擇數據庫
通知中的使用比較簡單也比較固定,建立RemoteViews導入佈局並設置點擊事件,而後將視圖設置爲通知的contentView:app
RemoteViews notificationLayout = new RemoteViews(getPackageName(), R.layout.notification);
notificationLayout.setOnClickPrndingIntent(…,…)//設置佈局中的點擊事件(單個View的PendingIntent)
notification.contentView = notificationLayout
notification.contentIntent = … // 設置整個通知的PendingIntent
複製代碼
另外一個使用場景就是桌面小部件,桌面小部件確實豐富了產品的使用,更方便了用戶的適應這點本人在開發中涉及到的不多,AppWidget的開發雖然比通知使用複雜一些但也是有章可循,只要遵循每一步的流程便可實現,下面一塊兒實現一個桌面小部件:框架
AppWidgetProvider是BroadcastReceive的子類,主要用於接收小部件操做或修改時的廣播意圖,AppWidget會根據狀態的不一樣發送如下廣播:ide
AppWidgetProvider除了直接監聽廣播外,其內部簡化了廣播的使用,提供了不一樣狀態的回調方法,在開發中也主要使用這些方法便可,具體以下:工具
既然AppWidgetProvider是廣播的子類,因此它的使用也必須在清單文件中完成註冊:佈局
<receiver android:name="MyAppWidgetProvider">
<intent-filter>
//配置AppWidget的意圖過濾
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
// 添加設置appwidget_info的xml文件
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
複製代碼
ConfigActivity顧名思義,用來設置桌面小部件,它在第一次添加小部件時會直接接入配置界面,能夠在其中提供RemoteViews的相關配置,在配置完成後退出活動便可自動更新視圖,具體實現方式分兩步:this
<activity android:name=".OtherActivity">
<intent-filter>
//必須設置APPWIDGET_CONFIGURE意圖用於隱式啓動活動
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
複製代碼
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.alex.kotlin.remoteview.OtherActivity">
</appwidget-provider>
複製代碼
AppWidgetProviderInfo主要用於設置AppWidget的基本數據,如:佈局、尺寸、更新頻率等,全部信息設置在xml文件中,並在清單文件中配置xml文件:spa
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>
複製代碼
xml標籤屬性:
class WidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
val remoteView = RemoteViews(context?.packageName,R.layout.remoteview)
val pendingIntentClick = PendingIntent.getActivity(context,0,Intent(context,MainActivity::class.java),0)
remoteView.setBitmap(R.id.imageView,"setImageBitmap", BitmapFactory.decodeResource(context?.resources,R.drawable.a_round))
remoteView.setOnClickPendingIntent(R.id.button,pendingIntentClick)
for (id in appWidgetIds!!){
appWidgetManager?.updateAppWidget(id,remoteView)
}
複製代碼
上面程序中建立了AppWidgetProvider的子類,在onUpdate()中創了RemoteView並設置數據,最後使用AppWidgetManager更新AppWidget
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="100dp"
android:minHeight="100dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/remoteview"
android:previewImage="@mipmap/ic_launcher_round"
android:configure="com.alex.kotlin.remoteview.OtherActivity"
android:widgetCategory="home_screen">
</appwidget-provider>
複製代碼
<receiver android:name=".test.WidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/widget_provider_info"/>
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="com.example.administrator.WidgetProvider.action.click"/>
</intent-filter>
</receiver>
複製代碼
在桌面小部件使用中,除了上面的使用還有一種就是列表小部件,即在桌面中添加顯示數據的列表如:ListView;此處不能使用RecyclerView,並且在此處的ListView使用方式也有所不一樣,在建立列表小部件以前,先介紹兩個類:
下面一塊兒實現一個列表的AppWidget,主要實現步驟以下:
class RemoteServiceImpl : RemoteViewsService() {
override fun onGetViewFactory(intent: Intent?): RemoteViewsFactory {
return WidgetFactory(applicationContext)
}
}
複製代碼
<service android:name=".appwidget.AppWidgetService"
//設置RemoteViews的權限
android:permission="android.permission.BIND_REMOTEVIEWS" />
複製代碼
const val CLICK_ACTION: String = "com.example.administrator.WidgetProvider.action.click"
val intent = Intent(context, RemoteServiceImpl::class.java) //設置綁定List數據的Service
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds!![0])
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
remoteView.setRemoteAdapter(R.id.listView, intent) //爲RemoteView的List設置適配服務
val tempIntent = Intent(CLICK_ACTION) //建立點擊的臨時Intent
tempIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
remoteView.setPendingIntentTemplate( //設置ListView中Item臨時佔位Intent
R.id.recyclerView,PendingIntent.getBroadcast(context, 0, tempIntent, PendingIntent.FLAG_CANCEL_CURRENT))
appWidgetManager?.updateAppWidget(appWidgetIds!![0],remoteView)
複製代碼
針對上面的程序有幾點說明:
public RemoteViews getViewAt(int i) { // 設置每一個item的數據
val remoteViews = RemoteViews(context.packageName,R.layout.remoteview)
remoteViews.setTextViewText(R.id.button,listArray[position])
val intent = Intent(WidgetProvider.CLICK_ACTION)
intent.putExtra("Extra",listArray[position])
remoteViews.setOnClickFillInIntent(R.layout.remoteview,intent). //設置佔位填充的Intent
return remoteViews
}
複製代碼
override fun onReceive(context: Context?, intent: Intent?) {
super.onReceive(context, intent)
when (intent?.action) {
CLICK_ACTION -> {
val positionDrawable = intent.getIntExtra("Extra", 0)
val remoteView = RemoteViews(context?.packageName, R.layout.remoteview)
remoteView.setImageViewBitmap(
R.id.imgBig,
BitmapFactory.decodeResource(context?.resources, WidgetFactory.getDrawable(positionDrawable))
)
val manager = AppWidgetManager.getInstance(context)
val componentName = ComponentName(context, WidgetProvider::class.java) manager.updateAppWidget(componentName,remoteView) } } } 複製代碼
上面代碼實現的是在點擊ListView的Item時,將RemoteViews中的大圖片換成點擊Item對應的圖片,效果以下:
RemoteViews主要用途是通知 和 桌面小部件,這二者分別由NotificationManger 和 Appwidgetmanger 管理,NotificationManger 和 AppwidgetManger 經過Binder 與SystemServer中的NotificationMangerServer 和 AppwidgetServer 實現進程通訊, 那它是如何跨進程控制佈局的呢?咱們設置的佈局又是什麼時候被加載的呢?帶着這個問題咱們一塊兒分析下其內部的工做原理:
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text); // 調用setCharSequence,傳入方法名
}
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); // 添加一個反射的Action
}
private void addAction(Action a) {
...
if (mActions == null) {
mActions = new ArrayList<Action>();
}
mActions.add(a); // 將Action 儲存在集合中
a.updateMemoryUsageEstimate(mMemoryUsageCounter);
}
複製代碼
本次原理分析以setTextViewText()爲例,上面程序執行如下操做:
AppWidgetManager提交更新以後RemoteViews便會由Binder跨進程傳輸到SystemServer進程中 ,以後在這個進程 RemoteViews會執行它的apply方法或者reapply方法
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context); // 獲取以前建立時保存的RemoteViews
View result = inflateView(context, rvToApply, parent); // 調用inflateView()導入佈局
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler); // 調用 performApply 執行apply()
return result;
}
複製代碼
執行操做:
private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
...
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(rv.getLayoutId(), parent, false); // 導入佈局
}
複製代碼
inflateView()中只是獲取LayoutInflater實例,而後根據保存的layout文件,將視圖導入佈局到parent中
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
for (int i = 0; i < count; i++) {
Action a = mActions.get(i); // 獲取以前儲存的 反射的Action
a.apply(v, parent, handler); // 調用Action的Apply()方法
}
}
}
複製代碼
performApply中就幹了一件事,取出以前保存Action的集合mActions,循環執行其中的每一個Action執行其apply(),從上面咱們直到此處保存的是ReflectionAction實例,因此一塊兒看看ReflectionAction中apply()方法;
@Override
public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
final View view = root.findViewById(viewId); //獲取Action保存View的Id
Class<?> param = getParameterType(); // 一眼就看出這是反射獲取
if (param == null) {
throw new ActionException("bad type: " + this.type);
}
try {
getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
} catch (ActionException e) {
throw e;
} catch (Exception ex) {
throw new ActionException(ex);
}
}
複製代碼
RemoteViews的工做過程總結以下: