[筆記]Android部分技術(shù)點目錄(1)

目錄

FragmentPagerAdapter和FragmentStatePagerAdapter
IntentService
HandlerThread
View的層級、測量、和繪制
webView的安全漏洞、耗電問題和內(nèi)存泄露
Binder機制的內(nèi)核原理和通信機制
事件分發(fā)
進程優(yōu)先級
進程?;?br> ProGuard原理和功能
AsyncTask的用法和常見問題
Activity和Fragment的生命周期
BroadCastReceiver的基本原理
配置改變與Activity的生命周期
徹底退出App的設(shè)計原理
電視App的非系統(tǒng)App不能通過Service做Toast的問題

FragmentPagerAdapter和FragmentStatePagerAdapter

前者適合少量Fragment,它在destroyItem時,是FragmentTransaction.detach(),detach只會執(zhí)行onPause,onStop,onDestroyView,不會執(zhí)行onDestroy和onDetach,也就是仍保留Fragment實例;
后者適合大量Fragment,它在detroyItem時,是FragmentTransaction.remove(),會執(zhí)行onDetroy和onDetach,不再有Fragment實例。

IntentService

定義:
是一個繼承了Service的抽象類,繼承它之后,可以處理異步任務(wù),優(yōu)先級還比Service高
封裝了HandlerThread和Handler,有一個工作線程來處理耗時操作
任務(wù)完成后,IntentService會自動停止,不需要stopSelf(),相應(yīng)的也可以啟動多次(保持一個實例)
每一個耗時操作會在IntentService的onHandleIntent回調(diào)方法中執(zhí)行,每次只執(zhí)行一個線程,依次執(zhí)行(串行,HandlerThread的Looper的MessageQueue)。
實現(xiàn):
1.構(gòu)造方法,傳一個字符串,作為線程名稱
2.onHandleIntent(Intent intent),實現(xiàn)異步任務(wù)
源碼:
onCreate時,創(chuàng)建一個HandlerThread,創(chuàng)建一個ServiceHandler,讓這個ServiceHandler引用HandlerThread的Looper對象,這樣就可以處理異步任務(wù)。
IntentService啟動時會回調(diào)onStartCommand方法-->onStart方法-->用myserviceHandler發(fā)送一個message--> myserviceHandler接收這個message,在handleMessage時,就會調(diào)用onHandleIntent方法了,因為myserviceHandler引用了HandlerThread的Looper對象,所以它實際上就是在HandlerThread這個工作線程里調(diào)用了onHandleIntent方法。
handleMessage里,調(diào)用完onHandleIntent方法后,會調(diào)用stopSelf(msg.arg1),有了msg.arg1參數(shù),stopSelf就會在消息處理完后,停止Service

HandlerThread

就是一個Thread線程,內(nèi)部有一個Looper,可以在onLooperPrepared中,可以重寫一點初始化
Handler其實是跟著Looper走的,引用的Looper在哪個線程,Handler就在哪個線程,Handler通過ThreadLocal引用所在線程的Looper,Looper通過msg.target控制Handler運作的。

View的層級、測量、和繪制

  1. 層級
    Activity
    PhoneWindow(Window的唯一實現(xiàn)類)
    DectorView(是頁面的根,繼承自FramLayout(所以是ViewGroup),是PhoneWindow的內(nèi)部類,PhoneWindow把DectorView創(chuàng)建出來,然后用ActivityThread去啟動Activity,實現(xiàn)用WindowManager加載View,最終用一個ViewRootImpl去setView,實現(xiàn)加載)
    ViewGroup
    View/ViewGroup
    Activity通過PhoneWindow的setContentView方法來設(shè)置布局
    View的繪制其實分為三步:
    measure 測量
    layout 擺放
    draw 重繪(只繪制需要重繪的)
  2. 測量
    measure,是final的,調(diào)了onMeasure
    onMeasure,可重寫,調(diào)了setMeasuredDimensions
    setMeasuredDimensions,真正給MeasuredWidth和MeasuredHeight賦值
    因為測到父View時,可能不知道子View的大小,所以父View先測量子View,再測量自己。
    如果第一次測量子View的結(jié)果超過父控件,會再測一次,所以可能測量兩次。
    樹形遞歸,ViewGroup發(fā)起,遍歷每一個子控件,通過父控件的MeasureSpec和子控件的LayoutParams來測量。
  3. 擺放和繪制
    MeasureSpec,測量規(guī)格,是父控件傳遞給子控件的,是模式和大小的組合值,32位,前兩位代表模式(EXACTLY對子容器指定尺寸、AT_MOST對子容器指定最大尺寸、UPSPECIFIED對子容器未指定尺寸),后30位代表尺寸。
    父控件傳來的MeasureSpec和LayoutParams共同決定子控件的大小
    View的繪制是在Framwork層處理的
  4. draw的有兩個容易混淆的方法
    invalidate()方法,會draw,只繪制需要重繪的,如果沒有大小變化,不會layout(setVisibility/requestFocus/setSelection/setEnabled等方法會觸發(fā))
    requestLayout()方法,會measure和layout,但是不會draw

