android利用TextureView实现一套虚拟桌面框架

工程目录

虚拟桌面的Demo工程架构如下:
在这里插入图片描述

其中app模块为主工程模块,加载TextureView并绘制虚拟桌面。
virtualapp1virtualapp2模块为虚拟应用,通过加载主工程的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的虚拟桌面效果。
未完待续。。。
待上传源码

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值