聽名字就可以看出,remote views是一種遠程view,感覺有點像遠程service,其實remote views是view的一個結構,他可以在其他的進程中顯示,由于它可以在其他的進程中顯示,那么他就可以跨進程的更新其他進程的view,這聽起來有點不可思議,感覺有點像aidl,但是我要告訴你這確實不是,那它的原理是什么呢?且聽后面慢慢道來。
remote views在Android中有兩個常見的應用場景:通知欄和桌面小部件。
Remote Views的應用
桌面部件與通知欄分別由AppWidgetManager 與 NotificationManager來管理.。分別與systemService進程中的AppWidgetServer的NotificationManagerServer進行通訊 ,所以,才需要RemoteView來更新界面.RemoteView實現了Paracelable,通過Bindler傳遞到 systemService進程中。
RemoteViews的內部結構
我們首先來看看RomoteViews的類圖:
RemoteViews:中保存Remote端的mPackage和mLayoutId;并用mActions:ArrayList<RemoteViews.Action>保存各種Action。
mPackage和mLayoutId是在構造RemoteViews時傳進去的[上文圖中的seq#1];
mActions是設置各種Remote端的響應Intent以及圖形元素的時候,保存到相應的Action中,然后把Action加入到這里保存的;
mLayoutId里的各種控件通過setTextViewText()/ setImageViewResource() / setProgressBar(),等函數在remote端設置的。這些方法再調用setType()[Type可為Boolean / Byte / Short / Int/ Long / char / String / Uri / Bitmap/ Bundle, etc]保存到ReflectionAction中。
SetOnClickPendingIntent是用來在local端用戶點擊viewId時,發出pendingIntent通知的。在SetOnClickPendingIntent的構造方法中保存viewId和pendingIntent。
ReflectionAction用來在local端顯示時,通過Reflect機制執行獲得Remote端資源的。在ReflectionAction的構造方法中保存viewId,methodName,type以及value。
ViewGroupAction和SetDrawableParameters也是RemoteViews.Action的子類,在這個場景中并未用到,基本原理相似,讀者可自行分析。
AppWidgetHostView(顯示RemoteViews內容)
RemoteViews提供了內容之后,AppWidgetHost會通過IAppWidgetHost.updateAppWidget()被通知到Remote端有更新,本地端把RemoteViews提供的內容顯示在AppWidgetHostView上。
我們來看下類圖:
我們分析下流程:
1.??????獲取RemoteViews里Remote端(AppWidgetProvider)的packageName和layoutId,通過packageName創建遠端的context——remoteContext。
2.??????通過RemoteViews的apply()方法,真正開始執行偵聽Click操作的動作;通過遠端Layout獲得本地使用的View。
2.1. ?克隆一個本地的LayoutInflater;[Seq#8]
2.2. ?用克隆出的LayoutInflater對remote端的layoutId執行Inflate,獲得Layout所描述的View的Hierarchy,亦即后面用到的rootView;[Seq#9~ 10]
2.3. ?對2.2中獲得的view執行performApply。[Seq#11~ 19]
performApply()對所有mActions中的Action都執行apply()操作。這樣,
2.3.1 對于setOnClickPendingIntent來說,[Seq#12~ 15]
2.3.2對于ReflectionAction來說,[Seq#16~ 19]
3.??????把獲得的View加入到本地的View系統中。[Seq#21]
看一段關鍵代碼,通過Reflect機制設置內容的代碼片段:
@Override public void apply(View root) { final View view = root.findViewById(viewId); Class param = getParameterType(); // 通過this.type得到class:int.class Class klass = view.getClass(); // 這個類在Remote的Layout中定義,這里為ImageView Method method = klass.getMethod(this.methodName, param); // methodName是實現View類里的方法名:setImageResource(int) try { // 執行ImageView.setImageResource(value),value為resId method.invoke(view, this.value); } catch (Exception ex) { throw new ActionException(ex); } }
應用實例:
本文開始說過Android RemoteView主要兩個應用,其中一個就是桌面小控件。那么接下來說說AppWidget。
AppWidget
AppWidget也就是“窗口小部件”,當我們點擊桌面的小部件的時候,其實是觸發Remote端的AppWidgetProvider實現;具體顯示是Local的AppWidgetHost通過AppWidgetHostView實現。AppWidgetHost、AppWidgetProvider與AppWidgetService和AppWidgetManager按照特有的機制組合在一起,才能完整的實現AppWidget機制。接下來我們具體分析下他的實現流程。
AppWidget系統框架
AppWidget實現Remote端提供UI元素,Local端具體顯示。AppWidgetHost在AppWidget系統中是Local端;AppWidgetProvider端是Remote端。AppWidgetHost和AppWidgetProvider直接或通過IAppWidgetService或間接的通過AppWidgetManager,與AppWidgetService實現交互。AppWidgetService是所有元素的總管,負責協調其他各個部分。
AppWidget簡要分析
AppWidgetHost
AppWidgetHost通過IAppWidgetService利用Binder機制實現與系統進程中的AppWidgetService通信;
AppWidgetHost有IAppWidgetHost(通過Callbacks)的實現,并在AppWidgetHost.startListening()中注冊到AppWidgetService中,實現當Remote端的數據有更新時,通過IAppWidgetHost.updateAppWidget()通知AppWidgetHost更新本地的顯示;或者當Remote端的Provider改變時通知AppWidgetHost。
AppWidgetHost創建本地AppWidgetHostView時,會以AppWidgetId和AppWidgetHostView加入mViews: HashMap<Interger,AppWidgetHostView>
AppWidgetProvider
AppWidgetProvider是AppWidget的Remote端內容提供方,并能注冊響應其所提供內容的某個View被點擊時,響應的Intent。
AppWidgetProvider是一個抽象類,實現類需要實現抽象方法onUpdate() / onDeleted()/ onEnabled()和onDisabled()。這是AppWidgetProvider的一個模板模式實現,要求AppWidgetProvider的實現者:
通常,對于應用開發來說不太注重AppWidget其他的部分,只是寫AppWidgetProvider,但一般也都稱AppWidgetProvider為AppWidget開發。
AppWidgetService
AppWidgetService通過IAppWidgetService提供方法給AppWidgetHost、AppWidgetProvider使用。
在mInstalledProviders:AppWidgetService.Provider中保存AppWidgetProvider的信息;在mHost:AppWidgetService.Host中保存AppWidgetHost的信息;并用mAppWidgetIds:AppWidgetService.AppWidgetId保存AppWidgetHost與AppWidgetProvider的綁定關系。
注意:
AppWidgetService運行于三個各自不同的進程空間:
AppWidgetHost和AppWidgetProvider要用到AppWidgetService的服務時,用Binder機制通過IAppWidgetService實現。AppWidgetService通過IAppWidgetHost通知AppWidgetHost;AppWidgetService通過發Broadcast通知AppWidgetProvider。
講完概念,我們來兩個實用的例子:
通知欄:
Notification notification=new Notification(); notification.icon=R.drawable.ic_launcher; notification.tickerText="hello world"; notification.when=System.currentTimeMillis(); notification.flags=Notification.FLAG_AUTO_CANCEL; Intent intent=new Intent(this,NotificationActivity.class); //定義延遲的意圖 PendingIntent pendingIntent=PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); notification.setLatestEventInfo(this, "我的標題","我的文本", pendingIntent); RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.remoteview); remoteViews.setTextViewText(R.id.title, "我的標題"); remoteViews.setTextViewText(R.id.content, "我的內容"); notification.contentView=remoteViews; notification.contentIntent=pendingIntent; NotificationManager manager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(1, notification);
桌面小控件:
要完成桌面小控件需要如下幾個步驟:
1 自定義布局? 2 定義配置文件? 3 自定義類繼承AppWidgetProvider? 4 功能清單注冊
<?xml version="1.0" encoding="utf-8"?><appwidget-provider xmlns:andro android:minWidth="84dip" android:minHeight="84dip" android:initialLayout="@layout/widget" android:updateperiodmillis="300000" ></appwidget-provider>
功能清單的配置:
<receiver android:name="com.example.remoteviewfinal.MyAppWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/provider_info" ></meta-data> </receiver>
自定義類:
public class MyAppWidgetProvider extends AppWidgetProvider { @Override public void onReceive(final Context context, Intent intent) { // TODO 自動生成的方法存根 super.onReceive(context, intent); /** * 將圖片旋轉一圈 */ new Thread(new Runnable() { @Override public void run() { Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher); AppWidgetManager manager=AppWidgetManager.getInstance(context); for(int i=0;i<37;i++){ float degree=(i*10)%360; RemoteViews remoteViews=new RemoteViews(context.getPackageName(), R.layout.widget); remoteViews.setImageViewBitmap(R.id.imageview, rotateBitmap(context,bitmap,degree)); componentname cn=new ComponentName(context, MyAppWidgetProvider.class); //更新界面 manager.updateAppWidget(cn, remoteViews); } } }).start(); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO 自動生成的方法存根 super.onUpdate(context, appWidgetManager, appWidgetIds); } /** * 當第一個實例被添加的時候調用 */ @Override public void onEnabled(Context context) { // TODO 自動生成的方法存根 super.onEnabled(context); } /** * 當實例被刪除的時候調用 */ @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO 自動生成的方法存根 super.onDeleted(context, appWidgetIds); } /** * 當最后一個實例被刪除的時候調用 */ @Override public void onDisabled(Context context) { // TODO 自動生成的方法存根 super.onDisabled(context); } private Bitmap rotateBitmap(Context context,Bitmap bitmap,float degree){ Matrix matrix=new Matrix(); matrix.reset(); matrix.setRotate(degree); Bitmap result=Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), bitmap.getHeight(), matrix,true); return result; }}
更新不同進程的界面:?
定義界面A 來更新不同進程的界面B. 我們可以將界面A中的remoteView 傳遞到界面B,界面B 獲取對象,調用控件的apply方法更新界面 修改A界面的process屬性,使其在不同的進程中運行。
界面A:
public void send(){ RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.widget); remoteViews.setTextViewText(R.id.msg, "我的信息"); PendingIntent pendingIntent=PendingIntent.getActivity(this,0, new Intent(this,NotificationActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent); Intent intent=new Intent(Constant.ACTION); intent.putExtra(Constant.VIEW, remoteViews); sendBroadcast(intent); }
界面B:
private BroadcastReceiver mRemoteBroadcastReceiver=new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { RemoteViews remoteViews=intent.getParcelableExtra(Constant.VIEW); if(remoteViews!=null){ View view=remoteViews.apply(NotificationActivity.this, container); container.addView(view); } } };
本文由 貴州做網站公司 整理發布,部分圖文來源于互聯網,如有侵權,請聯系我們刪除,謝謝!
網絡推廣與網站優化公司(網絡優化與推廣專家)作為數字營銷領域的核心服務提供方,其價值在于通過技術手段與策略規劃幫助企業提升線上曝光度、用戶轉化率及品牌影響力。這...
在當今數字化時代,公司網站已成為企業展示形象、傳遞信息和開展業務的重要平臺。然而,對于許多公司來說,網站建設的價格是一個關鍵考量因素。本文將圍繞“公司網站建設價...
在當今的數字化時代,企業網站已成為企業展示形象、吸引客戶和開展業務的重要平臺。然而,對于許多中小企業來說,高昂的網站建設費用可能會成為其發展的瓶頸。幸運的是,隨...
醒圖配方怎么分享?打開醒頭先打開醒頭在醒頭里面制做怎么制作模板擴建,然后把在里面有保存到多多分享功能,的或都可以不。qq版本7.1.5怎樣將個性名片變成默認?將手機個性名片設置為原來是系統默認的模板表就行,方法::手機登陸,將頁面向右滑動再看看,再再點擊左上角的頭像;直接點擊右下角的“個性名片”;將“個性名片”頁面來回滑動究竟有沒有下,然后點擊上面設置成的模板;在“選擇類型版式”的頁面上,點擊“立...
honor 9 lite微信怎么設置夜間模式?首先需要手機自帶【黑暗模式】,因為本身沒有 "夜間模式和or "黑暗模式和,但只隨著系統黑暗模式的開啟而開啟;2.然后將手機中的版本升級到7.0.13以上版本,打開設置-關于-勾選要升級的新版本;3.打開手機中的【黑暗模式】;4.回到,可以看到已經自動切換到夜間模式;瀏覽器夜間模式是什么意思?傲游瀏覽器有 "夜間模式和。您可以在瀏覽器右上角的快速工具...
我的IPHONE怎么升級ISO7?越獄后的iphone只能通過itunes來升級,截至2015/9/26,只能升級到IOS9,不能升級IOS7,升級步驟:1、iphone關機狀態,使用數據線連接電腦,打開電腦的iTunes軟件。2、按住Power鍵2秒。3、在不放開Power鍵的狀態下,按Home 鍵10秒,強制關機。4、不放開Home鍵,輕按Power鍵1次。保持不放開Home鍵15秒左右,手機...