Android View的繪制流程
Android View源碼解讀:淺談DecorView與ViewRootImpl

webView相關(guān)

1.js安全漏洞,老版本的webView.addJavascriptInterface方法被濫用,導(dǎo)致js用發(fā)射機制,可以調(diào)用任何本地方法。
2.在布局文件中,銷毀webView時,要先從父控件中remove掉webView,然后用webView.onDestory
3.jsbridge,js和native橋
4.webviewClient.onPageFinished,判斷網(wǎng)頁是否被加載完成,但是跳轉(zhuǎn)也會有,推薦用webviewClient.onProgessChanged函數(shù)
5.線程耗電,可以放在一個單獨的進程里,直接用system.exit()來退出整個進程。
6.硬件加速,拖動更順滑,但是副作用,加載可能有白塊。
7.內(nèi)存泄露,webView關(guān)聯(lián)Activity,而webView自己有獨立的線程,原理等同內(nèi)部類導(dǎo)致的內(nèi)存泄露。解決方法一個是獨立進程,另一個是動態(tài)添加webView+弱引用。

Binder

一、內(nèi)核知識
1.進程隔離/虛擬地址空間
只可以訪問許可的資源。
2.系統(tǒng)調(diào)用
從用戶空間,訪問內(nèi)核層的程序。
3.binder驅(qū)動
運行在內(nèi)核空間,負責(zé)用戶進程間的交互
二、Binder通信機制
1.背景
.Linux有很多跨進程通信機制。
.Binder比socket更高效、協(xié)議本身更安全(對通信雙方做身份校驗)。
2.通訊模型
·binder驅(qū)動好比電話基站(client查到service后,通過binder驅(qū)動實現(xiàn)通信)
·ServiceManager好比通信錄(service要注冊進來)
3.如何跨進程
·service會把帶函數(shù)的object注冊到sm,client通過binder驅(qū)動找到sm中有這個函數(shù)
·內(nèi)核中的binder驅(qū)動,給client一個代理對象,代理對象的方法是空的,client調(diào)用代理對象的函數(shù),binder驅(qū)動再去找service來處理,binder驅(qū)動是個中轉(zhuǎn)站
·aidl中的transact()函數(shù),是個Native函數(shù),就是在這里調(diào)用了底層的binder驅(qū)動完成跨進程通信

事件分發(fā)

  1. dispatchTouchEvent
    傳到view就一定會調(diào)用,返回是否消費此事件,從上往下dispatchTouchEvent
    Activity的dispatchTouchEvent里,會調(diào)用onUserInteraction(),主要用于屏保
    返回true的條件是:listener不為空、且listener處理onTouch事件返回true、且控件的enabled狀態(tài)
    如果返回true,當(dāng)前事件交給onTouchEvent,后續(xù)事件會繼續(xù)分發(fā)過來;
    如果返回false,當(dāng)前事件回傳給上級onTouchEvent,后續(xù)事件會繼續(xù)分發(fā)過來
  2. onInterceptTouchEvent
    是否攔截事件,如果攔截了,就調(diào)用onTouchEvent;如果不攔截,就交給child做dispatchTouchEvent,并返回child是否消費事件。
    如果自己onTouchEvent,或者child返回消費,就返回事件已消費
    Activity沒有onInterceptTouchEvent()方法
    View沒有onInterceptTouchEvent()方法
    如果返回true,當(dāng)前事件交給onTouchEvent,后續(xù)事件不會繼續(xù)分發(fā)過來;
    如果返回false,傳遞給下一級dispatchTouchEvent,后續(xù)事件會繼續(xù)分發(fā)過來
    針對一串事件來說,只有前一個返回true,才會繼續(xù)收到后一個(如果返回false,后續(xù)都會被分給上層的onTouchEvent)
  3. onTouchEvent
    從下往上onTouchEvent
    dispatchTouchEvent中的判斷分支里,會執(zhí)行onTouchEvent
    如果返回true,當(dāng)前事件不再向下傳遞,后續(xù)也會繼續(xù)分發(fā)過來,會直接傳遞到view的onTouchEvent,不再經(jīng)過
    如果返回false,當(dāng)前事件交給上級onTouchEvent,后續(xù)不會繼續(xù)分發(fā)過來

