概述
Android实现双屏异显的实现方式有2种。
方式一:在Android4.2及以上平台上,按照标准Android SDK提供的API,使用Presentation类,将一个APP的相关内容显示到指定的屏幕上,具体请参考https://developer.android.com/reference/android/app/Presentation.html。这种方式实现的是应用内的异显,也是View级别的异显。
方式二:通过修改Framework层的am,wm,display相关代码,从而在不同的显示设备上运行不同的应用,这种多任务的双屏异显方法实现了不同应用之间的异显,本文重点讲解这种双屏异显方法的实现方式。这种实现方式是基于某芯片厂商系列芯片,Android 6.0平台开发的。
这需要解决如下几个问题:
1.如何获取所有的TaskID?
在IActivityManager.java添加接口,public List
ActivityManagerNative.java种实现了IActivityManger接口,具体实现如下:
@Override
public List getAllTaskIds() throws RemoteException{
List taskIds = new ArrayList();
List stackInfos = getAllStackInfos();
if(stackInfos != null && stackInfos.size() > 0){
for(StackInfo info : stackInfos){
int[] taskIdArray = info.taskIds;
reverseArray(taskIdArray);
if(taskIdArray != null && taskIdArray.length > 0){
for(int taskId : taskIdArray)
taskIds.add(taskId);
}
}
}
return taskIds;
}
if(r.task != null && r.task.taskId == mService.getSecondDisplayTaskId())
return;
上述代码禁止副屏应用进入Pased状态,副屏应用将一直保持Resume状态
3.主屏与副屏的应用如何切换,同显与异显如何切换?这是通过操作主屏与副屏的窗口实现的,WindowManagerService.java添加如下代码:
public void setOnlyShowInExtendDisplay(Session session,IWindow client,int transit){
long origId = Binder.clearCallingIdentity();
synchronized(mWindowMap){
if(mDisplayContents == null || mDisplayContents.size() <= 1){
return;
}
final int displayCount = mDisplayContents.size();
DisplayContent defaultContent = getDefaultDisplayContentLocked();
int displayId = 0;
DisplayContent secondDisplayContent = null;
for(int i = 0; i < displayCount;i++){
final DisplayContent content = mDisplayContents.valueAt(i);
if(content != defaultContent){
secondDisplayContent = content;
displayId = secondDisplayContent.getDisplayId();
break;
}
}
if(secondDisplayContent == null){
return;
}
if(!okToDisplay()){
return;
}
WindowState current = windowForClientLocked(session, client, false);
if(isHomeWindow(current)){
return;
}
AppWindowToken wtoken = current.mAppToken;
if(wtoken == null){
return;
}
int groupId = wtoken.mTask.mTaskId;
mH.sendMessage(mH.obtainMessage(H.DO_TASK_DISPLAY_CHANGED, groupId, -1));
}
Binder.restoreCallingIdentity(origId);
}
/**
move window to second display
*/
public void moveTransitionToSecondDisplay(int groupId,int transit){
//if(!isShowDualScreen()){
// mSecondTaskIds.clear();
//}
//Settings.System.putInt(mContext.getContentResolver(), Settings.DUAL_SCREEN_ICON_USED, 0);
List allTaskIds = null;
try{
allTaskIds = mActivityManager.getAllTaskIds();
}catch (Exception e){
Log.i("DualScreen", "WindowManagerService->getAllTaskIds->e:" + e);
}
if(allTaskIds == null || allTaskIds.size() < 2)
return;
if(isShowDualScreen()){
moveWindowToSecondDisplayWithDualShow();
return;
}
long origId = Binder.clearCallingIdentity();
int curMoveTaskId = -1;
synchronized(mWindowMap){
if(mDisplayContents == null || mDisplayContents.size() <= 1){
return;
}
final int displayCount = mDisplayContents.size();
DisplayContent defaultContent = getDefaultDisplayContentLocked();
int displayId = 0;
DisplayContent secondDisplayContent = null;
for(int i = 0; i < displayCount;i++){
final DisplayContent content = mDisplayContents.valueAt(i);
if(content != defaultContent){
secondDisplayContent = content;
displayId = secondDisplayContent.getDisplayId();
break;
}
}
if(secondDisplayContent == null){
return;
}
if(!okToDisplay()){
return;
}
SurfaceControl.openTransaction();
WindowState win = null;
WindowList defaultWindows = defaultContent.getWindowList();
try{
WindowList secondDisplayAddList = new WindowList();
WindowList secondDisplayWindows = secondDisplayContent.getWindowList();
int topTaskId = -1;
if(allTaskIds != null && allTaskIds.size() > 0){
topTaskId = allTaskIds.get(0);
//mSecondTaskIds.add(topTaskId);
}
for(int i= defaultWindows.size()-1;i>=0;i--){
win = defaultWindows.get(i);
if(win == null){
continue;
}
if (win.mAppToken == null){
continue;
}
if(win.mAppToken.mTask == null){
continue;
}
int windowTaskId = win.mAppToken.mTask.mTaskId;
if(windowTaskId == topTaskId){
win.setPrimaryDisplay(true);
defaultWindows.remove(win);
mTempWindowList.add(win);
win.mDisplayContent = secondDisplayContent;
if(win.mWinAnimator != null){
int layerStack = secondDisplayContent.getDisplay().getLayerStack();
if(win.mWinAnimator.mSurfaceControl!= null){
win.mWinAnimator.mSurfaceControl.setLayerStack(layerStack);
}
}
secondDisplayAddList.add(0,win);
}
}
secondDisplayWindows.clear();
secondDisplayWindows.addAll(secondDisplayAddList);
for (int i = 0; i < displayCount; i++) {
final DisplayContent content = mDisplayContents.valueAt(i);
assignLayersLocked(content.getWindowList());
content.layoutNeeded = true;
}
Log.i("DualScreen", "WindowManagerService->allTaskIds:" + allTaskIds);
ArrayList allStacks = getAllStacks();
Log.i("DualScreen", "moveTransitionToSecondDisplay->allStacks:" + allStacks);
boolean isFind = false;
for(int i = allStacks.size() - 1; i >= 0; --i){
ActivityStack itemStack = allStacks.get(i);
//ArrayList getAllTasks()
List itemTasks = itemStack.getAllTasks();
if(itemTasks != null && itemTasks.size() > 0){
for(int k = itemTasks.size() - 1; k >= 0; --k){
TaskRecord itemTask = itemTasks.get(k);
List itemActivities = itemTask.mActivities;
for(int j = itemActivities.size() - 1; j >= 0; --j){
ActivityRecord itemActivity = itemActivities.get(j);
if(/*itemActivity.state == ActivityState.RESUMED && */itemTask.taskId != topTaskId){
curMoveTaskId = itemTask.taskId;
isFind = true;
break;
}
}
if(isFind)
break;
}
}
if(isFind)
break;
}
mSecondDisplayTaskId = topTaskId;
Log.i("DualScreen", "WindowManagerService->curMoveTaskId:" + curMoveTaskId);
misMovingToSecond = true;
Settings.System.putInt(mContext.getContentResolver(), Settings.DUAL_SCREEN_ICON_USED, 1);
switchFocusWindow(curMoveTaskId);
updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false);
mAppTransition.setReady();
performLayoutAndPlaceSurfacesLocked();
}finally{
SurfaceControl.closeTransaction();
}
//shouldAppMoveBack(-1);
}
Binder.restoreCallingIdentity(origId);
}
public void moveWindowToSecondDisplayWithDualShow(){
long origId = Binder.clearCallingIdentity();
int secondDisplayTaskId = getSecondDisplayTaskId();
int currentFocusedTaskId = -1;
int curMoveTaskId = secondDisplayTaskId;
if(mFocusedApp != null && mFocusedApp.mTask != null)
currentFocusedTaskId = mFocusedApp.mTask.mTaskId;
Log.i("DualScreen", "WindowManagerService->moveWindowToSecondDisplayWithDualShow->currentFocusedTaskId:" + currentFocusedTaskId);
//mSecondDisplayTaskId = currentFocusedTaskId;
synchronized(mWindowMap){
if(mDisplayContents == null || mDisplayContents.size() <= 1){
return;
}
final int displayCount = mDisplayContents.size();
DisplayContent defaultContent = getDefaultDisplayContentLocked();
int displayId = 0;
DisplayContent secondDisplayContent = null;
for(int i = 0; i < displayCount;i++){
final DisplayContent content = mDisplayContents.valueAt(i);
if(content != defaultContent){
secondDisplayContent = content;
displayId = secondDisplayContent.getDisplayId();
Log.d("DualScreen", "WindowManagerService->moveWindowToSecondDisplayWithDualShow->secondDisplayId:" + displayId);
break;
}
}
if(secondDisplayContent == null){
return;
}
if(!okToDisplay()){
return;
}
SurfaceControl.openTransaction();
WindowState win = null;
try{
WindowList secondDisplayAddList = new WindowList();
WindowList defaultDisplayAddList = new WindowList();
WindowList secondDisplayWindows = secondDisplayContent.getWindowList();
WindowList defaultWindows = defaultContent.getWindowList();
if(mTempWindowList != null && mTempWindowList.size() > 0)
defaultWindows.addAll(mTempWindowList);
mTempWindowList.clear();
for(int i= defaultWindows.size()-1;i>=0;i--){
win = defaultWindows.get(i);
if(win == null){
continue;
}
if (win.mAppToken == null){
continue;
}
if(win.mAppToken.mTask == null){
continue;
}
int windowTaskId = win.mAppToken.mTask.mTaskId;
if(windowTaskId == currentFocusedTaskId){
defaultWindows.remove(win);
win.mDisplayContent = secondDisplayContent;
if(win.mWinAnimator != null){
int layerStack = secondDisplayContent.getDisplay().getLayerStack();
if(win.mWinAnimator.mSurfaceControl!= null){
win.mWinAnimator.mSurfaceControl.setLayerStack(layerStack);
}
}
secondDisplayAddList.add(0,win);
}
}
for(int i= secondDisplayWindows.size()-1;i>=0;i--){
win = secondDisplayWindows.get(i);
if(win == null){
continue;
}
if (win.mAppToken == null){
continue;
}
if(win.mAppToken.mTask == null){
continue;
}
int windowTaskId = win.mAppToken.mTask.mTaskId;
if(windowTaskId == secondDisplayTaskId){
secondDisplayWindows.remove(win);
win.mDisplayContent = defaultContent;
if(win.mWinAnimator != null){
int layerStack = defaultContent.getDisplay().getLayerStack();
if(win.mWinAnimator.mSurfaceControl!= null){
win.mWinAnimator.mSurfaceControl.setLayerStack(layerStack);
}
}
defaultDisplayAddList.add(0,win);
}
}
//secondDisplayWindows.clear();
secondDisplayWindows.addAll(0,secondDisplayAddList);
//defaultWindows.clear();
defaultWindows.addAll(0, defaultDisplayAddList);
for (int i = 0; i < displayCount; i++) {
final DisplayContent content = mDisplayContents.valueAt(i);
assignLayersLocked(content.getWindowList());
content.layoutNeeded = true;
}
//Log.i("DualScreen", "WindowManagerService->moveWindowToSecondDisplayWithDualShow->allTaskIds:" + allTaskIds);
ArrayList allStacks = getAllStacks();
Log.i("DualScreen", "WindowManagerService->moveWindowToSecondDisplayWithDualShow->allStacks:" + allStacks);
mSecondDisplayTaskId = currentFocusedTaskId;
Log.i("DualScreen", "WindowManagerService->moveWindowToSecondDisplayWithDualShow->curMoveTaskId:" + curMoveTaskId);
misMovingToSecond = true;
Settings.System.putInt(mContext.getContentResolver(), Settings.DUAL_SCREEN_ICON_USED, 1);
switchFocusWindow(curMoveTaskId);
updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false);
mAppTransition.setReady();
performLayoutAndPlaceSurfacesLocked();
}finally{
SurfaceControl.closeTransaction();
}
//shouldAppMoveBack(-1);
}
Binder.restoreCallingIdentity(origId);
}
4.副屏页面如何全屏显示?修改PhoneWindowManager.java下layoutWindowLw方法添加代码如下:
pf.top = df.top = of.top = cf.top = vf.top = 0;
pf.right = df.right = of.right = cf.right = vf.right = width;
pf.bottom = df.bottom = of.bottom = cf.bottom = vf.bottom = height;