浮窗中addView()不显示 分析思路

看得到的真相往往可能是骗人的,但是代码不会. 如果程序中出现了”灵异”事件,一定是问题没有分析到位,亦或是分析问题的方向出错

最近在项目中,我需要在一个独立进程的不死服务中打开一个浮窗,但是浮窗不显示.但是在Activity中可以打开浮窗.这是问题的表现,其中不同点是创建WindowManager的Context不同,使用Activity的Context可以,使用Service和Application的Context都不可以.通过Log日志可以发现,addView()方法是执行了,但是View并没有显示出来.接下来,我就进行了各方位的尝试

一.分析中…

1.是否存在浮窗创建之后又被移除了?

屏蔽浮窗移除代码浮窗还是没有显示

2.参考网上提供方案把浮窗的创建过程放到服务中,即重启一个服务用于开启浮窗

代码如下,浮窗还是没有出现

class FloatWindowService : Service() {

    companion object {
        private const val TAG = "DaemonService"
        const val NOTICE_ID = 100


        fun openFloatWindowService(mContext: Context) {
            val intent = Intent(mContext, FloatWindowService::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            mContext.startService(intent)

        }
    }

    private val TAG = "FloatWindowManager"
    /**
     * 小悬浮窗View的实例
     */
    @SuppressLint("StaticFieldLeak")
    private var smallWindow: FloatWindowSmallView? = null

    /**
     * 小悬浮窗View的参数
     */
    private var smallWindowParams: WindowManager.LayoutParams? = null

    /**
     * 用于控制在屏幕上添加或移除悬浮窗
     */
    private var mWindowManager: WindowManager? = null

    /**
     * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
     *
     * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
     */
    var isWindowShowing: Boolean = false


    override fun onCreate() {
        super.onCreate()
        smallWindow = FloatWindowSmallView(applicationContext)
        smallWindowParams = WindowManager.LayoutParams()
        mWindowManager = getWindowManager(applicationContext)
        setSmallWindowParams(mWindowManager!!, smallWindowParams!!)

    }

    private fun setSmallWindowParams(windowManager: WindowManager,smallWindowParams: WindowManager.LayoutParams) {
        val outMetrics = DisplayMetrics()
        windowManager.defaultDisplay.getMetrics(outMetrics)
        val screenWidth = outMetrics.widthPixels
        val screenHeight = outMetrics.heightPixels
            smallWindowParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
//        smallWindowParams!!.format = PixelFormat.RGBA_8888
        smallWindowParams.format = PixelFormat.TRANSPARENT
        smallWindowParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        smallWindowParams!!.gravity = Gravity.LEFT or Gravity.TOP
        smallWindowParams!!.width = 600
        smallWindowParams!!.height = 600
        smallWindowParams!!.x = 0
        smallWindowParams!!.y = 0
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        smallWindow!!.setParams(smallWindowParams!!)
        mWindowManager!!.addView(smallWindow, smallWindowParams)
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
     *
     * @param context 必须为应用程序的Context.
     * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
     */
    private fun getWindowManager(context: Context): WindowManager {
        if (mWindowManager == null) {
            mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        }
        return mWindowManager!!
    }

    override fun onBind(intent: Intent?): IBinder {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

3.WindowManager问题

在我们平时写代码的时候,经常要用到windowmanager这个类,一般是通过addview的方式,把一个view添加到一个窗口中,在系统中,比如toast,悬浮框,状态栏,通知栏,锁屏界面他们都不是平常的activity中的window,因此通过windowmanager 直接addview的方式来呈现给用户

WindowManager是一个接口,并继承了ViewManager接口.ViewManager有三个方法,
addView,updateViewLayout,removeView,分别用于添加View,更新View的布局和移除View,由于WindowManager继承ViewManager,可见WindowManager也是对View进行管理,其中WindowManager有多了一个removeViewImmediate()用于立刻移除View,用于Activity中,防止Activity销毁之后造成窗口溢出

public interface ViewManager
{

    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

而从打印的Log信息来看,addView()是执行成功,没有异常的,所以浮窗是添加成功的.但是在创建浮窗和移除浮窗的时候,一定要先确认浮窗addView.如果浮窗已经添加了,再次添加会报异常;如果浮窗没有添加,在执行removeView(View view)会报异常

4.浮窗不显示是因为浮窗的宽高为0

(1)将浮窗layout中的宽高赋值,而不是wrap_content

(2)将addView(View view, ViewGroup.LayoutParams params)浮窗params的宽高写死为200,但是还是没有显示.但是点击浮窗所在的位置,会执行updateViewLayout方法和浮窗的点击事件,由此更加确认浮窗已经存在

5.在浮窗的构造方法中添加setBackgroundColor(Color.GREEN)

这个时候出现了一个绿色的方块儿区域,但是图片还是没有显示出来

6.在layout中的布局中添加背景

背景显示出来了,图片未显示

7.将app:srcCompat换为android:src

图片显示出来了,本来直接给出结果就可以了,但是我还是梳理了整个分析的流程.

接下来就是揭晓原因的时候了

使用 app:srcCompat 的时候 引入的图片显示不出来的解决方案
首先查看的你的Activity 继承的是那个Activity 如果是继承AppcompatActivity 使用 ImageView的 app:srcCompat 是没有问题的
如果你的Activity不是继承的AppcompatActivity, 需要用到 android.support.v7.widget.AppCompatImageView 代替 ImageView。

这样就可以解释为什么我可以通过Activity打开浮窗,而Service和Application的Context都不可以,当然我的Activity是继承AppcompatActivity.这里有两种修改方式,如下

方法一:修改ImageViewAppCompatImageView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/athanasy_float_window"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher_round"
        />

</LinearLayout>

方法二:修改 app:srcCompat为“““

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical">

     <ImageView
        android:id="@+id/athanasy_float_window"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher_round"
        />

</LinearLayout>

二.浮窗源码:

1. FloatWindowSmallView

package com.hp.athanasyservice.xiaoqi.floatwindow

import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.WindowManager
import android.widget.LinearLayout
import com.billy.cc.core.component.CC
import com.blankj.utilcode.util.ReflectUtils
import com.hp.athanasyservice.R
import com.hp.baseres.base.BaseApplication.Companion.mContext
import com.hp.baseres.utils.LogUtils
import com.hp.business.AppCCConstants
import kotlinx.android.synthetic.main.athanasy_small_float_window.view.*

/**
 * Created by tangdekun on 2017/6/2.
 */

class FloatWindowSmallView(mContext: Context) : LinearLayout(mContext) {

    /**
     * 用于更新小悬浮窗的位置
     */
    private val windowManager: WindowManager = mContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager

    /**
     * 小悬浮窗的参数
     */
    private var mParams: WindowManager.LayoutParams? = null

    /**
     * 记录当前手指位置在屏幕上的横坐标值
     */
    private var xInScreen: Float = 0.toFloat()

    /**
     * 记录当前手指位置在屏幕上的纵坐标值
     */
    private var yInScreen: Float = 0.toFloat()

    /**
     * 记录手指按下时在屏幕上的横坐标的值
     */
    private var xDownInScreen: Float = 0.toFloat()

    /**
     * 记录手指按下时在屏幕上的纵坐标的值
     */
    private var yDownInScreen: Float = 0.toFloat()

    /**
     * 记录手指按下时在小悬浮窗的View上的横坐标的值
     */
    private var xInView: Float = 0.toFloat()

    /**
     * 记录手指按下时在小悬浮窗的View上的纵坐标的值
     */
    private var yInView: Float = 0.toFloat()
    private var screenWidth: Float = 0.toFloat()
    private var screenHeight: Float = 0.toFloat()


    init {
        LayoutInflater.from(mContext).inflate(R.layout.athanasy_small_float_window, this)
        viewWidth = athanasy_float_window.layoutParams.width
        viewHeight = athanasy_float_window.layoutParams.height
    }


    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                // 手指按下时记录必要数据,纵坐标的值都需要减去状态栏高度
                xInView = event.x
                yInView = event.y
                xDownInScreen = event.rawX
                yDownInScreen = event.rawY - getStatusBarHeight()
                xInScreen = event.rawX
                yInScreen = event.rawY - getStatusBarHeight()
            }
            MotionEvent.ACTION_MOVE -> {
                xInScreen = event.rawX
                yInScreen = event.rawY - getStatusBarHeight()
                // 手指移动的时候更新小悬浮窗的位置
                updateViewPosition()
            }
            MotionEvent.ACTION_UP -> {
                val t = System.currentTimeMillis().toFloat()
                // 如果手指离开屏幕时,xDownInScreen和xInScreen相等,且yDownInScreen和yInScreen相等,则视为触发了单击事件。
                updateViewPositionFinish()
                LogUtils.d(TAG, "updateViewPositionFinish():" + (System.currentTimeMillis() - t))
                if (xDownInScreen == xInScreen && yDownInScreen == yInScreen) {
                    openBigWindow()
                }
                LogUtils.d(TAG, "openBigWindow():" + (System.currentTimeMillis() - t))
            }

        }
        return true
    }

    /**
     * 更新浮窗移动完毕的位置
     */
    private fun updateViewPositionFinish() {
        mParams!!.y = (yInScreen - yInView).toInt()
        screenWidth = windowManager.defaultDisplay.width.toFloat()
        screenHeight = windowManager.defaultDisplay.height.toFloat()
        if (xInScreen <= screenWidth / 2) {
            mParams!!.x = 0
        } else {
            mParams!!.x = screenWidth.toInt()
        }
        windowManager.updateViewLayout(this, mParams)
        mXProportion = mParams!!.x / screenWidth
        mYProportion = mParams!!.y / screenHeight
    }

    /**
     * 将小悬浮窗的参数传入,用于更新小悬浮窗的位置。
     *
     * @param params 小悬浮窗的参数
     */
    fun setParams(params: WindowManager.LayoutParams) {
        mParams = params
    }

    /**
     * 更新小悬浮窗在屏幕中的位置。
     */
    private fun updateViewPosition() {
        mParams!!.x = (xInScreen - xInView).toInt()
        mParams!!.y = (yInScreen - yInView).toInt()
        windowManager.updateViewLayout(this, mParams)
    }

    /**
     * 打开Activity
     */
    private fun openBigWindow() {
        CC.obtainBuilder(AppCCConstants.COMPONENT_NAME)
                .setActionName(AppCCConstants.ACTION_OPEN_MAINACTIVITY)
                .setContext(mContext)
                .build()
                .call()
    }

    /**
     * 用于获取状态栏的高度。
     *
     * @return 返回状态栏高度的像素值。
     */
    private fun getStatusBarHeight(): Int {

        if (statusBarHeight == 0) {
            try {
                val x = ReflectUtils.reflect("com.android.internal.R\$dimen").field("status_bar_height").get<Int>()
                statusBarHeight = resources.getDimensionPixelSize(x)
            } catch (e: Exception) {
                e.printStackTrace()
            }

        }
        return statusBarHeight
    }

    companion object {

        val TAG = FloatWindowSmallView::class.java.name!!
        /**
         * 记录每次浮窗当前的位置  --每次切换旋转屏幕按比例跳转位置
         */
        var mXProportion = 1f
        var mYProportion = 0.5f
        /**
         * 记录小悬浮窗的宽度
         */
        var viewWidth: Int = 0

        /**
         * 记录小悬浮窗的高度
         */
        var viewHeight: Int = 0

        /**
         * 记录系统状态栏的高度
         */
        private var statusBarHeight: Int = 0
    }
}

2. FloatWindowManager

package com.hp.athanasyservice.xiaoqi.floatwindow

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import com.hp.baseres.utils.LogUtils


/**
 * Created by tangdekun on 2017/6/2.
 */

object FloatWindowManager {

    private val TAG = "FloatWindowManager"
    /**
     * 小悬浮窗View的实例
     */
    @SuppressLint("StaticFieldLeak")
    private var smallWindow: FloatWindowSmallView? = null

    /**
     * 小悬浮窗View的参数
     */
    private var smallWindowParams: LayoutParams? = null

    /**
     * 用于控制在屏幕上添加或移除悬浮窗
     */
    private var mWindowManager: WindowManager? = null

    /**
     * 是否有悬浮窗(包括小悬浮窗和大悬浮窗)显示在屏幕上。
     *
     * @return 有悬浮窗显示在桌面上返回true,没有的话返回false。
     */
    var isWindowShowing: Boolean = false

    /**
     * 创建一个小悬浮窗。初始位置为屏幕的右部中间位置。
     *
     * @param context 必须为应用程序的Context.
     */
    fun createSmallWindow(context: Context) {
        val windowManager = getWindowManager(context)

        fun addView(context: Context, windowManager: WindowManager) {
            LogUtils.dTag(TAG, "创建浮窗$context")
            if (smallWindow == null) {
                smallWindow = FloatWindowSmallView(context)
                if (smallWindowParams == null) {
                    setSmallWindowParams(context)
                }
                smallWindow!!.setParams(smallWindowParams!!)
                LogUtils.dTag(TAG, smallWindowParams.toString())
            }
            windowManager.addView(smallWindow, smallWindowParams)
            isWindowShowing = true
        }
        if (!isWindowShowing) {
            addView(context, windowManager)
        } else {
            LogUtils.dTag(TAG, "浮窗已存在$context")
        }

    }


    /**
     * 重置小浮窗位置
     *
     * @param context
     */
    fun resetSmallWindowPosition(context: Context) {
        LogUtils.dTag(TAG, "resetSmallWindowPosition")
        if (smallWindowParams != null) {
            val windowManager = getWindowManager(context)
            val outMetrics = DisplayMetrics()
            windowManager.defaultDisplay.getMetrics(outMetrics)
            val screenWidth = outMetrics.widthPixels
            val screenHeight = outMetrics.heightPixels
            smallWindowParams!!.x = (FloatWindowSmallView.mXProportion * screenWidth).toInt()
            smallWindowParams!!.y = (FloatWindowSmallView.mYProportion * screenHeight).toInt()
            if (smallWindow != null) {
                getWindowManager(context).updateViewLayout(smallWindow, smallWindowParams)
            }
        }

    }

    private fun setSmallWindowParams(context: Context) {
        val windowManager = getWindowManager(context)
        val outMetrics = DisplayMetrics()
        windowManager.defaultDisplay.getMetrics(outMetrics)
        val screenWidth = outMetrics.widthPixels
        val screenHeight = outMetrics.heightPixels
        smallWindowParams = LayoutParams()
        smallWindowParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
        smallWindowParams!!.format = PixelFormat.RGBA_8888
        smallWindowParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        smallWindowParams!!.gravity = Gravity.LEFT or Gravity.TOP
        smallWindowParams!!.width = FloatWindowSmallView.viewWidth
        smallWindowParams!!.height = FloatWindowSmallView.viewHeight
        smallWindowParams!!.x = screenWidth
        smallWindowParams!!.y = screenHeight / 2
    }

    /**
     * 将小悬浮窗从屏幕上移除。
     *
     * @param context 必须为应用程序的Context.
     */
    fun removeSmallWindow(context: Context) {
        if (smallWindow != null && isWindowShowing) {
            LogUtils.dTag(TAG, "删除浮窗$context")
            val windowManager = getWindowManager(context)
            windowManager.removeView(smallWindow)
            isWindowShowing = false
            smallWindow = null
            smallWindowParams = null
        } else {
            LogUtils.dTag(TAG, "$context 浮窗不存在,无法删除")
        }

    }

    /**
     * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
     *
     * @param context 必须为应用程序的Context.
     * @return WindowManager的实例,用于控制在屏幕上添加或移除悬浮窗。
     */
    private fun getWindowManager(context: Context): WindowManager {
        if (mWindowManager == null) {
            mWindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        }
        return mWindowManager!!
    }


}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值