理解RemoteViews——RemoteViews的意義

上節我們分析了RemoteViews的內部機制,瞭解RemoteViews的內部機制可以讓我們更加清楚通知欄和桌面小工具的底層實現原理,但是本章對RemoteViews的探討並沒有停止,在本節中,我們將打造一個模擬的通知欄效果,並實現跨進程的UI更新。
首先有兩個Activity分別運行在不同的進程中,一個名字叫A,另一個叫B,其中A扮演着模擬通知欄的角色,而B則不停地發送通知欄消息,當然這是模擬消息。爲了模擬通知欄的效果,我們修改A的process屬性使其運行在單獨的進程中,這也A和B就構成了多進程通信的情形。我們在B中創建的RemoteViews對象,然後通知A顯示這個RemoteViews對象。如何通知A顯示B中的RemoteViews呢?我們可以像系統一樣採用Binder來實現,但是這裏爲了簡單起見就採用了廣播。B每發送一次模擬通知,就會發送一個特定的廣播,然後A接收到廣播後就開始顯示B中定義的RemoteViews對象,這個過程和系統通知欄消息顯示的過程幾乎一致,或者說這裏就是賦值了通知欄顯示過程而已。
首先看B的實現,B只要構造RemoteViews對象並將其傳輸給A即可,這一過程通知欄是採用Binder實現的,但是本例中採用廣播來實現的,RemoteViews對象通過Intent傳輸到A中,代碼如下所示。
public class ActivityB extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);

        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_simulated_notification);
        remoteViews.setTextViewText(R.id.msg, "msg from process:" + android.os.Process.myPid());
        remoteViews.setImageViewResource(R.id.icon, R.drawable.icon);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, DemoActivity_1.class),
                PendingIntent.FLAG_UPDATE_CURRENT);
        PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, DemoActivity_2.class),
                PendingIntent.FLAG_UPDATE_CURRENT);

        remoteViews.setOnClickPendingIntent(R.id.item_holder, pendingIntent);
        remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);


        Intent intent = new Intent(Constant.REMOTE_ACTION);
        intent.putExtra(Constant.EXTRA_REMOTE_VIEWS,remoteViews);
        sendBroadcast(intent);
        finish();
    }
}

A代碼也很簡單,只需要接受B中的廣播並顯示RemoteView即可,如下所示。

public class ActivityA extends AppCompatActivity {
    public static final String TAG = "ActivityA";

    private LinearLayout mRemoteViewsContent;

    private BroadcastReceiver mRemoteViewsReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            RemoteViews remoteViews = intent.getParcelableExtra(Constant.EXTRA_REMOTE_VIEWS);
            if (remoteViews != null) {
                updateUi(remoteViews);
            }
        }
    };

    private void updateUi(RemoteViews remoteViews){
        View view = remoteViews.apply(this, mRemoteViewsContent);
        mRemoteViewsContent.addView(view);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_activity);
        mRemoteViewsContent = (LinearLayout)findViewById(R.id.remote_views_content);
        registerReceiver(mRemoteViewsReceiver, new IntentFilter(Constant.REMOTE_ACTION));
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(mRemoteViewsReceiver);
        super.onDestroy();
    }

    public void sendMsg(View view) {
        startActivity(new Intent(this, ActivityB.class));
    }
}

上述代碼很簡單,除了註冊和檢出廣播以外,最主要的邏輯其實就是updateUi方法。當A收到廣播後,會從Intent中取出RemoteViews對象,然後通過它的apply方法加載佈局文件並執行更新操作,最後將得到的View調價到A的佈局中即可。可以發現,這個過程很簡單,但是通知欄的底層就是這麼實現的。

本節這個例子是可以在實際中使用的,比如現在有兩個應用,一個應用需要能夠更新另外一個應用中的某個界面,這個時候我們當然可以選擇AIDL去實現,但是如果對界面的更新比較繁瑣,這個時候就會有效率問題,同時AIDL接口就有可能會變得很複雜。這個時候如果採用RemoteViews來實現就沒有問題了。當然RemoteViews也有缺點,那就是它僅支持一些場景的View,對於自定義View它是不支持的。面對這種問題,到底是採用AIDL還是採用RemoteViews,這個要看具體情況,如果界面中的View都是一些簡單的且被RemoteViews支持的View,那麼可以考慮採用RemoteViews,否則就不適合用RemoteViews了。
如果打算採用RemoteViews來實現兩個應用之間的界面更新,那麼這裏還有一個問題,那就是佈局文件的加載問題。在上面的代碼中,我們直接通過RemoteViews的apply方法來加載並更新界面,如下所示。
        View view = remoteViews.apply(this, mRemoteViewsContent);
        mRemoteViewsContent.addView(view);
這種寫法在同一個應用的多進程情形下是合適的,但如果A和B屬於不同應用,那麼B中的佈局文件資源ID傳輸到A中以後很有可能是無效的,因爲A中的這個佈局文件的資源ID不可能剛好和B中的資源ID一樣,面對這種情況,我們就要適當修改RemoteViews的顯示過程的代碼了。這裏給出一種方法,既然資源ID不同,拿我們就通過資源名稱來加載佈局文件。首先兩個應用要提前約定好RemoteViews中的佈局文件的資源名稱,比如"layout_simulated_notifiaction",然後A中根據名稱查找到對應的佈局文件並加載,接着再調用RemoteViews的reapply方法即可將B中對View所做的一些列更新操作全面作用到A中加載的View上面。關於apply和reapply方法的差別再前面以及提到過,這裏就不多說了,這樣整個跨應用更新界面的流程就走通了,具體效果如下圖所示。可以發現B中的佈局文件已經成功的再A中顯示出來了。修改後的代碼如下所示:
        int layoutId = getResources().getIdentifier("layout_simulated_notification", "layout", getPackageName());
        View view = getLayoutInflater().inflate(layoutId,mRemoteViewsContent,false);
        remoteViews.reapply(this,view);
        mRemoteViewsContent.addView(view);


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章