ACTION_DOWN及其后續(xù)事件的攔截處理
1.如果最底層ViewC在onTouchEvent中處理了ACTION_DOWN的onTouchEvent,就會持續(xù)獲取后續(xù)的其他事件(如果不消費DOWN,其他事件也不會給它)
2.此時,后續(xù)的MOVE如果被上層ViewB截取,系統(tǒng)會做一個CANCEL事件傳給ViewC,這個MOVE不會給ViewB
3.再后續(xù)的MOVE會給ViewB的onTouchEvent(不會再給ViewB的onInterceptTouchEvent,因為在上一步中已經(jīng)返回過true,就不會再進來了)
4.如果最開始不消耗ACTION_DOWN事件(onTouchEvent()返回了false),那么同一事件序列中的其他事件都不會再交給它處理,并且事件將重新交由它的父元素去處理,及父元素的onTouchEvent()會被調(diào)用。
5.如果先消耗了ACTION_DOWN事件,但是后續(xù)不消耗ACTON_DOWN以外的其他事件,那么這個點擊事件會消失,此時父元素的onTouchEvent()并不會被調(diào)用,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件。最終這些消失的點擊事件會傳遞給Activity處理。

onTouch和onTouchEvent的區(qū)別
1.都在dispatchTouchEvent中調(diào)用
2.在dispatchTouchEvent源碼中,先執(zhí)行onTouch,如果onTouch返回false,才執(zhí)行onTouchEvent
3.因為listener執(zhí)行onTouch前有兩個&&的判斷,所以要看有l(wèi)istener,且控件是enable的,如果控件不是enable的,就只能通過onTouchEvent來處理了
反向回傳
如果最底層view不處理事件,會反向回傳,最終返還到activity中處理
Activity dispatchTouchEvent --> Activity onUserInteraction --> Viewgroup onInterceptTouchEvent--> View onTouchEvent --> Activity onTouchEvent(如果沒有處理,就返回到Activity處理)
Android事件分發(fā)機制“不詳解”
Android事件分發(fā)機制詳解:史上最全面、最易懂

進程優(yōu)先級

進程優(yōu)先級和GS相關(guān),優(yōu)先級越高,oom_adj值越低(系統(tǒng)進程為-16),AMS中的進程優(yōu)先級分為5等:
前臺 > 可見 > 服務(wù) > 后臺 > 空

  • 前臺進程 Activte process oom_adj為0
    包括正在runningactivity、與該activity綁定的service、正在onReceive的broadcastreceiver、正在onstart/onstop/ondestroy的service
    前臺響應(yīng)用戶事件的Activity以及與之綁定的Service
    startForeground的Service
    正在執(zhí)行onStart,onCreate,OnDestroy的Service
    正在執(zhí)行onReceive的BroadcastReceiver
  • 可見進程 Visible Process oom_adj為1
    包含onpause狀態(tài)的activity及其綁定的service
    也就是onPause但未onStop的Activity
    綁定到可見Activity的Service
  • 服務(wù)進程 Service process
    普通Service
  • 后臺/背景進程 Background process oom_adj為2
    不可見的activity,為onStop后的activity
    也就是onStop的Activity
  • 空進程 Empty process oom_adj為15
    不包含任何組件的進程
    空進程沒有活動狀態(tài),是為了緩存啟動數(shù)據(jù),方便下次啟動(例如,關(guān)掉重開后的瀏覽器網(wǎng)頁數(shù)據(jù)),能提高速度,并提供歷史數(shù)據(jù)
    Android平臺App進程優(yōu)先級

