欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 實(shí)現(xiàn)懸浮窗功能

 更新時(shí)間:2019年09月03日 14:09:46   作者:黃林晴  
這篇文章主要介紹了Android 實(shí)現(xiàn)懸浮窗功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

我們大多數(shù)在兩種情況下可以看到懸浮窗,一個(gè)是視頻通話時(shí)的懸浮窗,另一個(gè)是360衛(wèi)士的懸浮球,實(shí)現(xiàn)此功能的方式比較多,這里以視頻通話懸浮窗中的需求為例。編碼實(shí)現(xiàn)使用Kotlin。Java版本留言郵箱即可。

業(yè)務(wù)場(chǎng)景

以微信視頻通話為例,在視頻通話時(shí),我們打開(kāi)其他應(yīng)用或點(diǎn)擊Home鍵退出時(shí)或點(diǎn)擊縮放圖標(biāo),懸浮窗會(huì)顯示在其他應(yīng)用之上,給人的假象是通話頁(yè)面變小了,點(diǎn)擊懸浮窗回到通過(guò)頁(yè)面,懸浮窗消失。退出通話頁(yè)面懸浮窗消失。

業(yè)務(wù)場(chǎng)景技術(shù)分析

在編碼之前,我們必須將流程整理好,這樣更有利于編碼的實(shí)現(xiàn)。實(shí)現(xiàn)一個(gè)功能如果需要10分鐘,思考的時(shí)間是7分鐘,編碼占用的時(shí)間只是三分鐘。

1.懸浮窗可以顯示在其他應(yīng)用或launchers之上,這個(gè)肯定需要懸浮窗權(quán)限,而懸浮窗權(quán)限屬于特殊權(quán)限,所以只能通過(guò)引導(dǎo)用戶去打開(kāi)無(wú)法像危險(xiǎn)權(quán)限那樣直接申請(qǐng)。可以做到后臺(tái)顯示則說(shuō)明懸浮窗是一個(gè)Service。

2.通話頁(yè)面隱藏時(shí)懸浮窗顯示,通話頁(yè)面顯示時(shí)懸浮窗隱藏,可以看出懸浮窗和Activity的生命周期相關(guān)聯(lián),所以懸浮窗的Service和通話頁(yè)面的Activity是通過(guò)bind去綁定的。

3.既然Service和Activity是通過(guò)bind去綁定的,說(shuō)明當(dāng)懸浮窗顯示的時(shí)候,通話Activity雖然不可見(jiàn)但仍在運(yùn)行。

結(jié)合上述技術(shù)問(wèn)題分析,我們倒敘一一通過(guò)編碼實(shí)現(xiàn)

懸浮窗實(shí)現(xiàn)方案

實(shí)現(xiàn)效果      

準(zhǔn)備工作

       首先我們新建一個(gè)項(xiàng)目,項(xiàng)目中有兩個(gè)Activity,我們?cè)诘诙€(gè)Activity編寫(xiě)通話模擬頁(yè)面。在第二個(gè)頁(yè)面的原因我們后面會(huì)講到。

如何將acitivity置于后臺(tái)

其實(shí)很簡(jiǎn)單,我們調(diào)用一個(gè)方法即可

moveTaskToBack(true);

這個(gè)方法的含義就是將當(dāng)前的任務(wù)戰(zhàn)置于后臺(tái),so,為什么我要在第二個(gè)Activity中實(shí)現(xiàn)的原因之一,因?yàn)槟J(rèn)的Activity的啟動(dòng)模式是標(biāo)準(zhǔn)模式,而上面方法會(huì)將任務(wù)棧置于后臺(tái)而不是一個(gè)單獨(dú)的Activity,所以我們?yōu)榱孙@示懸浮窗時(shí)不影響操作軟件的其他功能,我們要將通話頁(yè)面的Activity設(shè)置為singleInstance,這樣當(dāng)調(diào)用上面方法的時(shí)候只是將通話頁(yè)面所在的Activity棧置于后臺(tái),如果你還不了解啟動(dòng)模式可以移步至上一篇文章:Activity的啟動(dòng)模式。

我們現(xiàn)在在右上方的點(diǎn)擊事件中添加上述代碼,可以看到通話頁(yè)面的Activity的已經(jīng)在后臺(tái)運(yùn)行了。

判斷是否有懸浮窗權(quán)限

點(diǎn)擊左上角圖標(biāo)時(shí),我們要先判斷當(dāng)前app是否有懸浮窗權(quán)限,首先我們?cè)谂渲梦募刑砑?,懸浮窗的?quán)限。

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

