類加載機制
如下圖所示,JVM類加載機制分為五個部分:加載,驗證,準備,解析,初始化,下面我們就分別來看一下這五個過程。
加載
加載是類加載過程中的一個階段,這個階段會在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的入口。注意這里不一定非得要從一個Class文件獲取,這里既可以從ZIP包中讀取(比如從jar包和war包中讀取),也可以在運行時計算生成(動態代理),也可以由其它文件生成(比如將JSP文件轉換成對應的Class類)。
驗證
這一階段的主要目的是為了確保Class文件的字節流中包含的信息是否符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。
準備
準備階段是正式為類變量分配內存并設置類變量的初始值階段,即在方法區中分配這些變量所使用的內存空間。注意這里所說的初始值概念,比如一個類變量定義為:
public static int v = 8080;
實際上變量v在準備階段過后的初始值為0而不是8080,將v賦值為8080的putstatic指令是程序被編譯后,存放于類構造器<client>方法之中,這里我們后面會解釋。 但是注意如果聲明為:
public static final int v = 8080;
在編譯階段會為v生成ConstantValue屬性,在準備階段虛擬機會根據ConstantValue屬性將v賦值為8080。
解析
解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程。符號引用就是class文件中的:
· CONSTANT_Class_info
· CONSTANT_Field_info
· CONSTANT_Method_info
等類型的常量。
下面我們解釋一下符號引用和直接引用的概念:
· 符號引用與虛擬機實現的布局無關,引用的目標并不一定要已經加載到內存中。各種虛擬機實現的內存布局可以各不相同,但是它們能接受的符號引用必須是一致的,因為符號引用的字面量形式明確定義在Java虛擬機規范的Class文件格式中。
· 直接引用可以是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經在內存中存在。
初始化
初始化階段是類加載最后一個階段,前面的類加載階段之后,除了在加載階段可以自定義類加載器以外,其它操作都由JVM主導。到了初始階段,才開始真正執行類中定義的Java程序代碼。
初始化階段是執行類構造器<client>方法的過程。<client>方法是由編譯器自動收集類中的類變量的賦值操作和靜態語句塊中的語句合并而成的。虛擬機會保證<client>方法執行之前,父類的<client>方法已經執行完畢。p.s: 如果一個類中沒有對靜態變量賦值也沒有靜態語句塊,那么編譯器可以不為這個類生成<client>()方法。
注意以下幾種情況不會執行類初始化:
· 通過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。
· 定義對象數組,不會觸發該類的初始化。
· 常量在編譯期間會存入調用類的常量池中,本質上并沒有直接引用定義常量的類,不會觸發定義常量所在的類。
· 通過類名獲取Class對象,不會觸發類的初始化。
· 通過Class.forName加載指定類時,如果指定參數initialize為false時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。
· 通過ClassLoader默認的loadClass方法,也不會觸發初始化動作。
類加載器
虛擬機設計團隊把加載動作放到JVM外部實現,以便讓應用程序決定如何獲取所需的類,JVM提供了3種類加載器:
· 啟動類加載器(Bootstrap ClassLoader):負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數指定路徑中的,且被虛擬機認可(按文件名識別,如rt.jar)的類。
· 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。
· 應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。
JVM通過雙親委派模型進行類的加載,當然我們也可以通過繼承java.lang.ClassLoader實現自定義的類加載器。
當一個類加載器收到類加載任務,會先交給其父類加載器去完成,因此最終加載任務都會傳遞到頂層的啟動類加載器,只有當父類加載器無法完成加載任務時,才會嘗試執行加載任務。
采用雙親委派的一個好處是比如加載位于rt.jar包中的類java.lang.Object,不管是哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個Object對象。
在有些情境中可能會出現要我們自己來實現一個類加載器的需求,由于這里涉及的內容比較廣泛,我想以后單獨寫一篇文章來講述,不過這里我們還是稍微來看一下。我們直接看一下jdk中的ClassLoader的源碼實現:
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
|
· 首先通過Class c = findLoadedClass(name);判斷一個類是否已經被加載過。
· 如果沒有被加載過執行if (c == null)中的程序,遵循雙親委派的模型,首先會通過遞歸從父加載器開始找,直到父類加載器是Bootstrap ClassLoader為止。
· 最后根據resolve的值,判斷這個class是否需要解析。
而上面的findClass()的實現如下,直接拋出一個異常,并且方法是protected,很明顯這是留給我們開發者自己去實現的,這里我們以后我們單獨寫一篇文章來講一下如何重寫findClass方法來實現我們自己的類加載器。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
|
Activity生命周期
一個Activity可以基本上存在三種狀態:
恢復
這項Activity是在屏幕前的,并有用戶取得其焦點。(此狀態,有時也簡稱為“運行”。)
暫停
另一個Activity在屏幕前,取得焦點,但原來的Activity仍然可見。也就是說,另一個Activity是這一個頂部可見,或者是部分透明的或不覆蓋整個屏幕。暫停的Activity完全是存在的( Activity 對象保留在內存中,它維護所有狀態和成員信息,并保持窗口管理器的聯系),但在極低的內存的情況下,可以被系統終止。
停止
Activity完全被另一個Activity遮住了(現在Activity是在“background”)。停止Activity也仍然存在( Activity 對象保留在內存中,它保持狀態和成員信息,但不和窗口管理器有關聯)。然而,它已不再是對用戶可見,其他地方需要內存時,它可以被系統終止。
如果一項Activity被暫停或停止,該系統可以從內存中刪除它,要求它結束(調用它的finish() 方法),或者干脆殺死它的進程。Activity再次打開時(在被結束或被殺死),它必須創造所有的一切。
實現生命周期回調函數
當Activity按照如上所述在不同狀態轉進,出的時,是通過各種回調方法進行通知的。所有的回調方法是鉤子,當的Activity狀態變化時您可以覆蓋他們來做適當的工作時。以下的Activity,包括基本生命周期的每一個方法
:
1.啟動onCreate()->onStart()->onResume()
2.按back鍵onPause()->onStop()->onDestroy()
再次啟動:onCreate()->onStart()->onResume()
3.按home鍵onPause()->onStop() 再次啟動時:onRestart()->onStart()->onResume()
4.切換到SecondActivity:FirstActivity.onPause()->FirstActivity.onStop()
再次啟動時: FirstActivity.onRestart()->FirstActivity.onStart()->FirstActivity.onResume()
5.切換SecondActivity (FirstActivity.finish()):
FirstActivity.onPause()->FirstActivity.onStop()->FirstActivity.onDestroy()
再次啟動時: FirstActivity.onCreate ()->FirstActivity.onStart()->FirstActivity.onResume()
6.切換到SecondActivity (SecondActivity主題為Dialog): FirstActivity.onPause()
再次啟動時: FirstActivity.onRestart()->FirstActivity.onStart()->FirstActivity.onResume()
7.配置改變Activity生命周期
某些設備配置在運行時可以改變(如屏幕方向,鍵盤的可用性,和語言)。當這種變化發生時,Android重新運行Activity(系統調用的 onDestroy() ,然后立即調用的 onCreate()) 。這種行為旨在幫助您用您所提供的(如不同的屏幕方向和大小不同的布局)的替代資源進行應用程序自動重載,以適應新的配置。
AndroidManifest.xml
<activity android:name=”…” android:configChanges="keyboardHidden|orientation|screenSize"/>
回調方法的作用,就是通知我們Activity生命周期的改變,然后我們可以處理這種改變,以便程序不會崩潰或者數據丟失等等,也就是擁有更好的用戶體檢,那么這么多回調方法里到底應該怎么做呢?
1、onCreate
最重要是在里面調用setContentView,還可以在里面初始化各控件、設置監聽、并初始化一些全局的變量。
因為在Activity的一次生命周期中,onCreate方法只會執行一次。在Paused和Stopped狀態下恢復或重啟的下,這些控件、監聽和全局變量也不會丟失。即便是內存不足,被回收了,再次Recreate的話,又是一次新的生命周期的開始,又會執行onCreate方法。
還可以在onCreate執行數據操作,比如從Cursor中檢索數據等等,但是如果你每次進入這個Activity都可能需要更新數據,那么最好放在onStart里面。(這個需要根據實際情況來確定)
2、onDestory
確定某些資源是否沒有被釋放,做一些最終的清理工作,比如在這個Activity的onCreate中開啟的某個線程,那么就要在onDestory中確定它是否結束了,如果沒有,就結束它。
3、onStart和onRestart、onStop
Activity進入到Stopped狀態之后,它極有可能被系統所回收,在某些極端情況下,系統可能是直接殺死應用程序的進程,而不是調用onDestory方法,所以我們需要在onStop方法中盡可能的釋放那些用戶暫時不需要使用的資源,防止內存泄露。
盡管onPause在onStop之前執行,但是onPause只適合做一些輕量級的操作,更多的耗時耗資源的操作還是要放在onStop里面,比如說對數據保存,需要用到的數據庫操作。
因為從Stopped狀態重啟之后, onStart和onRestart方法都會被執行,所以我們要判斷哪些操作分別要放在哪個方法里面 。因為可能在onStop方法里面釋放了一些資源,那么我們必須要重啟他們,這個時候這些重啟的操作放在onStart方法里面就比較好(因為onCreate之后也需要開啟這些資源)。那些因為Stopped之后引發的需要單獨操作的代碼,就可以放在onRestart里面。
4、onResume和onPause
onPause和onResume中做的操作,其實意義上和onStart和inStop差不多,只不過是要更輕量級的,因為onPause不能阻塞轉變到下一個Activity。
比如:停止動畫、取消broadcast receivers。當然相應的需要在onResume中重啟或初始化等等。
有時候也需要在onPause判斷用戶是調用finish結束這個Activity,還是暫時離開,以便區分處理。這時候可以調用isFinishing()方法來判斷。如果是用戶finish這個Activity,那么返回為true,如果只是暫時離開或者被系統回收的話,就返回false。
最后加上
Android****里面保存數據狀態有哪幾種方式?
onPause() 用來持久化數據狀態
onsavedInstanceState() 保存數據狀態
Handler機制
Message類
Message 實現了Parcelable 接口,也就是說實現了序列化,這就說明Message可以在不同進程之間傳遞。
包含一個名為target的Handler 對象
包含一個名為callback的Runnable 對象
使用obtain 方法可以從消息池中獲取Message的實例,也是推薦大家使用的方法,而不是直接調用構造方法。
非UI線程真的不能更新UI嗎?不一定,之所以子線程不能更新界面,是因為Android在線程的方法里面采用checkThread進行判斷是否是主線程,而這個方法是在ViewRootImpl中的,這個類是在onResume里面才生成的,因此,如果這個時候子線程在onCreate方法里面生成更新UI,而且沒有做阻塞,就是耗時多的操作,還是可以更新UI的。
MessageQueue的中文翻譯是消息隊列,顧名思義,它的內部存儲了一組消息,以隊列的形式對外提供插入和刪除的工作。雖然叫消息隊列,但是它的內部存儲結構并不是真正的隊列,而是采用單鏈表的數據結構來存儲消息列表。
handler的原理是什么?
答:1、handler封裝消息的發送(主要包括消息發送給誰)
2、Looper——消息封裝的載體。(1)內部包含一個MessageQueue,所有的Handler發送的消息都走向這個消息隊列;(2)Looper.Looper方法,就是一個死循環,不斷地從MessageQueue取消息,如果有消息就處理消息,沒有消息就阻塞。
3、MessageQueue,一個消息隊列,添加消息,處理消息
4、handler內部與Looper關聯,handler->Looper->MessageQueue
,handler發送消息就是向MessageQueue隊列發送消息。
總結:handler負責發送消息,Looper負責接收handler發送的消息,并把消息回傳給handler自己。
Android更新UI的方式?
答:1、runOnUIThread
2、handler post
3、handler sendMessage
4、view post
樂觀鎖與悲觀鎖
https://blog.csdn.net/truelove12358/article/details/54963791
樂觀鎖。更加寬松的加鎖機制,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
悲觀鎖。正如其名,具有強烈的獨占和排他特性。它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。但隨之而來的就是數據庫性能的大量開銷,特別是對長事務而言,這樣的開銷往往無法承受。
CAS操作是在樂觀鎖上面執行
JDK1.5中引入了底層的支持,在int、long和對象的引用等類型上都公開了CAS的操作,并且JVM把它們編譯為底層硬件提供的最有效的方法,在運行CAS的平臺上,運行時把它們編譯為相應的機器指令。在java.util.concurrent.atomic包下面的所有的原子變量類型中,比如AtomicInteger,都使用了這些底層的JVM支持為數字類型的引用類型提供一種高效的CAS操作。
在CAS操作中,會出現ABA問題。就是如果V的值先由A變成B,再由B變成A,那么仍然認為是發生了變化,并需要重新執行算法中的步驟。有簡單的解決方案:不是更新某個引用的值,而是更新兩個值,包括一個引用和一個版本號,即使這個值由A變為B,然后為變為A,版本號也是不同的。
Android 7.0 8.0 p 兼容性問題
Android 7.0
低電耗模式
當設備處于充電狀態且屏幕已關閉一定時間后,設備會進入低電耗模式并應用第一部分限制:關閉應用網絡訪問、推遲作業和同步。如果進入低電耗模式后設備處于靜止狀態達到一定時間,系統則會對 PowerManager.WakeLock、AlarmManager 鬧鈴、GPS 和 WLAN 掃描應用余下的低電耗模式限制。在此窗口期間,應用程序可以訪問網絡并執行任何被推遲的作業/同步。
后臺優化
移除了三項隱式廣播,以幫助優化內存使用和電量消耗
Android 框架提供多個解決方案來緩解對這些隱式廣播的需求。例如,JobSchedulerAPI 提供了一個穩健可靠的機制來安排滿足指定條件(例如連入無限流量網絡)時所執行的網絡操作。您甚至可以使用 JobScheduler 來適應內容提供程序變化。
權限更改
傳遞軟件包網域外的 file:// URI 可能給接收器留下無法訪問的路徑,分享私有文件內容的推薦方法是使用 FileProvider。
在應用間共享文件,同上
Android 8.0
針對所有 API 級別的應用
為提高設備性能,系統會限制未在前臺運行的應用的某些行為。具體而言:
現在,在后臺運行的應用對后臺服務的訪問受到限制。
應用無法使用其清單注冊大部分隱式廣播(即,并非專門針對此應用的廣播)。
后臺位置限制
為節約電池電量、保持良好的用戶體驗和確保系統健康運行,在運行 Android 8.0 的設備上使用后臺應用時,降低了后臺應用接收位置更新的頻率。此行為變更會影響包括 Google Play 服務在內的所有接收位置更新的應用。
此類變更會影響以下 API:
Fused Location Provider (FLP)
Geofencing
GNSS Measurements
Location Manager
Wi-Fi Manager
應用快捷鍵
com.android.launcher.action.INSTALL_SHORTCUT 廣播不再會對您的應用有任何影響,因為它現在是私有的隱式廣播。相反,您應使用 ShortcutManager 類中的 requestPinShortcut() 函數創建應用快捷方式。
現在,ACTION_CREATE_SHORTCUT Intent 可以創建可使用 ShortcutManager 類進行管理的應用快捷方式。此 Intent 還可以創建不與 ShortcutManager 交互的舊版啟動器快捷方式。在以前,此 Intent 只能創建舊版啟動器快捷方式。
現在,使用 requestPinShortcut() 創建的快捷方式和在處理 ACTION_CREATE_SHORTCUT Intent 的操作組件中創建的快捷方式均已轉換為功能齊全的應用快捷方式。因此,應用現在可以使用 ShortcutManager 中的函數來更新這些快捷方式。
舊版快捷方式仍然保留了它們在舊版 Android 中的功能,但您必須在應用中手動將它們轉換成應用快捷方式。
語言區域和國際化
Android 7.0(API 級別 24)引入能指定默認類別語言區域的概念,但是某些 API 在本應使用默認 DISPLAY 類別語言區域時,仍然使用不帶參數的通用 Locale.getDefault() 函數。現在,在 Android 8.0 中,以下函數使用 Locale.getDefault(Category.DISPLAY) 來代替 Locale.getDefault():
提醒窗口
輸入和導航
網頁表單自動填充 Android 自動填充框架提供對自動填充功能的內置支持
針對 Android 8.0 的應用
提醒窗口
使用 SYSTEM_ALERT_WINDOW 權限的應用無法再使用以下窗口類型來在其他應用和系統窗口上方顯示提醒窗口:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_SYSTEM_ERROR
相反,應用必須使用名為 TYPE_APPLICATION_OVERLAY 的新窗口類型。
使用 TYPE_APPLICATION_OVERLAY 窗口類型顯示應用的提醒窗口時,請記住新窗口類型的以下特性:
應用的提醒窗口始終顯示在狀態欄和輸入法等關鍵系統窗口的下面。
系統可以移動使用 TYPE_APPLICATION_OVERLAY 窗口類型的窗口或調整其大小,以改善屏幕顯示效果。
通過打開通知欄,用戶可以訪問設置來阻止應用顯示使用 TYPE_APPLICATION_OVERLAY 窗口類型顯示的提醒窗口。
媒體
框架會執行音頻閃避。進行 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 時,應用不會失去焦點。新的 API 適用于需要暫停而不是閃避的應用。請注意,此行為無法在 Android 8.0 1 版本中實現。
當用戶打電話時,活動的媒體流將在通話期間靜音。
集合的處理
在 Android 8.0 中,Collections.sort() 是在 List.sort() 的基礎上實現的。在 Android 7.x(API 級別 24 和 25)中,則恰恰相反。在過去,List.sort() 的默認實現會調用 Collections.sort()。
類加載行為
平臺將檢查類加載器返回的類描述符是否與預期的描述符一致。如果返回的描述符與預期不符,平臺會引發 NoClassDefFoundError 錯誤,并在異常日志中存儲一條注明不一致之處的詳細錯誤消息。
平臺還檢查請求的類描述符是否有效。此檢查捕獲間接加載諸如 GetFieldID() 等類的 JNI 調用,向這些類傳遞無效的描述符。例如,找不到包含 java/lang/String 簽名的字段,是因為此簽名無效;它應為 Ljava/lang/String;。
這與 JNI 對 FindClass() 的調用不同,其中 java/lang/String 是一個有效的完全限定名稱。
Android 8.0 不支持多個類加載器同時嘗試使用相同的 DexFile 對象來定義類。嘗試進行此操作,會導致 Android 運行時引發 InternalError 錯誤,同時顯示消息“Attempt to register dex file <filename> with multiple class loaders”。
Android P
屏幕缺口支持
您可以按如下方法在任何運行 Android P 的設備或模擬器上模擬屏幕缺口:
啟用開發者選項。
在 Developer options 屏幕中,向下滾動至 Drawing 部分并選擇 Simulate a display with a cutout。
選擇凹口屏幕的大小。
通知
短信 Android P 可在手機的“短信通知”中顯示圖像。SmartReply:Android P 支持您的短信應用中提供的建議回復。
多攝像頭支持和攝像頭更新
攝像頭方面的其他改進還包括新的會話參數和 Surface 共享,前者有助于降低首次拍照期間的延遲,而后者則讓攝像頭客戶端能夠處理各種用例,而無需停止并啟動攝像頭視頻流。 我們還針對基于顯示屏的 flash 支持和 OIS 時間戳訪問新增了一些 API,用以實現應用級的圖像穩定化和特效。
適用于位圖和可繪制對象的 ImageDecoder
您應使用 ImageDecoder 來解碼圖像,而不是使用 BitmapFactory 和 BitmapFactory.Options API。
ImageDecoder 讓您可以從字節緩沖區、文件或 URI 來創建 Drawable 或 Bitmap。
內存泄漏
1、單例造成的內存泄漏
由于單例的靜態特性使得其生命周期和應用的生命周期一樣長
2、非靜態內部類創建靜態實例造成的內存泄漏
因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,解決方法:將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,就使用Application的Context。
3、Handler造成的內存泄漏
1、從Android的角度
當Android應用程序啟動時,該應用程序的主線程會自動創建一個Looper對象和與之關聯的MessageQueue。當主線程中實例化一個Handler對象后,它就會自動與主線程Looper的MessageQueue關聯起來。所有發送到MessageQueue的Messag都會持有Handler的引用,所以Looper會據此回調Handle的handleMessage()方法來處理消息。只要MessageQueue中有未處理的Message,Looper就會不斷的從中取出并交給Handler處理。另外,主線程的Looper對象會伴隨該應用程序的整個生命周期。
2、 Java角度
在Java中,非靜態內部類和匿名類內部類都會潛在持有它們所屬的外部類的引用,但是靜態內部類卻不會。
4、線程造成的內存泄漏
示例:AsyncTask和Runnable
AsyncTask和Runnable都使用了匿名內部類,那么它們將持有其所在Activity的隱式引用。如果任務在Activity銷毀之前還未完成,那么將導致Activity的內存資源無法被回收,從而造成內存泄漏。
解決方法:將AsyncTask和Runnable類獨立出來或者使用靜態內部類,這樣便可以避免內存泄漏。
5、資源未關閉造成的內存泄漏
1)比如在Activity中register了一個BraodcastReceiver,但在Activity結束后沒有unregister該BraodcastReceiver。
2)資源性對象比如Cursor,Stream、File文件等往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。它們的緩沖不僅存在于 java虛擬機內,還存在于java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄漏。
3)對于資源性對象在不使用的時候,應該調用它的close()函數將其關閉掉,然后再設置為null。在我們的程序退出時一定要確保我們的資源性對象已經關閉。
4)Bitmap對象不在使用時調用recycle()釋放內存。2.3以后的bitmap應該是不需要手動recycle了,內存已經在java層了。
6、使用ListView時造成的內存泄漏
構造Adapter時,沒有使用緩存的convertView。
解決方法:在構造Adapter時,使用緩存的convertView。
7、集合容器中的內存泄露
如果這個集合是static的話,那情況就更嚴重了。
解決方法:在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
8、WebView造成的泄露
當我們不要使用WebView對象時,應該調用它的destory()函數來銷毀它,并釋放其占用的內存,否則其長期占用的內存也不能被回收,從而造成內存泄露。
解決方法:為WebView另外開啟一個進程,通過AIDL與主線程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷毀,從而達到內存的完整釋放。
大圖加載的緩存
按比例壓縮到跟控件大小,計算壓縮值,壓縮后,可以根據圖片的色彩存儲模式選擇顯示,減輕內存負擔。
Bitmap優化
LruCache 和diskLruCache 優化緩存。 當向 ImageView 中加載一張圖片時,首先會在 LruCache 的緩存中進行檢查。如果找到了相應的鍵值,則會立刻更新ImageView ,否則開啟一個后臺線程來加載這張圖片。
子線程能不能創建Handler
創建主線程的Looper、主線程的消息隊列...就連我們使用的handler也是主線程的。
不一定,之所以子線程不能更新界面,是因為Android在線程的方法里面采用checkThread進行判斷是否是主線程,而這個方法是在ViewRootImpl中的,這個類是在onResume里面才生成的,因此,如果這個時候子線程在onCreate方法里面生成更新UI,而且沒有做阻塞,就是耗時多的操作,還是可以更新UI的。
所以以后要想創建非主線程的Handler時,我們用HandlerThread類提供的Looper對象即可。創建HandlerThread對象的時候,有個參數,是指定線程名字的。
線程間通信其他方式
一:Handler實現線程間的通信,消息是通過綁定在主線程的Handler來傳遞的。
二、解決線程間通信的問題:使用AsyncTask:
三、runOnUiThread(),子線程中持有當前Activity引用(假如為Activity mActivity;),即可以調用mActivity的runOnUiThread(Runnable r)方法。
四、post()和postDelay()
子線程如果持有某個View的引用,要對該View進行更新,則可調用該View對象的post(Runnable r)或postDelay(Runnable r)方法
volatile原理
通俗點講就是說一個變量如果用volatile修飾了,則Java可以確保所有線程看到這個變量的值是一致的,如果某個線程對volatile修飾的共享變量進行更新,那么其他線程可以立馬看到這個更新,這就是所謂的線程可見性。
volatile的使用
防重排序
并發環境下的單例實現方式,我們通常可以采用雙重檢查加鎖(DCL)的方式來實現
實現可見性
可見性問題主要指一個線程修改了共享變量值,而另一個線程卻看不到。引起可見性問題的主要原因是每個線程擁有自己的一個高速緩存區——線程工作內存。volatile關鍵字能有效的解決這個問題
保證原子性
volatile只能保證對單次讀/寫的原子性
因為long和double兩種數據類型的操作可分為高32位和低32位兩部分,因此普通的long或double類型讀/寫可能不是原子的。因此,鼓勵大家將共享的long和double變量設置為volatile類型,這樣能保證任何情況下對long和double的單次讀/寫操作都具有原子性。
volatile的原理
可見性實現:(1)修改volatile變量時會強制將修改后的值刷新的主內存中。
(2)修改volatile變量后會導致其他線程工作內存中對應的變量值失效。因此,再讀取該變量值的時候就需要重新從讀取主內存中的值。
有序性實現:通俗一點說就是如果a happen-before b,則a所做的任何操作對b是可見的。
同一個線程中的,前面的操作 happen-before 后續的操作。(即單線程內按代碼順序執行。但是,在不影響在單線程環境執行結果的前提下,編譯器和處理器可以進行重排序,這是合法的。換句話說,這一是規則無法保證編譯重排和指令重排)。
volatile的使用優化 用一種追加字節的方式來優化隊列出隊和入隊的性能
那么是不是在使用volatile變量時都應該追加到64字節呢?不是的。在兩種場景下不應該使用這種方式。
緩存行非64字節寬的處理器。如P6系列和奔騰處理器,它們的L1和L2高速緩存行是32個字節寬。
共享變量不會被頻繁地寫。因為使用追加字節的方式需要處理器讀取更多的字節到高速緩沖區,這本身就會帶來一定的性能消耗,如果共享變量不被頻繁寫的話,鎖的幾率也非常小,就沒必要通過追加字節的方式來避免相互鎖定。
讀寫鎖的應用
比如說有兩個線程,他們之間共享一塊數據,在一個線程寫數據和一個線程讀數據的時候,怎么樣保證數據和邏輯的正確性。是否需要同步和加鎖呢?還是完全沒有必要?實際上,在前面關于volatile的介紹里已經基本上解決了。對于有多個讀取數據的線程和單個寫數據線程的場景,需要讀寫鎖。
RecyclerView與ListView的區別
RecyclerView,它主要的特點就是復用。我們知道,Listview中的Adapter中可以實現ViewHolder的復用。RecyclerView提供了一個耦合度更低的方式來復用ViewHolder,并且可以輕松的實現ListView、GridView以及瀑布流的效果。
LayoutManager
這個LayoutManager類決定視圖被放在畫面中哪個位置,但這只是它的眾多職責之一。它可以管理滾動和循環利用。LayoutManager只有一個叫做LinearLayoutManager的實現類,我們可以設置它的橫向和縱向。
ItemAnimator
ItemAnimator簡單來說是會根據適配器上收到的相關通知去動畫的顯示組件的修改,添加和刪除等。它會自動添加和移除item的動畫。自帶的默認效果也不錯,已經非常好了。
優點:
RecyclerView本身它是不關心視圖相關的問題的,由于ListView的緊耦合的問題,google的改進就是RecyclerView本身不參與任何視圖相關的問題。它不關心如何將子View放在合適的位置,也不關心如何分割這些子View,更不關心每個子View各自的外觀。更進一步來說就是RecyclerView它只負責回收和重用的工作,這也是它名字的由來。
所有關于布局、繪制和其他相關的問題,也就是跟數據展示相關的所有問題,都被委派給了一些”插件化”的類來處理。這使得RecyclerView的API變得非常靈活。你需要一個新的布局么?接入另一個LayoutManager就可以了!你想要不同的動畫么?接入一個新的ItemAnimator就可以了,諸如此類等等。還能局部刷新。
缺點:
在RecyclerView中,沒有一個onItemClickListener方法。所以目前在適配器中處理這樣的事件比較好。如果想要從適配器上添加或移除條目,需要明確通知適配器。這與先前的notifyDataSetChanged()方法稍微有些不同。具體操作在適配器代碼中就可以體現。
事件分發機制(看書)
動畫(看書)
okhttp****支不支持優先級
本篇文章的網絡層是OKHttp,既然選擇了OkHttp,如果要在onDestroy中取消未開始執行以及已經開始執行的網絡請求,就必須給每一個請求設置一個tag,然后通過該tag來需要網絡請求。比較明智的做法是以該Activity的上下文的hash值作為tag。取消請求時將hash值傳入,則該界面所有的請求都可以取消。
底層Request暴露優先級之后我們需要實現一個比較器,根據優先級由大到小進行排序。
ssl****握手誰實現的
首先tcp三次握手:Platform.get().connectSocket
其次獲得I/O流:source = Okio.buffer(Okio.source(rawSocket));sink = Okio.buffer(Okio.sink(rawSocket));
然后判斷是否需要ssl,如果需要則進行ssl:connectTls(readTimeout, writeTimeout, connectionSpecSelector);
在connectTls中,忽略其他的,ssl握手發生在
sslSocket.startHandshake();
底層實現握手。
okhttp websocket****應用
https://blog.csdn.net/tq08g2z/article/details/77311767
什么情況會產生ANR
在Android上,如果你的應用程序有一段時間響應不夠靈敏,系統會向用戶顯示一個對話框,這個對話框稱作應用程序無響應(ANR:Application Not Responding)對話框。默認情況下,在android中Activity的最長執行時間是5秒,BroadcastReceiver的最長執行時間則是10秒。
產生原因:由于主線程有很多重要事情要做,比如響應點擊事件等。如果在主線程里做了太多耗時的操作,則有可能引發ANR。盡量將耗時的操作放在子線程里。
由于主線程導致的情況:
1.耗時網絡訪問
2.當有大量數據讀寫操作時再請求數據讀寫
3.數據庫操作(比如其他大數據量應用訪問數據庫導致數據庫負載過重時)
4.硬件操作(比如Camera)
非主線程導致的情況:
1.非主線程持有lock,導致主線程等待lock超時
2.非主線程終止或者崩潰導致主線程一直等待
廣播****onReceive****方法調用線程
不建議開線程,建議開service,onreceive一下子就失活了
靜態廣播接收流程
廣播的發送是通過sendBroadcast 執行的,
總結:
廣播的注冊過程 :最終在ActivityManagerService中將遠程的InnerInnerReceiver以及Intent-filter對象存儲起來。
廣播的發送以及接受:內部會首先根據傳入的Intent-filter 查找出匹配的廣播接受者,并將改接受者放到BroadcastQueue中,緊接著系統會遍歷ArrayList中的廣播,并將其發送給它們對應的廣播接受者,最后調用到廣播接受者的onReceiver方法。
動態廣播能不能重復注冊
第三次被踢就會出現三次…..我在程序中使用的是動態注冊,結果我換成靜態注冊就沒問題了。想了想貌似問題就在這里,應該是重復注冊了廣播接收。找到問題以后將廣播接收對象定義為靜態對象,只初始化一次,問題迎刃而解。也就是說,使用動態注冊,每注冊一次就會生成一個廣播接收的對象。
Butterknife****工作原理
ButterKnife是通過使用注解方式來自動生成模板代碼,從而來將Activity中的字段和方法與View綁定在一起。
butterknife的原理主要分為三個部分來介紹,主要為:注解生成模板代碼分析、butterknife.bind()方法分析、生成的模板類代碼分析。
butterknife注冊的注解器為ButterKnifeProcessor
http://www.lxweimin.com/p/5cba8a5514cb
Android****仿微信朋友圈圖片展示效果****,
1.透明Activity
2.計算gridView下imageView Item所在位置
3.一張圖大小
4.圖片展示動畫
LeakCanary****原理
[圖片上傳失敗...(image-36b1d8-1533601512661)]
Activity****啟動模式及幾個模式的應用場景
[圖片上傳失敗...(image-ff924c-1533601512660)]
1. SingleTask模式的運用場景
最常見的應用場景就是保持我們應用開啟后僅僅有一個Activity的實例。最典型的樣例就是應用中展示的主頁(Home頁)。假設用戶在主頁跳轉到其他頁面,運行多次操作后想返回到主頁,假設不使用SingleTask模式,在點擊返回的過程中會多次看到主頁,這明顯就是設計不合理了。
還有:
|
LauchMode
|
Instance
|
| --- | --- |
|
standard
|
mainfest中沒有配置就默認標準模式
|
|
singleTop
|
登錄頁面、WXPayEntryActivity、WXEntryActivity 、推送通知欄
|
|
singleTask
|
程序模塊邏輯入口:主頁面(Fragment的containerActivity)、WebView頁面、掃一掃頁面
|
|
singleInstance
|
系統Launcher、鎖屏鍵、來電顯示等系統應用
|
滅屏會不會觸發onSavedInstance
會的。
調用時機 : Activity 容易被銷毀的時候調用, 注意是容易被銷毀, 也可能沒有銷毀就調用了;
按下Home鍵 : Activity 進入了后臺, 此時會調用該方法;
按下電源鍵 : 屏幕關閉, Activity 進入后臺;
啟動其它 Activity : Activity 被壓入了任務棧的棧底;
橫豎屏切換 : 會銷毀當前 Activity 并重新創建;
onRestoreInstanceState和onSavedInstanceState是否成對出現
onSavedInstanceState()和onRestoreInstanceState()并不是activity生命周期的方法。
onSaveInstanceState()會在onPause()或onStop()之前執行,onRestoreInstanceState()會在onStart()和onResume()之間執行。
當應用遇到意外情況(內存不足,用戶直接按home鍵)由系統直接銷毀一個Activity時,onSaveInstanceState()就會調用,但是當用戶主動銷毀activity,如按back鍵,或直接執行finish(),這種情況下onSaveInstanceState()就不會執行,因為這種情況下,用戶的行為決定了不需要保存Activity的狀態。
那么onRestoreInstanceState()會跟onSaveInstanceState()成對出現嗎? 答案是不會成對出現,onSaveInstanceState()需要調用的時,activity可能銷毀,也可能沒有銷毀,只有在activity銷毀重建的時候onRestoreInstanceState()才會調用。
在onSaveInstanceState()中默認情況下具體干些什么?
默認情況下默認會自動保存Activity中的某些狀態,比如activity中各種UI的狀態,因此在activity被“系統”銷毀和重建的時候,這些Ui的狀態會默認保存,但是前提條件是Ui控件必須制定id,如果沒有指定id的話,UI的狀態是無法保存 的。
總結下Activity數據的保存和恢復:
activity中保存數據有兩種方式onPause(),onSaveInstance(bundle), 恢復數據也有兩種途徑onCreate(Bundle), onRestoreInstanceState(budle),默認情況下onSaveInstanceSate()和onRestoreInstanceState()會對UI狀態進行保存和恢復,如果需要保存其他數據可以在onSaveInstanceState(),onPause()保存,但是如果是持久化的數據得通過onPause()保存(google推薦)。
Service生命周期的理解
bindService整個代碼怎么寫
這個過程比較復雜,但總體來說,思路還是比較清晰的,整個調用過程為MainActivity.bindService->CounterService.onCreate->CounterService.onBind->MainActivity.ServiceConnection.onServiceConnection->CounterService.CounterBinder.getService。下面,我們就先用一個序列圖來總體描述這個服務綁定的過程,然后就具體分析每一個步驟。
與service通信是否會阻塞當前線程
會的,因為service本來就是在主線程,為了避免阻塞,所以我們往往在service創建子線程,或者是啟用intentservice,比較常見的Service傳遞數據到Activity有三種方式, 通過Binder, 通過Broadcast廣播, 自定義接口回調, 都是借助于IBinder暴露Service中的相應操作。網上還有些文章中提到用觀察者模式,觀察者模式也是接口回調的一種方式,
如果是耗時方法,為什么會阻塞
是因為在主線程,ANR
如果不是耗時方法,為什么不會阻塞
如果遠端是耗時操作,怎么不等待結果讓主線程先運行
ANR
startService和bindSerivce對service生命周期的影響
一、正常情況(應該大家都很熟了,簡單介紹):
(1)單獨使用startService():
onCreate()->onStartCommand()->Service running->onDestroy()->Service shut down
(2)單獨使用bindService():
onCreate()->onBind()->Clients are bound to service->onUnbind()->onDestroy()->Service shut down
例子一:按順序1,2,3,4執行
(1)startServic:調用onCreate()->onStartCommand()
(2)bindService:調用onBind()
(3)stopService:沒有調用onDestory() Service仍然在運行!
(4)unbindService:調用onUnbind()->onDestory() 此時Service關閉!
例子二:將例子一3,4調換
(1)startServic:調用onCreate()->onStartCommand()
(2)bindService:調用onBind()
(3)unbindService:調用onUnbind() Service仍然在運行!
(4)stopService:調用onDestory() 此時Service才關閉!
aidl傳遞Bitmap需要注意的事項
???線程問題?注意實體類實現Parcelable借口。
EventBus原理
在3.0的版本中,使用的注解類型為Runtime
三要素:
A,Event:事件,
B,Publisher:發布者,可以在任意線程發布事件
C,Subscrible:訂閱者,
1,采用單利雙重鎖模式創建對象
2,構造方法
2.1,粘性事件,保存到ConCurrenHashMap集合,(在構造方法中實現),
HashMap效率高,但線程不安全,在多線程的情況下,盡量用ConcurrentHashMap,避免多線程并發異常
3,注冊register()方法主要做了2件事:
3.1,找到訂閱者的方法.找出傳進來的訂閱者的所有訂閱方法,然后遍歷訂閱者的方法.
A,通過反射來獲取訂閱者中所有的方法,并根據方法的類型,參數和注解找到訂閱方法.
3.2,訂閱者的注冊
注解處理器怎么工作,注解處理器有哪些API
注解處理器是(Annotation Processor)是javac的一個工具,用來在編譯時掃描和編譯和處理注解,就是把標記了注解的類,變量等作為輸入內容,經過注解處理器處理,生成想要生成的java代碼。
處理器的寫法有固定的套路,繼承AbstractProcessor。
init(ProcessingEnvironment processingEnv) 被注解處理工具調用,參數ProcessingEnvironment 提供了Element,Filer,Messager等工具
getSupportedAnnotationTypes() 指定注解處理器是注冊給那一個注解的,它是一個字符串的集合,意味著可以支持多個類型的注解,并且字符串是合法全名。
getSupportedSourceVersion 指定Java版本
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) 這個也是最主要的,在這里掃描和處理你的注解并生成Java代碼,信息都在參數RoundEnvironment 里了,后面會介紹。
Glide原理
郭霖大佬寫 https://blog.csdn.net/guolin_blog/article/details/53759439
Lrucache原理
LinkedHashMap原理
HashMap原理
o 解決Hash沖突的方法
o equals和hashcode作用
o hashcode如何實現
自己為知筆記
Object類下有什么方法
registerNatives() //私有方法
getClass() //返回此 Object 的運行類。
hashCode() //用于獲取對象的哈希值。
equals(Object obj) //用于確認兩個對象是否“相同”。
clone() //創建并返回此對象的一個副本。
toString() //返回該對象的字符串表示。
notify() //喚醒在此對象監視器上等待的單個線程。
notifyAll() //喚醒在此對象監視器上等待的所有線程。
wait(long timeout) //在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或 者超過指定的時間量前,導致當前線程等待。
wait(long timeout, int nanos) //在其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量前,導致當前線程等待。
wait() //用于讓當前線程失去操作權限,當前線程進入等待序列
finalize() //當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法。
Android中的類加載器
類加載器之間的區別
Android中的ClassLoader類型也可分為系統ClassLoader和自定義ClassLoader。其中系統ClassLoader包括3種分別是:
BootClassLoader,Android系統啟動時會使用BootClassLoader來預加載常用類,與Java中的Bootstrap ClassLoader不同的是,它并不是由C/C++代碼實現,而是由Java實現的。BootClassLoader是ClassLoader的一個內部類。
PathClassLoader,全名是dalvik/system.PathClassLoader,可以加載已經安裝的Apk,也就是/data/app/package 下的apk文件,也可以加載/vendor/lib, /system/lib下的nativeLibrary。
DexClassLoader,全名是dalvik/system.DexClassLoader,可以加載一個未安裝的apk文件。
從打印的結果也可以證實:App系統類加載器是PathClassLoader,而BootClassLoader是其parent類加載器。
Dex融合用的哪種類加載器
DexClassLoader是一個可以從包含classes.dex實體的.jar或.apk文件中加載classes的類加載器。可以用于實現dex的動態加載、代碼熱更新等等。這個類加載器必須要一個app的私有、可寫目錄來緩存經過優化的classes(odex文件),使用Context.getDir(String, int)方法可以創建一個這樣的目錄。
父類是什么及三者之間的關系
是ClassLoader,也不同于Java的ClassLoader。
https://blog.csdn.net/mynameishuangshuai/article/details/52737581
雙親委派模型
如果使用委托機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發現已經加載過了System那么直接返回內存中的System即可而不需要重新加載,這樣內存中就只有一份System的字節碼了
Android中的動畫及區別(看書,或者其他
Android中序列化方式
兩者區別
永久的保存對象數據
通過序列化操作將對象數據在網絡上進行傳輸
將對象數據在進程之間進行傳遞
Serializable 直接implement
Parcelable 不僅要implement 還要還需要實現內部的相應方法
為什么Parcelable性能更好
Parcelable與Serializable的性能比較
首先Parcelable的性能要強于Serializable的原因我需要簡單的闡述一下
1). 在內存的使用中,前者在性能方面要強于后者
2). 后者在序列化操作的時候會產生大量的臨時變量,(原因是使用了反射機制)從而導致GC的頻繁調用,因此在性能上會稍微遜色
3). Parcelable是以Ibinder作為信息載體的.在內存上的開銷比較小,因此在內存之間進行數據傳遞的時候,Android推薦使用Parcelable,既然是內存方面比價有優勢,那么自然就要優先選擇.
4). 在讀寫數據的時候,Parcelable是在內存中直接進行讀寫,而Serializable是通過使用IO流的形式將數據讀寫入在硬盤上.
但是:雖然Parcelable的性能要強于Serializable,但是仍然有特殊的情況需要使用Serializable,而不去使用Parcelable,因為****Parcelable****無法將數據進行持久化,因此在將數據保存在磁盤的時候,仍然需要使用后者,因為前者無法很好的將數據進行持久化.(原因是在不同的Android版本當中,Parcelable可能會不同,因此數據的持久化方面仍然是使用Serializable)
序列化UID作用
輔助完成序列化和反序列化,當一個類實現SerSerializable接口,沒有添加serialVersionUID的作用字段時,IDE會發出警告,這個字段可以手動指定一個值,比如1L,也可指定為IED根據類的結構生成一個long值,它們的效果是一樣的。在序列化時會將這個值寫入存儲介質,反序列化時就校驗本地類的serialVersionUID和序列化介質中的是否一致,不一致將拋出異常 java.io.InvalidClassException
(1)若不指定:系統會根據類的結構計算出一個serialVersionUID,一旦類的結構發生改變這個值就會改變,將導致反序列化失敗;
(2)指定一個值:當類的結構發生改變時,也可以不修改serialVersionUID的值,這種情況下能最大程度上通過反序列化回復數據,若類的結構發生毀滅性的改變,例如字段數據類型改變了,也會導致反序列失敗。
VLayout實現原理
從官網的介紹VLayout擴展的主要是LayoutManager,具體布局邏輯在LayoutHelper中實現。Vlayout提供了各種各樣的LayoutHelper實現,同時也可以非常容易地擴展實現自定義的LayoutHelper。
[圖片上傳失敗...(image-216067-1533601512655)]
可以看的上圖的列表中有各種各樣的布局,這些都是LayoutHelper的功勞。DelegateAdapter負責組合多個adapter,展示在RecyclerView中。
這有一個比較明顯的缺點是,每個子adapter只負責一個連續的區域,如果,有多個區域需要同樣的adapter,就需要添加兩次adapter。
VLayout為什么不用RecyclerView實現多Item
HTTPS連接過程
分為http鏈接和SSL鏈接過程
應用構建過程
[圖片上傳失敗...(image-71cd6a-1533601512656)]
通常的構建過程就是如上圖所示,下面是具體描述:
1.AAPT(Android Asset Packaging Tool)工具會打包應用中的資源文件,如AndroidManifest.xml、layout布局中的xml等,并將xml文件編譯為二進制形式,當然assets文件夾中的文件不會被編譯,圖片及raw文件夾中的資源也會保持原來的形態,需要注意的是raw文件夾中的資源也會生成資源id。AAPT編譯完成之后會生成R.java文件。
2.AIDL工具會將所有的aidl接口轉化為java接口。
3.所有的java代碼,包括R.java與aidl文件都會被Java編譯器編譯成.class文件。
4.Dex工具會將上述產生的.class文件及第三庫及其他.class文件編譯成.dex文件(dex文件是Dalvik虛擬機可以執行的格式),dex文件最終會被打包進APK文件。
5.ApkBuilder工具會將編譯過的資源及未編譯過的資源(如圖片等)以及.dex文件打包成APK文件。
6.生成APK文件后,需要對其簽名才可安裝到設備,平時測試時會使用debug keystore,當正式發布應用時必須使用release版的keystore對應用進行簽名。
7.如果對APK正式簽名,還需要使用zipalign工具對APK進行對齊操作,這樣做的好處是當應用運行時會提高速度,但是相應的會增加內存的開銷。
簽名機制流程:
1、對Apk中的每個文件做一次算法(數據摘要+Base64編碼),保存到MANIFEST.MF文件中
2、對MANIFEST.MF整個文件做一次算法(數據摘要+Base64編碼),存放到CERT.SF文件的頭屬性中,在對MANIFEST.MF文件中各個屬性塊做一次算法(數據摘要+Base64編碼),存到到一個屬性塊中。
3、對CERT.SF文件做簽名,內容存檔到CERT.RSA中
應用簽名校驗過程
1、驗證Apk中的每個文件的算法(數據摘要+Base64編碼)和MANIFEST.MF文件中的對應屬性塊內容是否配對
2、驗證CERT.SF文件的簽名信息和CERT.RSA中的內容是否一致
3、MANIFEST.MF整個文件簽名在CERT.SF文件中頭屬性中的值是否匹配以及驗證MANIFEST.MF文件中的各個屬性塊的簽名在CERT.SF文件中是否匹配
V1簽名和V2簽名區別
v1簽名是對jar進行簽名,V2簽名是對整個apk簽名:官方介紹就是:v2簽名是在整個APK文件的二進制內容上計算和驗證的,v1是在歸檔文件中解壓縮文件內容。
二者簽名所產生的結果:
v1:在v1中只對未壓縮的文件內容進行了驗證,所以在APK簽名之后可以進行很多修改——文件可以移動,甚至可以重新壓縮。即可以對簽名后的文件在進行處理 。
v2:v2簽名驗證了歸檔中的所有字節,而不是單獨的ZIP條目,如果您在構建過程中有任何定制任務,包括篡改或處理APK文件,請確保禁用它們,否則您可能會使v2簽名失效,從而使您的APKs與Android 7.0和以上版本不兼容。
Dex加固原理
下面就來看一下Android中加殼的原理:
[圖片上傳失敗...(image-2f742d-1533601512656)]
我們在加固的過程中需要三個對象:
1****、需要加密的****Apk(****源****Apk)
2****、殼程序****Apk(****負責解密****Apk****工作****)
3****、加密工具****(****將源****Apk****進行加密和殼****Dex****合并成新的****Dex)
主要步驟:
我們拿到需要加密的****Apk****和自己的殼程序****Apk****,然后用加密算法對源****Apk****進行加密在將殼****Apk****進行合并得到新的****Dex****文件,最后替換殼程序中的****dex****文件即可,得到新的****Apk,****那么這個新的****Apk****我們也叫作脫殼程序****Apk.****他已經不是一個完整意義上的****Apk****程序了,他的主要工作是:負責解密源****Apk.****然后加載****Apk,****讓其正常運行起來。
APK瘦身
1、使用一套資源
對于絕大對數APP來說,只需要取一套設計圖就足夠了。鑒于現在分辨率的趨勢,建議取720p的資源,放到xhdpi目錄。
2、開啟minifyEnabled混淆代碼
在gradle使用minifyEnabled進行Proguard混淆的配置,可大大減小APP大小
3、開啟shrinkResources去除無用資源
在gradle使用shrinkResources去除無用資源,效果非常好。
4、清理無用資源
Lint在檢查完成后,會提供一份詳細的資源文件清單,并且將沒有用到的資源在 UnusedResources:Unused resources 區域。只要我們沒有通過反射使用這些資源,就可以放心的刪掉它們了。版本迭代過程中,不但有廢棄代碼冗余,肯定會有無用的圖片存在。真正起效果只能通過Android Studio自帶的 “Remove Unused Resources”小插件來實現了
5、刪除無用的語言資源
大部分應用其實并不需要支持幾十種語言的國際化支持。還好強大的gradle支持語言的配置,比如國內應用只支持中文:
6、使用jpg格式,使用webp格式,縮小大圖
7、刪除armable-v7包下的so ,刪除x86包下的so
8、使用微信資源壓縮打包工具