工程目录
虚拟桌面的Demo工程架构如下:
其中app
模块为主工程模块,加载TextureView
并绘制虚拟桌面。
virtualapp1
和virtualapp2
模块为虚拟应用,通过加载主工程的Surface并绘制自己的虚拟界面。
virtual-lib
为工程sdk,包含app
模块调用的TextureView
绘制的相关接口以及虚拟应用渲染组件。
效果
实现的简单Demo效果如下:
画面全部通过TextureView
渲染而成,虚拟应用界面可响应触摸和点击事件。
virtual-lib
介绍
VirtualService.java
为每个虚拟应用需要声明的服务,以便主工程查找对应的入口。
public class VirtualService extends Service {
private VirtualServiceBinder virtualServiceBinder;
@Override
public void onCreate() {
super.onCreate();
Looper renderLooper = Looper.getMainLooper();
if (renderLooper == null) {
int renderPriority = Process.THREAD_PRIORITY_DISPLAY;
VirtualRenderThread thread = new VirtualRenderThread(renderPriority);
renderLooper = thread.getLooper();
}
virtualServiceBinder = new VirtualServiceBinder(this, renderLooper);
}
@Override
public IBinder onBind(Intent intent) {
return virtualServiceBinder;
}
static class VirtualServiceBinder extends IVirtualService.Stub {
private Context context;
private Looper looper;
VirtualServiceBinder(Context context, Looper looper) {
this.context = context;
this.looper = looper;
}
@Override
public IVirtualView getVirtualView(IVirtualClient virtualClient, Intent intent) throws RemoteException {
try {
VirtualHost virtualHost = new VirtualHost(context, intent, looper);
try {
return virtualHost;
} finally {
virtualHost.create(virtualClient);
}
} catch (Exception e) {
return null;
}
}
@Override
public void release(IVirtualView virtualView) throws RemoteException {
if (virtualView != null) {
IBinder binder = virtualView.asBinder();
if (binder instanceof VirtualHost) {
((VirtualHost) binder).destroy();
}
}
}
}
}
虚拟应用中需要AndroidManifest.xml
中声明如下:
<service
android:name="com.example.virtual_lib.VirtualService"
android:exported="true">
<meta-data
android:name="com.example.virtualproject.PROVIDER"
android:resource="@xml/virtural_provider"/>
<intent-filter>
<action android:name="com.example.intent.action.virtualproject.VIRTUAL_VIEW"/>
</intent-filter>
</service>
virtural_provider.xml
声明如下:
<VirtualProvider xmlns:widget="http://schemas.android.com/apk/res-auto">
<VirtualView
widget:virtualClass="com.example.virtualapp1.CustomVirtualComponent" />
</VirtualProvider>
虚拟应用通过VirtualComponent
实现Surface的渲染
public abstract class VirtualComponent {
private static final String TAG = VirtualComponent.class.getSimpleName();
private final Object eventLock = new Object();
private final Object surfaceLock = new Object();
private volatile Surface surface;
private Context context;
private Looper renderLooper;
private Rect virtualRect;
private VirtualSurfaceLayout virtualSurfaceLayout;
private Runnable renderRunnable;
private volatile Handler renderHandler;
private volatile boolean isCreated = false;
private volatile boolean isAttachedToWindow = false;
private volatile boolean isSurfaceCreated = false;
private IVirtualClient virtualClient;
@SuppressLint("ClickableViewAccessibility")
final void init(Context context, Looper renderLooper) {
this.context = context;
this.renderLooper = renderLooper;
virtualRect = new Rect();
virtualSurfaceLayout = new VirtualSurfaceLayout(context);
renderRunnable = new Runnable() {
@Override
public void run() {
handleRender();
}
};
}
protected final Context getContext() {
return context;
}
protected Looper getRenderLooper() {
return renderLooper;
}
protected Handler getRenderHandler() {
if (renderHandler == null) {
synchronized (this) {
if (renderHandler == null) {
renderHandler = new Handler(getRenderLooper());
}
}
}
return renderHandler;
}
@SuppressWarnings("unchecked")
protected <T extends View> T findViewById(int id) {
return (T) virtualSurfaceLayout.findViewById(id);
}
protected void setContentView(int layoutId) {
setContentView(View.inflate(context, layoutId, null));
}
protected void setContentView(View virtualView) {
setContentView(virtualView, null);
}
protected void setContentView(View contentView, FrameLayout.LayoutParams layoutParams) {
virtualSurfaceLayout.setContentView(contentView, layoutParams);
ImageView imageView = new ImageView(getContext());
imageView.setImageResource(R.drawable.ic_close);
ViewGroup.LayoutParams itemParams = new ViewGroup.LayoutParams(200, 200);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
view.performClick();
if (virtualClient != null && motionEvent.getAction() == MotionEvent.ACTION_DOWN ) {
try {
if (surface != null) {
surface.release();
surface = null;
}
virtualClient.close();
} catch (RemoteException e) {
e.printStackTrace();
}
}
return true;
}
});
virtualSurfaceLayout.addView(imageView, itemParams);
}
final void create() {
synchronized (eventLock) {
createInternal();
}
}
private void createInternal() {
if (!isCreated) {
isCreated = true;
getRenderHandler().post(new Runnable() {
@Override
public void run() {
onCreated();
triggerRender();
}
});
}
}
protected void onCreated() {
}
protected void onDestroyed() {
}
final void destroy() {
synchronized (eventLock) {
destroyInternal();
}
}
private void destroyInternal() {
if (isCreated) {
isCreated = false;
if (isSurfaceCreated()) {
surfaceDestroyedInternal(surface);
}
if (isAttachedToWindow()) {
detachedFromWindowInternal();
}
getRenderHandler().post(new Runnable() {
@Override
public void run() {
onDestroyed();
}
});
}
}
protected final boolean isCreated() {
return isCreated;
}
final void attachedToWindow() {
synchronized (eventLock) {
attachedToWindowInternal();
}
}
private void attachedToWindowInternal() {
if (!isAttachedToWindow) {
isAttachedToWindow = true;
getRenderHandler().post(new Runnable() {
@Override
public void run() {
virtualSurfaceLayout.dispatchAttachedToWindow();
triggerRender();
}
});
}
}
final void detachedFromWindow() {
synchronized (eventLock) {
detachedFromWindowInternal();
}
}
private void detachedFromWindowInternal() {
if (isAttachedToWindow) {
isAttachedToWindow = false;
getRenderHandler().post(new Runnable() {
@Override
public void run() {
virtualSurfaceLayout.dispatchDetachedFromWindow();
}
});
}
}
protected final boolean isAttachedToWindow() {
return isAttachedToWindow;
}
final void surfaceCreated(Surface surface) {
synchronized (eventLock) {
surfaceCreatedInternal(surface);
}
}
private void surfaceCreatedInternal(final Surface surface) {
if (!isSurfaceCreated) {
isSurfaceCreated = true;
synchronized (surfaceLock) {
if (this.surface != null) {
this.surface.release();
}
this.surface = surface;
}
getRenderHandler().post(new Runnable() {
@Override
public void run() {
triggerRender();
}
});
}
}
final void surfaceDestroyed(Surface surface) {
synchronized (eventLock) {
surfaceDestroyedInternal(surface);
}
}
private void surfaceDestroyedInternal(final Surface surface) {
if (isSurfaceCreated) {
isSurfaceCreated = false;
synchronized (surfaceLock) {
if (this.surface != null) {
this.surface.release();
this.surface = null;
}
}
getRenderHandler().post(new Runnable() {
@Override
public void run() {
if (surface != null) {
surface.release();
}
}
});
}
}
protected final boolean isSurfaceCreated() {
return isSurfaceCreated;
}
final void surfaceChanged(Surface surface, int format, int width, int height) {
synchronized (eventLock) {
surfaceChangedInternal(surface, format, width, height);
}
}
private void surfaceChangedInternal(final Surface surface, final int format, final int width, final int height) {
synchronized (surfaceLock) {
if (this.surface != null) {
this.surface.release();
}
this.surface = surface;
}
getRenderHandler().post(new Runnable() {
@Override
public void run() {
virtualRect.set(0, 0, width, height);
ViewGroup.LayoutParams layoutParams = virtualSurfaceLayout.getLayoutParams();
if (layoutParams == null) {
layoutParams = new ViewGroup.LayoutParams(width, height);
virtualSurfaceLayout.setLayoutParams(layoutParams);
} else {
layoutParams.width = width;
layoutParams.height = height;
}
requestLayout();
triggerRender();
}
});
}
final boolean handleTouchEvent(MotionEvent event) {
synchronized (eventLock) {
return dispatchTouchEventInternal(event);
}
}
private boolean dispatchTouchEventInternal(MotionEvent event) {
return dispatchTouchEvent(event);
}
protected boolean dispatchTouchEvent(final MotionEvent event) {
getRenderHandler().post(new Runnable() {
@Override
public void run() {
if (!virtualSurfaceLayout.dispatchTouchEvent(event)) {
onTouchEvent(event);
}
}
});
return true;
}
protected boolean onTouchEvent(MotionEvent event) {
return true;
}
protected final void requestRender() {
Handler renderHandler = getRenderHandler();
if (!renderHandler.hasCallbacks(renderRunnable)) {
renderHandler.post(renderRunnable);
}
}
private void triggerRender() {
if (isCreated()
&& isAttachedToWindow()
&& isSurfaceCreated()) {
requestRender();
}
}
private void handleRender() {
doRender();
}
private void doRender() {
synchronized (surfaceLock) {
if (surface != null && surface.isValid()) {
Canvas canvas = null;
try {
requestLayout();
canvas = surface.lockCanvas(virtualRect);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
virtualSurfaceLayout.draw(canvas);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
if (canvas != null) {
try {
surface.unlockCanvasAndPost(canvas);
} catch (Exception ignore) {}
}
}
}
}
}
private void requestLayout() {
int widthSpec = View.MeasureSpec.makeMeasureSpec(virtualRect.width(), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(virtualRect.height(), View.MeasureSpec.EXACTLY);
virtualSurfaceLayout.measure(widthSpec, heightSpec);
virtualSurfaceLayout.layout(0, 0, virtualSurfaceLayout.getMeasuredWidth(), virtualSurfaceLayout.getMeasuredHeight());
}
public void setClient(IVirtualClient virtualClient) {
this.virtualClient = virtualClient;
}
}
VirtualAppUtils
工具类来加载虚拟应用的相关参数:
public class VirtualAppUtils {
private static final String KEY_VIRTUAL_PROVIDER = "com.example.virtualproject.PROVIDER";
private static final String TAG_VIRTUAL = "VirtualView";
private static final String ATTR_VIRTUAL_CLASS = "virtualClass";
private static final String ATTR_VIRTUAL_ICON = "virtualIcon";
private static final String ATTR_VIRTUAL_LAYOUT = "virtualLayout";
public static List<VirtualProvider> getVirtualAppInfos(Context context) {
List<VirtualProvider> virtualProviders = new ArrayList<>();
PackageManager packageManager = context.getPackageManager();
List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(new Intent(VirtualViewHelper.VIRTUAL_SERVICE_ACTION), PackageManager.GET_META_DATA);
for (ResolveInfo info : resolveInfos) {
info.getIconResource();
if (info.serviceInfo != null) {
try (XmlResourceParser parser = info.serviceInfo.loadXmlMetaData(packageManager, KEY_VIRTUAL_PROVIDER)) {
if (parser != null) {
try {
Resources virtualResources = packageManager.getResourcesForApplication(info.serviceInfo.applicationInfo);
virtualProviders.addAll(parseVirtualProvider(info.serviceInfo.applicationInfo, packageManager, virtualResources, parser));
} catch (IOException | XmlPullParserException | PackageManager.NameNotFoundException ignore) {}
}
}
}
}
return virtualProviders;
}
private static List<VirtualProvider> parseVirtualProvider(ApplicationInfo applicationInfo, PackageManager packageManager, Resources virtualResources, XmlResourceParser parser)
throws IOException, XmlPullParserException {
List<VirtualProvider> virtualProviders = new ArrayList<>();
while(parser.next() != XmlResourceParser.END_DOCUMENT) {
if(parser.getEventType() == XmlResourceParser.START_TAG && TAG_VIRTUAL.equals(parser.getName())) {
VirtualProvider virtualProvider = new VirtualProvider(applicationInfo.packageName, virtualResources);
virtualProvider.setIconId(applicationInfo.icon);
virtualProvider.setIconDrawable(applicationInfo.loadIcon(packageManager));
virtualProvider.setLabel(applicationInfo.loadLabel(packageManager).toString());
for (int i = 0; i < parser.getAttributeCount(); i++) {
String name = parser.getAttributeName(i);
if (ATTR_VIRTUAL_CLASS.equals(name)) {
virtualProvider.setClassName(parser.getAttributeValue(i));
} else if (ATTR_VIRTUAL_ICON.equals(name)) {
virtualProvider.setIconId(parser.getAttributeResourceValue(i, applicationInfo.icon));
} else if (ATTR_VIRTUAL_LAYOUT.equals(name)) {
virtualProvider.setLayout(parser.getAttributeIntValue(i, 0));
}
}
if (virtualProvider.getLabel() == null) {
if (applicationInfo.labelRes != 0) {
virtualProvider.setLabel(virtualResources.getString(applicationInfo.labelRes));
} else {
virtualProvider.setLabel(applicationInfo.packageName);
}
}
if (virtualProvider.getIconId() == 0) {
virtualProvider.setIconId(applicationInfo.icon);
}
virtualProviders.add(virtualProvider);
}
}
return virtualProviders;
}
}
由于TexttureView
不同于在Activity
中添加View
的流程,需要通过反射回调连接过程:
@SuppressLint("ViewConstructor")
public class VirtualSurfaceLayout extends FrameLayout {
private boolean isAttachedToWindow;
private Method onAttachedToWindowMethod;
private Method onDetachedFromWindowMethod;
public VirtualSurfaceLayout(Context context) {
super(context);
new FrameLayout(context).addView(this);
}
public final void setContentView(View contentView, ViewGroup.LayoutParams layoutParams) {
removeAllViews();
if (contentView != null) {
if (layoutParams == null) {
layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}
addView(contentView, layoutParams);
}
}
final void dispatchAttachedToWindow() {
if (!isAttachedToWindow) {
isAttachedToWindow = true;
dispatchAttachedToWindow(this);
}
}
final void dispatchDetachedFromWindow() {
if (isAttachedToWindow) {
isAttachedToWindow = false;
dispatchDetachedFromWindow(this);
}
}
@Override
public boolean isAttachedToWindow() {
return isAttachedToWindow;
}
private void dispatchAttachedToWindow(View view) {
if (view != null) {
if (onAttachedToWindowMethod == null) {
try {
onAttachedToWindowMethod = View.class.getDeclaredMethod("onAttachedToWindow");
onAttachedToWindowMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
return;
}
}
try {
onAttachedToWindowMethod.invoke(view);
} catch (IllegalAccessException | InvocationTargetException ignore) {}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
dispatchAttachedToWindow(viewGroup.getChildAt(i));
}
}
}
}
private void dispatchDetachedFromWindow(View view) {
if (view != null) {
if (onDetachedFromWindowMethod == null) {
try {
onDetachedFromWindowMethod = View.class.getDeclaredMethod("onDetachedFromWindow");
onDetachedFromWindowMethod.setAccessible(true);
} catch (NoSuchMethodException e) {
return;
}
}
try {
onDetachedFromWindowMethod.invoke(view);
} catch (IllegalAccessException | InvocationTargetException ignore) {}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
dispatchDetachedFromWindow(viewGroup.getChildAt(i));
}
}
}
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
if (isAttachedToWindow()) {
dispatchAttachedToWindow(child);
}
child.setSelected(isSelected());
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
if (isAttachedToWindow()) {
dispatchDetachedFromWindow(child);
}
}
}
应用
可实现多个TextrueView绑定多个虚拟应用显示,组建一个多组件/卡片显示的主界面。也可通过将Surface编码然后跨设备传输,在另一设备界面渲染,实现如HiCar、CarPlay的虚拟桌面效果。
未完待续。。。
待上传源码