(很多文章標(biāo)題都是懸浮窗如何繞過(guò)權(quán)限,什么設(shè)置類(lèi)型為T(mén)OAST或者PHONE,我想說(shuō)不可能的事,TOAST類(lèi)型的雖然部分機(jī)型可以顯示但是就是一個(gè)普通的TOSAT會(huì)自動(dòng)消失)

那么我們?nèi)绾闻袛嗍欠裼袘腋〈皺?quán)限呢,這一塊不同廠商處理方案可能不一樣,這里我們用一種通用的處理方案,測(cè)試表明除了(vivo部分)無(wú)效,其他多數(shù)機(jī)型都o(jì)k。并且vivo部分機(jī)型微信通話也不會(huì)彈出提示(這我就放心了~)

fun zoom(v: View) {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
      Toast.makeText(this, "當(dāng)前無(wú)權(quán)限,請(qǐng)授權(quán)", Toast.LENGTH_SHORT)
      GlobalDialogSingle(this, "", "當(dāng)前未獲取懸浮窗權(quán)限", "去開(kāi)啟", DialogInterface.OnClickListener { dialog, which ->
        dialog.dismiss()
        startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
      }).show()
 
    } else {
      moveTaskToBack(true)
      val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
      hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
    }
  }
}

我們通過(guò)Settings.canDrawOverlays(this)來(lái)判斷當(dāng)前應(yīng)用是否有懸浮窗權(quán)限,如果沒(méi)有,我們彈窗提示,通過(guò)

startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)

 跳轉(zhuǎn)到開(kāi)啟懸浮窗權(quán)限頁(yè)面。如果懸浮窗權(quán)限已開(kāi)啟,直接將當(dāng)前任務(wù)棧置于后臺(tái),開(kāi)啟服務(wù)即可。

其實(shí)回調(diào)方法,并沒(méi)有直接告訴我們是否授權(quán)成功,所以我們需要在回調(diào)中再次判斷

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
  if (requestCode == 0) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "授權(quán)失敗", Toast.LENGTH_SHORT).show()
      } else {
        Handler().postDelayed({
          val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
          intent.putExtra("rangeTime", rangeTime)
          hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
          moveTaskToBack(true)
        }, 1000)
 
      }
    }
  }
}

這里我們可以看到回調(diào)中延遲了1秒,因?yàn)闇y(cè)試發(fā)現(xiàn)某些機(jī)型反應(yīng)“過(guò)快”,收到回調(diào)的時(shí)候還以為沒(méi)有授權(quán)成功,其實(shí)已經(jīng)成功了。

綁定Service我們需要一個(gè)ServiceConnection對(duì)象

internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
  override fun onServiceConnected(name: ComponentName, service: IBinder) {
    // 獲取服務(wù)的操作對(duì)象
    val binder = service as FloatWinfowServices.MyBinder
    binder.service
  }
  override fun onServiceDisconnected(name: ComponentName) {}
}

Main2Activity的完整代碼如下所示:

/**
 * @author Huanglinqing
 */
class Main2Activity : AppCompatActivity() {
  private val chronometer: Chronometer? = null
  private var hasBind = false
  private val rangeTime: Long = 0
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main2)
  }
  fun zoom(v: View) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if (!Settings.canDrawOverlays(this)) {
        Toast.makeText(this, "當(dāng)前無(wú)權(quán)限,請(qǐng)授權(quán)", Toast.LENGTH_SHORT)
        GlobalDialogSingle(this, "", "當(dāng)前未獲取懸浮窗權(quán)限", "去開(kāi)啟", DialogInterface.OnClickListener { dialog, which ->
          dialog.dismiss()
          startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + packageName)), 0)
        }).show()
      } else {
        moveTaskToBack(true)
        val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
        hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
      }
    }
  }
  internal var mVideoServiceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
      // 獲取服務(wù)的操作對(duì)象
      val binder = service as FloatWinfowServices.MyBinder
      binder.service
    }
    override fun onServiceDisconnected(name: ComponentName) {}
  }
  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    if (requestCode == 0) {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if (!Settings.canDrawOverlays(this)) {
          Toast.makeText(this, "授權(quán)失敗", Toast.LENGTH_SHORT).show()
        } else {
          Handler().postDelayed({
            val intent = Intent(this@Main2Activity, FloatWinfowServices::class.java)
            intent.putExtra("rangeTime", rangeTime)
            hasBind = bindService(intent, mVideoServiceConnection, Context.BIND_AUTO_CREATE)
            moveTaskToBack(true)
          }, 1000)
        }
      }
    }
  }
  override fun onRestart() {
    super.onRestart()
    Log.d("RemoteView", "重新顯示了")
    //不顯示懸浮框
    if (hasBind) {
      unbindService(mVideoServiceConnection)
      hasBind = false
    }
  }
  override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
  }
  override fun onDestroy() {
    super.onDestroy()
  }
}

