前言
隨著時代的發(fā)展挑胸,單在一塊屏幕上操作應(yīng)用已遠(yuǎn)遠(yuǎn)不能滿足與日俱增的用戶需求宰闰,安卓系統(tǒng)多屏互動也隨即誕生移袍。起初提到多屏幕的交互,開發(fā)者們更多的是想到使用RTP實現(xiàn)的視頻流傳輸螟左,然而傳輸視頻流存在著性能損耗路狮,視頻壓縮蔚约,刷新幀率等多方面的問題苹祟,讓不少想嘗試多屏互動開發(fā)的開發(fā)者望而卻步树枫。其實實現(xiàn)多屏互動也不止是只有視頻流傳輸一條路,今天就讓我們來實現(xiàn)一個使用安卓系統(tǒng)組件Presentation實現(xiàn)的多屏互動應(yīng)用奔誓。
Presentation
A presentation is a special kind of dialog whose purpose is to
present content on a secondary display.
----------Google
從本質(zhì)上來說Presentation只是一個特殊的Dialog而已厨喂,常規(guī)的Dialog是show在了操作屏上,而Presentation是show在了指定的操作屏上派阱,這個指定屏可以是系統(tǒng)的第二塊甚至是第三塊屏幕贫母,同時也可以是主應(yīng)用操作屏盒刚。如果Presentation顯示到了主應(yīng)用屏上伪冰,顯示效果與常規(guī)Dialog無異。
根據(jù)不同的顯示需求,Presentation有兩種顯示方式吓懈,一種是隨應(yīng)用Activity顯示隱藏同步的輔助屏顯示耻警,另一種是常駐輔助屏的保持顯示模式甸怕。如果需要使用常駐顯示模式梢杭,那么Presentation的顯示需要放到Service中武契。
雙屏開發(fā)準(zhǔn)備
常規(guī)的開發(fā)設(shè)備是不俱備輔助屏幕的,開發(fā)調(diào)試雙屏顯示届垫,我們可以借助開發(fā)者模式中的模擬輔助屏幕功能装处。進(jìn)入手機(jī)---設(shè)置---開發(fā)者模式---模擬輔助顯示設(shè)備---選擇一款需要調(diào)試分辨率的屏幕浸船。
這里需要注意一件事情判族,開發(fā)設(shè)備的系統(tǒng)版本要保證在安卓4.2以上,因為Presentation支持的版本是minSdkVersion >= 17槽惫。開啟輔助顯示設(shè)備的時候默認(rèn)是采用透屏模式界斜,即輔助屏顯示主屏的投影合冀,當(dāng)對輔助屏有定制開發(fā)需求顯示指定界面的時候君躺,就需要用到Presentation了棕叫。
雙屏異顯實例
創(chuàng)建輔助屏幕Presentation
輔助屏中有地圖與搜索兩個頁面俺泣,并可以自由的在兩個頁面間進(jìn)行切換。Presentation構(gòu)造函數(shù)中的Context參數(shù)可以是Activity横漏,也可以是ApplicationContext或者是Service缎浇,但如果是ApplicationContext或是Service類型的Context打掘,Presentation的Type類型必須為SYSTEM_ALERT尊蚁。
/**
* @author : YangHaoYi on 2019/1/2.
* Email : yang.haoyi@qq.com
* Description :雙屏異顯輔助屏幕
* Change : YangHaoYi on 2019/1/2.
* Version : V 1.0
*/
public class SecondScreenPresentation extends Presentation implements MapFrameLayout.MapCallBackListener,
SearchFrameLayout.SearchCallBack {
/** TAG **/
private static final String TAG = "Presentation";
/** 根布局 **/
private FrameLayout fmContent;
/** 地圖頁 **/
private MapFrameLayout mapFrameLayout;
/** 搜索頁 **/
private SearchFrameLayout searchFrameLayout;
/** Context 可以為Activity Application Service **/
public SecondScreenPresentation(Context outerContext, Display display) {
super(outerContext, display);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.presentation_search);
init();
}
private void init(){
initView();
initEvent();
}
// ··· 省略頁面初始化與頁面事件代碼
}
搭建Presentation顯示環(huán)境
因為我們所要實現(xiàn)的功能是在應(yīng)用退居后臺也能夠在輔助屏進(jìn)行顯示横朋,所以采用Service進(jìn)行Presentation的顯示,前文提到過Presentation與Dialog的區(qū)別在于衙传,Presentation可以自行選擇顯示的屏幕蓖捶,那么他是怎么控制顯示在指定屏幕上的呢扁远?這里我們需要用到安卓系統(tǒng)的DisplayManager畅买,通過他的getDisplays方法我們可以獲取到當(dāng)前系統(tǒng)所掛載的所有屏幕谷羞,然后對指定的屏幕進(jìn)行Presentation顯示操作。以一塊主屏和一塊輔屏為例犀填,在獲取到displays數(shù)組后宏浩,displays[0]即為我們系統(tǒng)的主屏幕靠瞎,displays[1]即為系統(tǒng)的輔助屏幕乏盐。因為設(shè)置了顯示類型為SYSTEM_ALERT父能,所以這里需要給我們的應(yīng)用添加相應(yīng)的權(quán)限何吝,6.0以上系統(tǒng)記得申請動態(tài)權(quán)限鹃唯。
/** 彈窗權(quán)限 **/
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
/** 屏幕管理器 **/
private DisplayManager mDisplayManager;
/** 屏幕數(shù)組 **/
private Display[] displays;
/** 初始化第二塊屏幕 **/
private void initPresentation(){
if(null==presentation){
mDisplayManager = (DisplayManager) this.getSystemService(Context.DISPLAY_SERVICE);
displays = mDisplayManager.getDisplays();
if(displays.length > 1){
// displays[1]是副屏
presentation = new SecondScreenPresentation(this, displays[1]);
presentation.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
}
}
綁定Presentation服務(wù)
主屏通過綁定服務(wù)開啟Presentation的顯示黔酥,在服務(wù)的onServiceConnected方法中進(jìn)行Presentation的show操作。
/**
* @author : YangHaoYi on 2019/4/30.
* Email : yang.haoyi@qq.com
* Description :離屏邏輯控制中心
* Change : YangHaoYi on 2019/4/30.
* Version : V 1.0
*/
public class PresentationPresenter {
private MultiScreenService multiScreenService;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
multiScreenService = ((MultiScreenService.MultiScreenBinder) service).getService();
//顯示第二塊屏幕
multiScreenService.showSearchPresentation();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//恢復(fù)置空
multiScreenService = null;
}
};
public void openSearchPresentation(Activity activity){
Intent intent = new Intent(activity,MultiScreenService.class);
activity.bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
}
}
業(yè)務(wù)邏輯
為了方便對雙屏異現(xiàn)實現(xiàn)功能的理解,這里通過一個簡單業(yè)務(wù)邏輯的示例來展示跪者。主屏幕上顯示地圖應(yīng)用棵帽,地圖具有放大、縮小與跳轉(zhuǎn)搜索頁面的功能渣玲,搜索頁具有搜索與返回地圖頁的功能逗概。輔助屏Presentation上具有和主屏應(yīng)用相同的兩個頁面地圖與搜索頁,同時具有相同的功能事件忘衍。
主屏與輔助屏是完全相同的兩個頁面逾苫,筆者做示例Demo采用的是MVP的模式淑履,即View層保證主屏與輔助屏是兩個相同對象隶垮,頁面封裝在MapFrameLayout與SearchFrameLayout中。
/** 初始化頁面 **/
private void initPage(){
mapFrameLayout = new MapFrameLayout(getContext());
mapFrameLayout.setMapCallBackListener(this);
fmContent.addView(mapFrameLayout);
searchFrameLayout = new SearchFrameLayout(getContext());
}
/** 地圖回調(diào) **/
@Override
public void mapCallBackContent(MapFrameLayout.MapEvent event, Object data) {
switch (event){
case Search:
if(null == searchFrameLayout){
searchFrameLayout = new SearchFrameLayout(getContext());
searchFrameLayout.setSearchBackListener(this);
fmContent.addView(searchFrameLayout);
}else {
fmContent.addView(searchFrameLayout);
}
break;
default:
break;
}
}
Model層與Presenter層完全復(fù)用秘噪。
/**
* @author : YangHaoYi on 2019/4/3017:23.
* Email : yang.haoyi@qq.com
* Description :搜索數(shù)據(jù)中心
* Change : YangHaoYi on 2019/4/3017:23.
* Version : V 1.0
*/
public class SearchModel {
/**
* 轉(zhuǎn)換搜索數(shù)據(jù)
* @return 搜索結(jié)果
* **/
public SearchResultData doNearbySearch(){
SearchResultData resultData = new SearchResultData();
resultData.setCode(0);
resultData.setDescription("請求成功");
SearchResultData.PayloadData payloadData = new SearchResultData.PayloadData();
payloadData.setTitle("市圖書館");
payloadData.setAddress("遼寧省沈陽市沈河區(qū)青年大街205號");
payloadData.setNotice("市圖書館");
payloadData.setLat("41.765923");
payloadData.setLng("123.442674");
return resultData;
}
}
/**
* @author : YangHaoYi on 2019/4/3010:54.
* Email : yang.haoyi@qq.com
* Description :搜索邏輯控制中心
* Change : YangHaoYi on 2019/4/3010:54.
* Version : V 1.0
*/
public class SearchPresenter {
/** 搜索數(shù)據(jù)Model **/
private SearchModel searchModel;
/** 搜索View接口 **/
private ISearchView searchView;
public SearchPresenter(ISearchView searchView) {
this.searchView = searchView;
searchModel = new SearchModel();
}
/** 執(zhí)行搜索 **/
public void nearbySearch(){
searchView.showSearchResult(searchModel.doNearbySearch());
}
}
主屏與輔助屏事件支持同屏同顯與同屏異顯兩種模式狸吞,同屏同顯即主屏操作事件的時候同步給輔助屏,可以通過事件總線EventBus或者RxBus實現(xiàn)指煎。頁面數(shù)據(jù)的刷新與變更可以使用總線機(jī)制實現(xiàn)蹋偏,也可以使用 Jetpack的LiveData實現(xiàn),基于谷歌 Jetpack的LiveData實現(xiàn)可以參考筆者關(guān)于LiveData的文章《基于DataBinding與LiveData的MVVM實踐(Kotlin)》至壤。
多屏顯示性能分析
CPU占用
我們知道威始,在一個典型的顯示系統(tǒng)中,一般包括CPU像街、GPU黎棠、Display三個部分。CPU 計算屏幕數(shù)據(jù)镰绎、GPU 進(jìn)一步處理和緩存脓斩、最后 Display 再將緩存中(buffer)的屏幕數(shù)據(jù)顯示出來。而CPU計算屏幕數(shù)據(jù)指的就是View樹的繪制過程畴栖,也就是窗體對應(yīng)的視圖樹從根布局 DecorView 開始層層遍歷每個 View随静,分別執(zhí)行測量、布局吗讶、繪制三個操作的過程燎猛,對于多屏顯示來說,因為新增了一個輔助屏幕的頁面展示照皆,勢必會增加CPU的占用重绷。
讓我們通過Android Studio的Android Profiler來實際看一下CPU的使用情況:
序號 | 時間節(jié)點 | CPU峰值 | CPU均值 |
---|---|---|---|
1 | 輔助屏設(shè)置頁顯示之前 | 11.9% | 9.7% |
2 | 輔助屏開始顯示設(shè)置頁10s取樣 | 28.5% | 26.3% |
3 | 輔助屏顯示設(shè)置頁后 | 21.2% | 16.1% |
序號 | 時間節(jié)點 | CPU峰值 | CPU均值 |
---|---|---|---|
1 | 輔助屏地圖頁顯示之前 | 13.3% | 12.5% |
2 | 輔助屏開始顯示地圖頁10s取樣 | 60.5% | 43.2% |
3 | 輔助屏顯示地圖頁后 | 41.9% | 37% |
可以看到CPU的占用情況與輔助屏頁的繪制相關(guān),普通頁面CPU占用約提升0.7纵寝,顯示雙屏UI后CPU占用均值16.1%论寨⌒橇ⅲ基于SurfaceView的地圖頁面CPU占用提升1.5,顯示雙屏UI后CPU占用均值37%葬凳。
內(nèi)存占用
多屏顯示增加CPU占用的同時也將引發(fā)更多的內(nèi)存分配绰垂,內(nèi)存的占用與頁面的復(fù)雜度息息相關(guān),頁面使用的對象越豐富火焰,特效越復(fù)雜劲装,帶來的內(nèi)存消耗也就越多,通常來說有些功能本身就會很耗內(nèi)存昌简,例如視頻直播占业,音樂播放,地圖顯示等纯赎。
同樣的谦疾,讓我們繼續(xù)通過Android Studio的Android Profiler分析一下多屏顯示的內(nèi)存占用情況:
序號 | 時間節(jié)點 | 內(nèi)存占用峰值 | 內(nèi)存占用均值 |
---|---|---|---|
1 | 輔助屏設(shè)置頁顯示之前 | 82.5MB | 82.1MB |
2 | 輔助屏開始顯示設(shè)置頁10s取樣 | 85.4MB | 85MB |
3 | 輔助屏顯示設(shè)置頁后 | 86.1MB | 86MB |
序號 | 時間節(jié)點 | 內(nèi)存占用峰值 | 內(nèi)存占用均值 |
---|---|---|---|
1 | 輔助屏地圖頁顯示之前 | 86MB | 85.4MB |
2 | 輔助屏開始顯示地圖頁10s取樣 | 100.6MB | 60MB |
3 | 輔助屏顯示地圖頁后 | 73MB | 60MB |
可以看到內(nèi)存占用情況與頁面負(fù)責(zé)度相關(guān),當(dāng)輔助屏為普通頁面的情況下犬金,內(nèi)存占用只增加了4MB左右念恍,內(nèi)存平穩(wěn)。當(dāng)輔助屏為基于SurfaceView的地圖頁面的情況下晚顷,內(nèi)存拉升20MB左右峰伙。
示例源碼
文章對Presentation功能展示的Demo已上傳GitHub,感興趣的朋友可以clone下來共同探討一下该默。
GitHub源碼鏈接