進程?;?/h1>
  1. 原因:業(yè)務(wù)需要,比如必須動態(tài)注冊的廣播 im通信 push 等
    后臺進程用lru 判斷回收哪個
    后臺進程和空進程在內(nèi)存充足時也可能回收
  2. 回收策略
    Low memory killer. 用Linux的 out of memory 改的,定期運行,用打分機制選擇進程回收,分高者回收
    通過OOM_ODJ判斷閥值,閥值是進程優(yōu)先級
  3. ?;罘桨?br> ·監(jiān)聽系統(tǒng)廣播 但受權(quán)限和攔截影響
    ·service啟動方式(START_STICKY) 但有兩種情況失效:連續(xù)三次機會5 10 20秒重試失?。恢鲃雨P(guān)閉時
    ·Native機制 Native不受AMS管理,自由度很高,在Native代碼中監(jiān)聽主進程,用定時輪詢或文件鎖來監(jiān)控(5.0以上會強殺Group中的所有進程)
    ·JobSchedular機制 定時API21以后
    需要寫一個類繼承JobService,在onstart里聲明創(chuàng)建JobScheduler對象,并用JobInfo建造者模式設(shè)置參數(shù)
public class MyJobService extends JobService {
    private JobScheduler mJobScheduler;
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(startId++,
                    new ComponentName(getPackageName(), JobHandlerService.class.getName()));
            builder.setPeriodic(5000); //每隔5秒運行一次
            builder.setRequiresCharging(true);
            builder.setPersisted(true);  //設(shè)置設(shè)備重啟后,是否重新執(zhí)行任務(wù)
            builder.setRequiresDeviceIdle(true);
        }
        return START_STICKY;
    }
...

·賬戶同步機制 跟隨賬戶同步機制來處理

通過提高進程優(yōu)先級保活