新建懸浮窗Service

新建懸浮窗Service FloatWinfowServices,因?yàn)槲覀兪褂玫腂indService,我們?cè)趏nBind方法中初始化service中的布局

override fun onBind(intent: Intent): IBinder? {
  initWindow()
  //懸浮框點(diǎn)擊事件的處理
  initFloating()
  return MyBinder()
}

service中我們通過(guò)WindowManager來(lái)添加一個(gè)布局顯示。

/**
 * 初始化窗口
 */
private fun initWindow() {
  winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
  //設(shè)置好懸浮窗的參數(shù)
  wmParams = params
  // 懸浮窗默認(rèn)顯示以左上角為起始坐標(biāo)
  wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
  //懸浮窗的開(kāi)始位置,因?yàn)樵O(shè)置的是從左上角開(kāi)始,所以屏幕左上角是x=0;y=0
  wmParams!!.x = winManager!!.defaultDisplay.width
  wmParams!!.y = 210
  //得到容器,通過(guò)這個(gè)inflater來(lái)獲得懸浮窗控件
  inflater = LayoutInflater.from(applicationContext)
  // 獲取浮動(dòng)窗口視圖所在布局
  mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
  // 添加懸浮窗的視圖
  winManager!!.addView(mFloatingLayout, wmParams)
}

懸浮窗的參數(shù)主要設(shè)置懸浮窗的類(lèi)型為

WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY

8.0 以下可設(shè)置為:

wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE

代碼如下所示:

private //設(shè)置window type 下面變量2002是在屏幕區(qū)域顯示,2003則可以顯示在狀態(tài)欄之上
    //設(shè)置可以顯示在狀態(tài)欄上
    //設(shè)置懸浮窗口長(zhǎng)寬數(shù)據(jù)
val params: WindowManager.LayoutParams
  get() {
    wmParams = WindowManager.LayoutParams()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
    } else {
      wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
    }
    wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
    wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
    wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
    return wmParams
  }

當(dāng)點(diǎn)擊懸浮窗的時(shí)候回到Activity2頁(yè)面,并且懸浮窗消失,所以我們只需要給懸浮窗添加點(diǎn)擊事件

linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }

當(dāng)Service走到onDestory的時(shí)候?qū)iew移除,對(duì)于Activity2頁(yè)面來(lái)說(shuō) 當(dāng)onResume的時(shí)候 解綁Service,當(dāng)onstop的時(shí)候 綁定Service。

從效果圖中我們可以看到懸浮窗可以拖拽的,所以還要設(shè)置觸摸事件,當(dāng)移動(dòng)距離超過(guò)某個(gè)值的時(shí)候讓onTouch消費(fèi)事件,這樣就不會(huì)觸發(fā)點(diǎn)擊事件了。這個(gè)算是view比較基礎(chǔ)的知識(shí),相信大家都明白了。

//開(kāi)始觸控的坐標(biāo),移動(dòng)時(shí)的坐標(biāo)(相對(duì)于屏幕左上角的坐標(biāo))
private var mTouchStartX: Int = 0
private var mTouchStartY: Int = 0
private var mTouchCurrentX: Int = 0
private var mTouchCurrentY: Int = 0
//開(kāi)始時(shí)的坐標(biāo)和結(jié)束時(shí)的坐標(biāo)(相對(duì)于自身控件的坐標(biāo))
private var mStartX: Int = 0
private var mStartY: Int = 0
private var mStopX: Int = 0
private var mStopY: Int = 0
//判斷懸浮窗口是否移動(dòng),這里做個(gè)標(biāo)記,防止移動(dòng)后松手觸發(fā)了點(diǎn)擊事件
private var isMove: Boolean = false
private inner class FloatingListener : View.OnTouchListener {
  override fun onTouch(v: View, event: MotionEvent): Boolean {
    val action = event.action
    when (action) {
      MotionEvent.ACTION_DOWN -> {
        isMove = false
        mTouchStartX = event.rawX.toInt()
        mTouchStartY = event.rawY.toInt()
        mStartX = event.x.toInt()
        mStartY = event.y.toInt()
      }
      MotionEvent.ACTION_MOVE -> {
        mTouchCurrentX = event.rawX.toInt()
        mTouchCurrentY = event.rawY.toInt()
        wmParams!!.x += mTouchCurrentX - mTouchStartX
        wmParams!!.y += mTouchCurrentY - mTouchStartY
        winManager!!.updateViewLayout(mFloatingLayout, wmParams)
        mTouchStartX = mTouchCurrentX
        mTouchStartY = mTouchCurrentY
      }
      MotionEvent.ACTION_UP -> {
        mStopX = event.x.toInt()
        mStopY = event.y.toInt()
        if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
          isMove = true
        }
      }
      else -> {
      }
    }
    //如果是移動(dòng)事件不觸發(fā)OnClick事件,防止移動(dòng)的時(shí)候一放手形成點(diǎn)擊事件
    return isMove
  }
}

