如何在运行Oreo和Pie的设备上,在锁定屏幕上叠加布局

8
在我的应用程序中,我正在尝试使用服务叠加布局,当我运行服务时,它可以成功执行并在锁屏上显示布局,但目标设备版本低于Oreo时。但是,当我尝试从Oreo之后的版本叠加相同的内容时,它不起作用。然而,当我将标志更改为Type_Application_Overlay,如其他答案建议的那样,它只会在屏幕解锁时显示布局,但我想在锁屏上显示它。我尝试了很多搜索,但没有找到任何有用的答案来解决这个问题。一些答案建议在锁屏上显示活动,我也尝试过,但仍然没有帮助!此外,在Play商店上有一些应用程序可以轻松地在Oreo和Pie上显示任何布局:

您可以查看此应用程序:

https://play.google.com/store/apps/details?id=com.wifihacker.whousemywifi.wifirouter.wifisecurity

这个应用可以在锁屏界面上轻松地显示自定义全屏布局,而无需请求任何覆盖权限或设备管理员权限。
既然这个应用能够在锁屏界面上叠加,那为什么我不能做相同的事情呢?
非常感谢您提供任何帮助和建议!
这是我目前用于锁屏的服务代码:
class LockScreenService : Service() {

    private lateinit var mReceiver: BroadcastReceiver
    private var isShowing = false

    private lateinit var windowManager: WindowManager
    lateinit var  params: WindowManager.LayoutParams
    lateinit var myview: View
    var  downX:Int = 0
    lateinit var locklayout:RelativeLayout
     var upX:Int = 0
    var indicator: WaveLoadingView?=null
    lateinit var date:TextView
    lateinit var time:TextView
    lateinit var settings:ImageView
    lateinit var unlock:LinearLayout
    var r:Runnable?=null
    companion object{

    }
    private val mBatInfoReceiver = object : BroadcastReceiver() {
        override fun onReceive(ctxt: Context, intent: Intent) {
            val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
            if (indicator!=null)
            {
                indicator!!.progressValue = level
                indicator!!.setAnimDuration(3000)
                indicator!!.startAnimation()
            }
            }
    }

    override fun onBind(intent: Intent): IBinder? {
        // TODO Auto-generated method stub
        return null
    }

    override fun onCreate() {
        super.onCreate()
        try {
            EventBus.getDefault().register(this)
            windowManager = applicationContext.getSystemService(WINDOW_SERVICE) as WindowManager

            val li = getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater

            myview = li.inflate(R.layout.lockscreenlayout, null)
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
                params = WindowManager.LayoutParams(
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                    PixelFormat.TRANSLUCENT
                )
            } else {
                params = WindowManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
                    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                            or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                            or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                    PixelFormat.TRANSLUCENT
                )
                myview.setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                            or View.SYSTEM_UI_FLAG_FULLSCREEN
                            or View.SYSTEM_UI_FLAG_VISIBLE
                            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                            or View.SYSTEM_UI_FLAG_IMMERSIVE
                            or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                )
            }
            myview.setVisibility(View.VISIBLE)
            settings = myview.findViewById(R.id.imgsetting)
            settings.setOnClickListener {
                var intent = Intent(this, Settings::class.java)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                this.startActivity(intent)
                windowManager.removeViewImmediate(myview)
            }

             indicator = myview.findViewById(R.id.indicator)
            locklayout = myview.findViewById(R.id.locklay)
            this.registerReceiver(this.mBatInfoReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            time = myview.findViewById(R.id.time)
            date = myview.findViewById(R.id.date)

            date.text =
                SimpleDateFormat("EEEE").format(Calendar.getInstance().time).toString() + "," + SimpleDateFormat("dd").format(
                    Calendar.getInstance().time
                ).toString() + " " + SimpleDateFormat("MMMM").format(Calendar.getInstance().time).toString()
            try {
                unlock.setOnTouchListener(object : View.OnTouchListener {
                    override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                        if (event!!.getAction() == MotionEvent.ACTION_DOWN) {
                            downX = event.getX().toInt()

                            return true
                        } else if (event.getAction() == MotionEvent.ACTION_UP) {
                            upX = event.getX().toInt()
                            if (upX - downX > 100) {
                                val animation = AnimationUtils.loadAnimation(applicationContext, R.anim.left_right_anim)
                                animation.setAnimationListener(object : Animation.AnimationListener {
                                    override fun onAnimationStart(animation: Animation) {}

                                    override fun onAnimationRepeat(animation: Animation) {}

                                    override fun onAnimationEnd(animation: Animation) {
                                        windowManager.removeViewImmediate(myview)
                                    }
                                })
                                locklayout.startAnimation(animation)
                                // swipe right
                            } else if (downX - upX > -100) {

                            }
                            return true

                        }
                        return false
                    }
                })
            } catch (ex: Exception)
            {}


            //Register receiver for determining screen off and if user is present
            mReceiver = LockScreenStateReceiver()
            val filter = IntentFilter(Intent.ACTION_SCREEN_OFF)
            filter.addAction(Intent.ACTION_USER_PRESENT)

            registerReceiver(mReceiver, filter)
        }
        catch (ex:Exception)
        {

        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
         return START_STICKY
       }

    inner class LockScreenStateReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            try {
                if (intent.action == Intent.ACTION_SCREEN_OFF) {
                    //if screen is turn off show the textview
                    if (!isShowing) {
                        windowManager.addView(myview, params)
                        isShowing = true
                    }
                } else if (intent.action == Intent.ACTION_USER_PRESENT) {
                    //Handle resuming events if user is present/screen is unlocked remove the textview immediately
                    if (isShowing) {
                        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
                            //windowManager.removeViewImmediate(myview)
                        }
                        isShowing = false
                    }
                }
            }
            catch (ex:Exception){

            }
        }

    }

    override fun onDestroy() {
        //unregister receiver when the service is destroy
        try {
            EventBus.getDefault().unregister(this)
            if (mReceiver != null) {
                unregisterReceiver(mReceiver)
            }

            //remove view if it is showing and the service is destroy
            if (isShowing) {
                if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
                    //windowManager.removeViewImmediate(myview)
                }
                isShowing = false
            }
        }
        catch (ex:Exception)
        {

        }
        super.onDestroy()
    }

}
2个回答

