Android全局桌面精灵Unity实现
网页上找了一圈实现方案,大家的实现方案都是类似的。发现一个问题就是,用的都是旧版本的unity。以下的发案是基于新版本unity去实现。我使用的unity版本是2022.3.15
大致思路:
悬浮窗绘制unity场景
中间遇到的问题:
(1):相机背景不透明
解决方案
相机修改颜色值00000000
//看到这个方法
mUnityPlayer.setAlpha(0);
(2):进程切换导致画面不渲染
切换进程会执行一次暂停操作
@Override protected void onPause()
{
super.onPause();
// MultiWindowSupport.saveMultiWindowMode(this);
// if (MultiWindowSupport.getAllowResizableWindow(this))
// return;
//
// FloatingService.mUnityPlayer.pause();
}
暴力解决
直接屏蔽相关代码
1:准备Android工程
创建Android工程
相机配置修改
Android 工程导出配置
导出Android工程即可
2:android studio 工程配置
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools">
<uses-feature android:glEsVersion="0x00030001" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:extractNativeLibs="true">
<meta-data android:name="unity.splash-mode" android:value="0" />
<meta-data android:name="unity.splash-enable" android:value="True" />
<meta-data android:name="unity.launch-fullscreen" android:value="False" />
<meta-data android:name="unity.allow-resizable-window" android:value="False" />
<meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data android:name="unity.auto-report-fully-drawn" android:value="true" />
<activity android:name="com.unity3d.player.UnityPlayerActivity" android:theme="@style/UnityThemeSelector.Translucent" android:screenOrientation="fullUser" android:launchMode="singleTask" android:maxAspectRatio="2.1" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|fontScale|layoutDirection|density" android:resizeableActivity="false" android:hardwareAccelerated="false" android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
<layout android:defaultWidth="200px" android:defaultHeight="200px" android:minWidth="200px" android:minHeight="200px" />
</activity>
<service android:name=".FloatingService"/>
</application>
</manifest>
Service
package com.unity3d.player;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import com.unity3d.player.UnityPlayer;
public class FloatingService extends Service {
private WindowManager mWindowManager;
public static UnityPlayer mUnityPlayer;
@Override
public void onCreate() {
super.onCreate();
// 检查权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(this)) {
stopSelf();
return;
}
// 创建UnityPlayer实例
// 设置布局参数
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // 使用TYPE_APPLICATION_OVERLAY
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
// 指定悬浮窗的位置
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 0;
params.y = 100;
params.width=500;
params.height=500;
params.alpha=1f;
mUnityPlayer.setAlpha(0);
// FloatingService.mUnityPlayer.resume();
// 获取WindowManager服务
mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
mWindowManager.addView(mUnityPlayer.getView(), params);
// FloatingService.mUnityPlayer.start();
FloatingService.mUnityPlayer.resume();
// 处理悬浮窗的触摸事件
mUnityPlayer.setOnTouchListener(new View.OnTouchListener() {
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
params.x = initialX + (int) (event.getRawX() - initialTouchX);
params.y = initialY + (int) (event.getRawY() - initialTouchY);
mWindowManager.updateViewLayout(mUnityPlayer, params);
return true;
}
return false;
}
});
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
FloatingService.mUnityPlayer.resume();
mUnityPlayer.windowFocusChanged(true);
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
if (mUnityPlayer.getView() != null) mUnityPlayer.quit();
if (mUnityPlayer.getView() != null) mWindowManager.removeView(mUnityPlayer);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
Activity
package com.unity3d.player;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.os.Process;
public class UnityPlayerActivity extends Activity implements IUnityPlayerLifecycleEvents
{
private static final int OVERLAY_PERMISSION_REQ_CODE = 2084;
// public UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code
// Override this in your custom UnityPlayerActivity to tweak the command line arguments passed to the Unity Android Player
// The command line arguments are passed as a string, separated by spaces
// UnityPlayerActivity calls this from 'onCreate'
// Supported: -force-gles20, -force-gles30, -force-gles31, -force-gles31aep, -force-gles32, -force-gles, -force-vulkan
// See https://docs.unity3d.com/Manual/CommandLineArguments.html
// @param cmdLine the current command line arguments, may be null
// @return the modified command line string or null
protected String updateUnityCommandLineArguments(String cmdLine)
{
return cmdLine;
}
// Setup activity layout
@Override protected void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
FloatingService.mUnityPlayer= new UnityPlayer(this);
// setContentView(FloatingService.mUnityPlayer);
// 启动 FloatingService
Intent intent = new Intent(this, FloatingService.class);
startService(intent);
// 结束当前活动
onUnityPlayerUnloaded();
FloatingService.mUnityPlayer.windowFocusChanged(true);
//
// 启动悬浮窗服务
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// if (!Settings.canDrawOverlays(this)) {
// Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()));
// startActivityForResult(intent, 2084);
// } else {
// startFloatingService();
//
// }
// } else {
// startFloatingService();
// }
//
// String cmdLine = updateUnityCommandLineArguments(getIntent().getStringExtra("unity"));
// getIntent().putExtra("unity", cmdLine);
//
// mUnityPlayer = new UnityPlayer(this, this);
// setContentView(mUnityPlayer);
// mUnityPlayer.requestFocus();
}
private void startFloatingService() {
Log.d("create", "onCreate: floating1");
Intent intent = new Intent(this, FloatingService.class);
startService(intent);
Log.d("create", "onCreate: floating2");
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 2084) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Settings.canDrawOverlays(this)) {
startFloatingService();
} else {
// 权限未授予,显示错误消息或禁用相关功能
}
}
}
}
// When Unity player unloaded move task to background
@Override public void onUnityPlayerUnloaded() {
moveTaskToBack(true);
}
// Callback before Unity player process is killed
@Override public void onUnityPlayerQuitted() {
}
@Override protected void onNewIntent(Intent intent)
{
// To support deep linking, we need to make sure that the client can get access to
// the last sent intent. The clients access this through a JNI api that allows them
// to get the intent set on launch. To update that after launch we have to manually
// replace the intent with the one caught here.
setIntent(intent);
FloatingService.mUnityPlayer.newIntent(intent);
Log.d("resume","App is inint");
}
// Quit Unity
@Override protected void onDestroy ()
{
// FloatingService.mUnityPlayer.destroy();
super.onDestroy();
}
// If the activity is in multi window mode or resizing the activity is allowed we will use
// onStart/onStop (the visibility callbacks) to determine when to pause/resume.
// Otherwise it will be done in onPause/onResume as Unity has done historically to preserve
// existing behavior.
@Override protected void onStop()
{
super.onStop();
if (!MultiWindowSupport.getAllowResizableWindow(this))
return;
FloatingService.mUnityPlayer.pause();
Log.d("start","App is stop");
}
@Override protected void onStart()
{
super.onStart();
if (!MultiWindowSupport.getAllowResizableWindow(this))
return;
if(FloatingService.mUnityPlayer!=null)
{
FloatingService.mUnityPlayer.resume();
}
Log.d("start","App is start");
//
}
// Pause Unity
@Override protected void onPause()
{
super.onPause();
// MultiWindowSupport.saveMultiWindowMode(this);
// if (MultiWindowSupport.getAllowResizableWindow(this))
// return;
//
// FloatingService.mUnityPlayer.pause();
Log.d("start","App is pause");
}
// Resume Unity
@Override protected void onResume()
{
super.onResume();
//
// if (MultiWindowSupport.getAllowResizableWindow(this) && !MultiWindowSupport.isMultiWindowModeChangedToTrue(this))
// return;
if(FloatingService.mUnityPlayer!=null)
{
FloatingService.mUnityPlayer.resume();
}
Log.d("resume","App is resume");
}
// Low Memory Unity
@Override public void onLowMemory()
{
super.onLowMemory();
FloatingService.mUnityPlayer.lowMemory();
}
// Trim Memory Unity
@Override public void onTrimMemory(int level)
{
super.onTrimMemory(level);
if (level == TRIM_MEMORY_RUNNING_CRITICAL)
{
FloatingService.mUnityPlayer.lowMemory();
}
}
// This ensures the layout will be correct.
@Override public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
FloatingService.mUnityPlayer.configurationChanged(newConfig);
}
// Notify Unity of the focus change.
@Override public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
FloatingService.mUnityPlayer.windowFocusChanged(hasFocus);
}
// For some reason the multiple keyevent type is not supported by the ndk.
// Force event injection by overriding dispatchKeyEvent().
@Override public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_MULTIPLE)
return FloatingService.mUnityPlayer.injectEvent(event);
return super.dispatchKeyEvent(event);
}
// Pass any events not handled by (unfocused) views straight to UnityPlayer
@Override public boolean onKeyUp(int keyCode, KeyEvent event) { return FloatingService.mUnityPlayer.onKeyUp(keyCode, event); }
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return FloatingService.mUnityPlayer.onKeyDown(keyCode, event); }
@Override public boolean onTouchEvent(MotionEvent event) { return FloatingService.mUnityPlayer.onTouchEvent(event); }
@Override public boolean onGenericMotionEvent(MotionEvent event) { return FloatingService.mUnityPlayer.onGenericMotionEvent(event); }
}
结果