FloatWinfowServices所有代碼如下所示:

class FloatWinfowServices : Service() {
   private var winManager: WindowManager? = null
  private var wmParams: WindowManager.LayoutParams? = null
  private var inflater: LayoutInflater? = null
  //浮動(dòng)布局
  private var mFloatingLayout: View? = null
  private var linearLayout: LinearLayout? = null
  private var chronometer: Chronometer? = null
  override fun onBind(intent: Intent): IBinder? {
    initWindow()
    //懸浮框點(diǎn)擊事件的處理
    initFloating()
    return MyBinder()
  }
  inner class MyBinder : Binder() {
    val service: FloatWinfowServices
      get() = this@FloatWinfowServices
  }
  override fun onCreate() {
    super.onCreate()
  }
  /**
   * 懸浮窗點(diǎn)擊事件
   */
  private fun initFloating() {
    linearLayout = mFloatingLayout!!.findViewById<LinearLayout>(R.id.line1)
    linearLayout!!.setOnClickListener { startActivity(Intent(this@FloatWinfowServices, Main2Activity::class.java)) }
    //懸浮框觸摸事件,設(shè)置懸浮框可拖動(dòng)
    linearLayout!!.setOnTouchListener(FloatingListener())
  }
  //開(kāi)始觸控的坐標(biāo),移動(dòng)時(shí)的坐標(biāo)(相對(duì)于屏幕左上角的坐標(biāo))
  private var mTouchStartX: Int = 0
  private var mTouchStartY: Int = 0
  private var mTouchCurrentX: Int = 0
  private var mTouchCurrentY: Int = 0
  //開(kāi)始時(shí)的坐標(biāo)和結(jié)束時(shí)的坐標(biāo)(相對(duì)于自身控件的坐標(biāo))
  private var mStartX: Int = 0
  private var mStartY: Int = 0
  private var mStopX: Int = 0
  private var mStopY: Int = 0
  //判斷懸浮窗口是否移動(dòng),這里做個(gè)標(biāo)記,防止移動(dòng)后松手觸發(fā)了點(diǎn)擊事件
  private var isMove: Boolean = false
  private inner class FloatingListener : View.OnTouchListener {
    override fun onTouch(v: View, event: MotionEvent): Boolean {
      val action = event.action
      when (action) {
        MotionEvent.ACTION_DOWN -> {
          isMove = false
          mTouchStartX = event.rawX.toInt()
          mTouchStartY = event.rawY.toInt()
          mStartX = event.x.toInt()
          mStartY = event.y.toInt()
        }
        MotionEvent.ACTION_MOVE -> {
          mTouchCurrentX = event.rawX.toInt()
          mTouchCurrentY = event.rawY.toInt()
          wmParams!!.x += mTouchCurrentX - mTouchStartX
          wmParams!!.y += mTouchCurrentY - mTouchStartY
          winManager!!.updateViewLayout(mFloatingLayout, wmParams)
          mTouchStartX = mTouchCurrentX
          mTouchStartY = mTouchCurrentY
        }
        MotionEvent.ACTION_UP -> {
          mStopX = event.x.toInt()
          mStopY = event.y.toInt()
          if (Math.abs(mStartX - mStopX) >= 1 || Math.abs(mStartY - mStopY) >= 1) {
            isMove = true
          }
        }
        else -> {
        }
      }
      //如果是移動(dòng)事件不觸發(fā)OnClick事件,防止移動(dòng)的時(shí)候一放手形成點(diǎn)擊事件
      return isMove
    }
  }
  /**
   * 初始化窗口
   */
  private fun initWindow() {
    winManager = application.getSystemService(Context.WINDOW_SERVICE) as WindowManager
    //設(shè)置好懸浮窗的參數(shù)
    wmParams = params
    // 懸浮窗默認(rèn)顯示以左上角為起始坐標(biāo)
    wmParams!!.gravity = Gravity.LEFT or Gravity.TOP
    //懸浮窗的開(kāi)始位置,因?yàn)樵O(shè)置的是從左上角開(kāi)始,所以屏幕左上角是x=0;y=0
    wmParams!!.x = winManager!!.defaultDisplay.width
    wmParams!!.y = 210
    //得到容器,通過(guò)這個(gè)inflater來(lái)獲得懸浮窗控件
    inflater = LayoutInflater.from(applicationContext)
    // 獲取浮動(dòng)窗口視圖所在布局
    mFloatingLayout = inflater!!.inflate(R.layout.remoteview, null)
    chronometer = mFloatingLayout!!.findViewById<Chronometer>(R.id.chronometer)
    chronometer!!.start()
    // 添加懸浮窗的視圖
    winManager!!.addView(mFloatingLayout, wmParams)
  }
  private //設(shè)置window type 下面變量2002是在屏幕區(qū)域顯示,2003則可以顯示在狀態(tài)欄之上
      //設(shè)置可以顯示在狀態(tài)欄上
      //設(shè)置懸浮窗口長(zhǎng)寬數(shù)據(jù)
  val params: WindowManager.LayoutParams
    get() {
      wmParams = WindowManager.LayoutParams()
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
      } else {
        wmParams!!.type = WindowManager.LayoutParams.TYPE_PHONE
      }
      wmParams!!.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
          WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
          WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
      wmParams!!.width = WindowManager.LayoutParams.WRAP_CONTENT
      wmParams!!.height = WindowManager.LayoutParams.WRAP_CONTENT
      return wmParams
    }
  override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
    return super.onStartCommand(intent, flags, startId)
  }
  override fun onDestroy() {
    super.onDestroy()
    winManager!!.removeView(mFloatingLayout)
  }
}