在遵循系統(tǒng)游戲規(guī)則的前提下,要實現(xiàn)進程?;?,就要提高進程優(yōu)先級。

  • oom_adj
    進程優(yōu)先級主要由系統(tǒng)維護的oom_adj的值判斷,值越低優(yōu)先級越高(系統(tǒng)進程為-16、前臺進程0),值越高優(yōu)先級越低,越容易回收(空進程15)。
    提升進程優(yōu)先級,就是壓低oom_adj的值。
  • 查看
    在adb中可以用命令查看進程優(yōu)先級,具體為:
    1.adb shell
    2.ps 所有進程,找到你的app進程名(默認包名)
    3.ps | grep 進程名
    4.cat /proc/pid/oom_adj (其中pid是上述grep得到的進程號)
  • 方法
    有多種方法可以提升進程優(yōu)先級:
    1.為application設(shè)置屬性:android:persistent=”true”
    缺點:僅限部分系統(tǒng)App,而且某些平臺可能導(dǎo)致無法訪問SD卡
    2.在onDestroy中重啟
    缺點:進程被殺時不會觸發(fā)onDestroy
    3.intent-filter中設(shè)置android:priority(
    錯誤:很多人說android:priority可以提升進程優(yōu)先級,這是錯誤的,怎么可能這么容易,根據(jù)官方文檔說法,It provides information about how able an activity is to respond to an intent that matches the filter, relative to other activities that could also respond to the intent.這是用來給組件排序的,在隱式調(diào)用Activity/Service時,如果有多個滿足條件的組件對象,就根據(jù)android:priority來判斷讓哪個組件優(yōu)先響應(yīng);在有序廣播中也是類似的機制。
    4.setForeground綁定到通知欄
    缺點:會顯示到通知欄中/在App圖標(biāo)上顯示數(shù)字,需要通過雙服務(wù)的寫法屏蔽顯示(微信的做法),即在Service中寫一個靜態(tài)內(nèi)部Service(在靜態(tài)內(nèi)部類在manifest中用$符注冊,如:<service android:name=".GrayService$GrayInnerService"/>),兩個服務(wù)都調(diào)用startForeground,并使用同一個notificationid,然后銷毀靜態(tài)內(nèi)部類,因為靜態(tài)內(nèi)部類被銷毀,所以通知欄會消失,這實際上是利用了一個系統(tǒng)bug。
    5.維持1像素的透明懸浮窗,保持為前臺進程,并在recentTask中不顯示該Activity。
    6.進程拆分,相互喚醒
    根據(jù)業(yè)務(wù)聚合,拆分為多進程,進程直接可以相互喚醒。
    另外,拆分進程有利于減低內(nèi)存銷毀(如拆分為低消耗網(wǎng)絡(luò)推送、后臺、中消耗主界面、重消耗webview/gallery等),比較耗內(nèi)存的進程可以主動回收進程,徹底釋放,網(wǎng)絡(luò)推送的進程可以維持低內(nèi)存消耗避免oom。
    7.進程回收時的數(shù)據(jù)保護
    如果進程最終被回收了,就需要保存回收前的數(shù)據(jù),可以在onDestroy和onTrimMemory等生命周期中回收數(shù)據(jù)。
    微信Android客戶端后臺保活經(jīng)驗分享
    Android 進程常駐(4)----native保活5.0以上方案推演過程以及代碼詳述
    KeepProcLive開源項目

5.0/6.0以后獲取當(dāng)前運行的進程

讓用戶授權(quán)USEAGE_STATE,或通過讀系統(tǒng)的proc文件找oom評分最低的App。

ProGuard原理和功能

ProGuard是一個開源項目,因為Java是跨平臺的解釋性語言,編譯成的字節(jié)碼里有很多源碼信息,這些無用且危險,proGuard可以處理掉這些信息。
ProGuard使用Entrypoint的概念,EntryPoint是不會被處理的類和方法,ProGuard會從EntryPoint開始(所以android.app.Activity等都被寫成keep),遞歸遍歷,看哪些類和類成員被使用了,未被使用的在壓縮階段丟棄。
Progard有4個方面的功能:

  1. 壓縮 去除無用類和字段
  2. 優(yōu)化 字節(jié)碼去除無用指令
    優(yōu)化的步驟中,那些非EntryPoint的類、方法都會被設(shè)置為private、static或final,不使用的參數(shù)會被移除???
  3. 混淆 字節(jié)碼改無意義名
  4. 預(yù)檢測 檢查處理后的代碼(Android不需要預(yù)檢測???)
    Android混淆打包那些事兒

AsyncTask

抽象類,需要自己擴展,本質(zhì)是一個封裝了一個靜態(tài)線程池和handler的異步框架,只能做短耗時操作。
3個參數(shù)
int 傳入的參數(shù)
int 反饋進度
String 返回結(jié)果
5個方法
onPreExecute 初始化 UI線程 比如顯示進度條
doInBackground 耗時操作,可以調(diào)用publishProgress更新進度, return返回結(jié)果
publishProgress更新進度
onProgressUpdate 更新進度
onPostExecute 完成操作
四個問題:
1.內(nèi)存泄露AsyncTask也是非靜態(tài)內(nèi)部類
2.生命周期長過Activity,有后臺任務(wù),需要顯式調(diào)用asyncTask.cancel()
3.結(jié)果可能丟失
4.并行和串行,1.6~2.3是并行,之前和之后是串行

Activity和Fragment生命周期

Activity生命周期
running paused stoped destroyed
oncreate onstart onresume onpause onstop ondestroy
onrestart
onstart和onresume的區(qū)別是,onstart只可見,不可操縱
Fragment
Fragment的完整生命周期
onAttach onCreate onCreateView onActivityCreated onStart onResume onPause onStop onDestoryView onDestroy onDetach
FragmentPagerAdapter和FragmentStatePagerAdapter的區(qū)別,就是前者在destroyItem時只是detach移除而不銷毀,后者會remove銷毀Fragment,所以后者更省內(nèi)存,適用于管理大量的Fragment

BroadCastReceiver基本原理

普通的是通過AMS作為通信樞紐,通過Binder跨進程通信機制,向AMS發(fā)起廣播,由AMS分發(fā)廣播
LocalBroadCastReceiver則使用主線程的Handler進行通信,所以效率更高,更安全。

配置改變與Activity生命周期

如果設(shè)置了強制橫屏,Activity會經(jīng)歷多次創(chuàng)建和銷毀,要避免的話,需要把這個事件抓到onConfigurationChanged里,自己處理。
首先,App需要權(quán)限,android.permission.CHANGE_CONFIGURATION,獲取系統(tǒng)設(shè)置權(quán)限,這是一個需要用戶確認才能獲取的權(quán)限
然后,要配置為攔截事件,android:configChanges="orientation|screenSize",官方文檔是明確要求加screenSize的,因為screenSize會要求用戶選擇不同的畫面和圖像。
有些寫法里有keyboard/keyboardHidden,那其實是鍵盤彈出,跟橫屏沒關(guān)系;
有些寫法里有l(wèi)ayoutDirection,那其實是配合語言變化的local事件(有些語言要求從右向左書寫);
其他的配置變更還有:
mcc/mnc:SIM卡變化
local:變更語言
fontScale:字體縮放
smallestScreenSize:換了物理屏幕,比如外接顯示器
uiMode:UI模式切換,比如夜間模式
keyboard:外接鍵盤
keyboardHidden:鍵盤變得可以/不可以訪問,比如滑出/滑入鍵盤
screenLayout:屏幕的布局變了,(懵逼,什么意思)
touchscreen:觸摸屏變了(基本不應(yīng)該發(fā)生)
navigation:導(dǎo)航類型變了,如增加了鍵盤后,可以用鍵盤控制導(dǎo)航方向(基本不發(fā)生)
最后,除非自己想做邏輯處理,否則Activity里可以不重寫onConfigurationChanged方法。
Android橫豎屏切換小結(jié)

徹底退出App的設(shè)計原理

以前遇到過一個Activity的onResume異常導(dǎo)致自動重啟時反復(fù)崩潰的問題:
ActivityA啟動ActivityB,ActivityB的onResume做一個崩潰異常,然后在Application崩潰時自動重啟,就會發(fā)現(xiàn),重啟后雖然應(yīng)該打開ActivityA,但是App還是會嘗試打開ActivityB,結(jié)果再次崩潰。

直接原因是App退出時,系統(tǒng)會檢查Activity棧是否為空,如果為空,會自動重啟,所以如果我們要強退App,需要自己維持一個Activity棧,先把所有的Activity全部退出,然后強退整個進程。
至于重建后,界面直接表示的是棧頂?shù)腁ctivityB不是MainActivity,是因為Application在重建時,會按照棧列表重建,而ActivityB是在Task棧頂?shù)?,這樣在Application重建后,ActivityB又會執(zhí)行resume,導(dǎo)致繼續(xù)崩潰。