9
我非常感谢@Emir提供的答案,但他提供的代码在某些设备上无法运行,并且活动无法在锁定屏幕上启动。
Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

这里的问题是代码中的'OR'部分导致某些设备出现问题,为了解决这个问题,我做的是在'OR'部分内部不再调用window标志,而是单独调用所有标志。下面是我的onCreate中所做的。

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_lock_screen)
   //execute all flags individually to solve this issue
     window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
        window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD)
        window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
        val mUIFlag = ( View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_VISIBLE
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_IMMERSIVE
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)

        window.decorView.systemUiVisibility = mUIFlag

        }

我把这段代码放在启动画面上,但它没有显示在锁屏界面。我们应该在哪个活动中放置Windows标志代码? - Android Geek
@AndroidGeek 你需要将它放在用于显示在锁屏上的活动的oncreate中,只需遵循活动生命周期即可,它应该可以工作,但是在某些设备上仍然存在一些问题,但这真的很少见! - Mr. Patel
实际上,我已经在服务中创建了我的覆盖视图,但是现在为了删除这些过时的标志,我应该创建一个透明活动来显示我的视图。无论如何,谢谢! - Android Geek

2
我曾从事VOIP工作,基本上我的任务是当应用程序收到远程通知时,启动我的IncomingCallActivity来提醒用户有来电。这不需要覆盖或唤醒锁定权限。
你所需要做的就是为你的锁屏覆盖创建一个Activity,在服务中启动该Activity。在onCreate方法中添加相关的WindowManager标志到你的Activity的Window中即可。请参考WindowManagerWindow
 Window window = getWindow();
    window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

我在Oreo上测试过,它能正常工作。


2
当我使用这些标志时,它们在某些设备上仅显示,并且不像预期那样工作。此外,在一些拥有Pie的设备上也无法正常工作。然而,在三星平板电脑上运行相同的代码时,它可以正常工作而没有任何问题。 - Mr. Patel
它在华为Y6 Android P上运行良好。我的服务是前台服务,我不知道这是否有所不同。三星J4 Android 8也运行良好。所以你的意思是活动正在启动(在logcat上看到),但没有显示在锁屏界面上。 - Emir
如果你的代码在你的角度上运行正常,你可以将其Github链接分享给我,我会检查看是否有遗漏的地方! - Mr. Patel
很高兴你解决了问题。不过问题出在哪里呢? - Emir
等一下,我会在这里发布我解决这个问题的答案! - Mr. Patel
显示剩余11条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接