實(shí)際應(yīng)用中需要考慮的一些其他問(wèn)題

在使用使用的過(guò)程中,我們肯定會(huì)遇到其他問(wèn)題:

1.用戶使用過(guò)程中,可能會(huì)直接按Home鍵,這個(gè)時(shí)候如何提示呢?

   產(chǎn)生問(wèn)題原因:因?yàn)橛脩舭碒ome鍵之后,開(kāi)發(fā)者無(wú)法重寫(xiě)Home鍵邏輯,此時(shí)應(yīng)用不在前臺(tái)運(yùn)行,無(wú)法彈窗提醒,此時(shí)用戶點(diǎn)擊APP圖標(biāo)進(jìn)入的是第一個(gè)棧,這個(gè)時(shí)候用戶就沒(méi)有進(jìn)入通話頁(yè)面的入口了。

  解決方案:

  第一種解決方案 我們可以仿照微信那樣去做,就是在整個(gè)通話過(guò)程中開(kāi)啟一個(gè)前臺(tái)通知,用戶點(diǎn)擊通知時(shí)進(jìn)入通話頁(yè)面。

  第二種解決方案 就是檢測(cè)應(yīng)用是否在前臺(tái),當(dāng)通話頁(yè)面在運(yùn)行的時(shí)候,并且應(yīng)用重新回到前臺(tái),我們廣播到其他頁(yè)面,提示權(quán)限引導(dǎo)即可。

2.用戶在通話頁(yè)面(singleInstance模式),點(diǎn)擊Home鍵

應(yīng)用在后臺(tái)運(yùn)行的時(shí)候,通話結(jié)束,Activity被finish,此時(shí)從任務(wù)程序中切回應(yīng)用你會(huì)發(fā)現(xiàn)打開(kāi)的竟然是通話頁(yè)面!

這個(gè)問(wèn)題簡(jiǎn)單的說(shuō)就是,如果你在通話頁(yè)面呼叫某人,通話過(guò)程中按Home鍵,然后電話掛斷,此時(shí)你從任務(wù)程序中切回應(yīng)用,會(huì)再次呼叫這個(gè)人,也就是這種狀態(tài)下重新回到了onCreate方法。

問(wèn)題產(chǎn)生原因:

