前言(只適用于targetSdkVersion <= 27):
最近公司項目需要訪問剪切板內容蜈膨,但是由于Android系統(tǒng)權限收緊屿笼,對于剪切板的訪問也收到了影響,具體的約束如下翁巍。
除非您的應用程序是默認輸入法編輯器或當前具有焦點的應用程序驴一,否則您的應用程序無法訪問剪貼板數(shù)據(jù)。
影響針對 Android Q API 級別運行的應用的更改
對于我們的項目來說灶壶,如果我們要正確的獲取剪切板的內容肝断,則必須是在獲取焦點之后,于是進行了如下的嘗試驰凛。
V1.0
通過 DecorView.viewTreeObserver.addOnWindowFocusChangeListener(listener) 來監(jiān)聽窗口焦點的變化胸懈,在監(jiān)聽到獲取焦點后訪問剪切板。我們把監(jiān)聽放在窗口獲取焦點前恰响,這里我把它放在了Activity.onReume()方法里面
val decorView = activity.window.decorView
val listener = object : ViewTreeObserver.OnWindowFocusChangeListener {
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
// 做一些 獲取剪切板內容趣钱,移除監(jiān)聽的的操作(如果不在合適的時機移除,下次獲取焦點時會 有重復調用的問題)
}
}
}
decorView.viewTreeObserver.addOnWindowFocusChangeListener(listener)
OK 完成了第一版之后胚宦,在Activity里面使用并沒有問題首有,但是當在Fragment中使用就出現(xiàn)問題了。我們模仿Activity中的使用方法枢劝,在Fragment中的onResume() 方法中實現(xiàn)一樣的焦點監(jiān)聽(這里我們的Fragment是一定會觸發(fā)onResume())井联,但是發(fā)現(xiàn),首次打開Fragment沒有辦法監(jiān)聽到焦點變化您旁。至于原因也是非常的簡單:設置監(jiān)聽時烙常,窗口已經(jīng)獲取到了焦點,所以第一次是監(jiān)聽不到變化的被冒。
V1.1
針對上面的問題军掂,想出了一種解決思路:
如果,窗口已經(jīng)獲取了焦點昨悼,我直接訪問剪切板蝗锥。如果此時還沒有獲取焦點,我設置監(jiān)聽率触,當監(jiān)聽到獲取焦點后再獲取剪切板內容终议。思路看起來沒有問題,說干就干。
問題1:我要怎么判斷已經(jīng)獲取焦點
我們知道窗口焦點發(fā)生變化時穴张,會觸發(fā) onWindowFocusChanged()细燎,但是這個方法只在Activity 中有,F(xiàn)ragment中并沒有這個方法皂甘,看到這里有人就說了:“我們Activty 與 Fragment不是可以相互通信嗎玻驻?我們可以將狀態(tài)傳遞過去呀“。這樣是可以偿枕,但是無形之間增加了頁面間的耦合程度璧瞬,而且作為一個工具類來說,肯定希望對外界的依賴越少越好渐夸。如果不這樣做嗤锉,還有其他更好的方式嗎?我們來一起探索一下墓塌。
上邊說到窗口焦點發(fā)生變化時瘟忱,會觸發(fā) onWindowFocusChanged(),那這個調用這個方法的地方有什么標志位保留焦點的狀態(tài)呢苫幢?如果有访诱,我們是不是可以通過反射獲取呢?
通過斷點調試态坦,我們看下調用鏈:
通過調用鏈盐数,我們看到了有兩個方法。我們看下ViewRootImpl.handleWindowFocusChanged()方法
private void handleWindowFocusChanged() // 處理窗口焦點變化
{
final boolean hasWindowFocus;
synchronized (this) {
...
hasWindowFocus = mUpcomingWindowFocus;
...
}
.....
if (mView != null) {
.....
mView.dispatchWindowFocusChanged(hasWindowFocus);
mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
....
}
....
}
可以看到伞梯,方法內部會用兩個dispatchWindowFocusChanged() && dispatchWindowFocusChanged()對事件進行分發(fā)玫氢。這里的 hasWindowFocus 即 mUpcomingWindowFocus 就是當前窗口焦點的狀態(tài),這里的 mUpcomingWindowFocus是一個全局變量谜诫,如果可能拿到 mUpcomingWindowFocus 豈不是就可以判斷焦點了漾峡。接下來就用反射操作一下:
private fun isWindowFocused(activity: Activity): Boolean {
var hasFocus = false // 是否有焦點
try {
val entry = activity.window.decorView.rootView.parent // 這個就是ViewRootImpl
val clazz = activity.window.decorView.rootView.parent.javaClass
val field = clazz.getDeclaredField("mUpcomingWindowFocus")
if (field != null) {
field.isAccessible = true
hasFocus = field.getBoolean(entry) // 是否有焦點
}
} catch (e: Exception) {
}
return hasFocus
}
實驗了一下,沒有問題
現(xiàn)在可以改造下訪問剪切板方法了喻旷,完成生逸。如果有錯還望指出。
fun getContentFromClip(){
if(isWindowFocused(activity)){
// 直接訪問剪切板內容
....
return
}
val decorView = activity.window.decorView
val listener = object : ViewTreeObserver.OnWindowFocusChangeListener {
override fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
// 做一些 獲取剪切板內容且预,移除監(jiān)聽的的操作(如果不在合適的時機移除槽袄,下次獲取焦點時會 有重復調用的問題)
}
}
}
decorView.viewTreeObserver.addOnWindowFocusChangeListener(listener)
}