但是這個現(xiàn)象令人迷惑,系統(tǒng)為什么這樣設(shè)計?

其實,這與Android系統(tǒng)的內(nèi)存管理和進程管理有關(guān)。
從系統(tǒng)功能上,Android系統(tǒng)為了跨App靈活跳轉(zhuǎn),不鼓勵開發(fā)者主動退出自己的進程。
這就必然帶來進程越開越多,內(nèi)存不夠用的情況。
Android因此建立了進程自動管理,視當(dāng)前內(nèi)存情況,自動去回收一些進程,按照進程優(yōu)先級和進程消耗的內(nèi)存大小,自動回收部分進程。
但這些進程是自動回收的,并非用戶操作,所以Android系統(tǒng)會在內(nèi)存充裕的情況下,再去嘗試重建被回收的進程(使用新進程ID)。
重建進程時,會根據(jù)Activity棧,判斷要重建哪些Activity。
為了在回收和重建過程中,保存和恢復(fù)Activity的數(shù)據(jù),提供了onSaveInstanceState和onRestoreInstanceState接口(這就是系統(tǒng)“未經(jīng)許可”銷毀你Activity的實際場景)。

電視App非系統(tǒng)App的Service不能Toast的問題

電視App中,跨App啟動Service沒有問題,但是如果要在Service中Toast,需要Service所在的App作為SystemApp。
這是因為Andriod系統(tǒng)會把通知作為一種權(quán)限進行管理,有的手機也會有不能Toast的問題,需要在設(shè)置->安全與隱私->通知中心里,開啟權(quán)限。
電視App遇到這個問題,可能是電視系統(tǒng)默認把非系統(tǒng)App的這個權(quán)限給關(guān)掉了,否則影響用戶體驗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容