1.因?yàn)橥ㄔ掜?yè)面是singleInstance模式,此時(shí)有兩個(gè)任務(wù)棧,按Home鍵后再?gòu)娜蝿?wù)程序中切回,此時(shí)應(yīng)用只保留了第二個(gè)任務(wù)棧,已經(jīng)失去了和第一個(gè)任務(wù)棧的關(guān)系,finish之后無(wú)法在回到第一個(gè)任務(wù)棧。

解決方案:

1.(不推薦)通話頁(yè)面不使用singleInstance模式,這種情況下,在通話過(guò)程中無(wú)法操作軟件的其他功能,一般都不采取。

2.(我目前的解決方案)設(shè)置一個(gè)標(biāo)記位,標(biāo)記當(dāng)前是否在通話,在onCreate中如果通話已經(jīng)結(jié)束了,跳轉(zhuǎn)到一個(gè)過(guò)渡頁(yè)面(標(biāo)準(zhǔn)模式),過(guò)渡頁(yè)面中finish,就可以了,添加過(guò)渡頁(yè)面的原因是我們不知道上一個(gè)頁(yè)面是哪里,因?yàn)槲覀兪盏絹?lái)電可能是任意頁(yè)面,我們我們?cè)谶^(guò)渡頁(yè)面finsh之后,就再次回到了第一個(gè)任務(wù)棧。

總結(jié)

以上所述是小編給大家介紹的Android 實(shí)現(xiàn)懸浮窗功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!

相關(guān)文章

  • 發(fā)布?Android?library?到?Maven?解析

    發(fā)布?Android?library?到?Maven?解析

    這篇文章主要介紹了發(fā)布?Android?library到Maven解析,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • Android編程之內(nèi)存溢出解決方案(OOM)實(shí)例總結(jié)

    Android編程之內(nèi)存溢出解決方案(OOM)實(shí)例總結(jié)

    這篇文章主要介紹了Android編程之內(nèi)存溢出解決方案(OOM),結(jié)合實(shí)例實(shí)例總結(jié)分析了Android編程過(guò)程中常見(jiàn)的內(nèi)存溢出情況與對(duì)應(yīng)的解決方法,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11
  • Android 文件選擇器詳解及實(shí)例代碼

    Android 文件選擇器詳解及實(shí)例代碼

    這篇文章主要介紹了Android 文件選擇器詳解的相關(guān)資料,并附實(shí)例代碼,需要的朋友可以參考下
    2016-10-10
  • Android Studio 3.0 原生支持kotlin 例子詳解

    Android Studio 3.0 原生支持kotlin 例子詳解

    這篇文章主要介紹了 Android Studio 3.0 原生支持kotlin 例子詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-05-05
  • Android判斷字符串中是否含字母、中文或數(shù)字

    Android判斷字符串中是否含字母、中文或數(shù)字

    這篇文章主要介紹了Android判斷字符串中是否含字母、中文或數(shù)字的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Android自定義EditText實(shí)現(xiàn)淘寶登錄功能

    Android自定義EditText實(shí)現(xiàn)淘寶登錄功能

    這篇文章主要為大家詳細(xì)介紹了Android自定義EditText實(shí)現(xiàn)淘寶登錄功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • android?studio組件通信:Intend啟動(dòng)Activity接收返回結(jié)果

    android?studio組件通信:Intend啟動(dòng)Activity接收返回結(jié)果

    這篇文章主要介紹了android?studio組件通信:Intend啟動(dòng)Activity接收返回結(jié)果,設(shè)計(jì)一個(gè)主Activity和一個(gè)子Activity(Sub-Activity),使用主Activity上的按鈕啟動(dòng)子Activity,并將子Activity的一些信息返回給主Activity,并顯示在主Activity上,需要的朋友可以參考一下
    2021-12-12
  • Flutter Reusable Lottie Animations技巧

    Flutter Reusable Lottie Animations技巧

    這篇文章主要為大家介紹了Flutter Reusable Lottie Animations技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Android開(kāi)發(fā)之自定義UI組件詳解

    Android開(kāi)發(fā)之自定義UI組件詳解

    這篇文章主要給大家介紹了關(guān)于Android開(kāi)發(fā)之自定義UI組件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Android View教程之自定義驗(yàn)證碼輸入框效果

    Android View教程之自定義驗(yàn)證碼輸入框效果

    這篇文章主要給大家介紹了關(guān)于Android View教程之自定義驗(yàn)證碼輸入框效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-05-05

最新評(píng)論