onSaveInstanceState,就是用bundle存儲。
1、當用戶按下手機home鍵的時候。
2、長按手機home鍵或者按下菜單鍵時。
3、手機息屏時。
4、FirstActivity啟動SecondActivity,FirstActivity就會調用,也就是說打開新Activity時,原Activity就會調用。
5、默認情況下橫豎屏切換時。
當豎屏切換到橫屏時,系統會銷毀豎屏Activity然後創建橫屏的Activity,所以豎屏被銷毀時,該方法就會被調用。
Bundle存取數據具體實現
內部維護了一個ArrayMap,具體定義是在其父類BaseBundle中;
HashMap:大小16位數組,空間不足時,空間將會以2倍的,採用鍊表結合結構進行數據存儲。
ArrayMap:是一個<key,value>映射的數據結構,它設計上更多的是考慮內存的優化,內部是使用兩個數組進行數據存儲,一個數組記錄key的hash值,另外一個數組記錄Value值,它和SparseArray一樣,也會對key使用二分法進行從小到大排序,在添加、刪除、查找數據的時候都是先使用二分查找法得到相應的index,效率稿。
java程序在內存中的存儲分配情況:
一、堆區: 1.存儲的全部是對象,每個對象都包含一個與之對應的class的信息。(class的目的是得到操作指令) 2.jvm只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身
棧區: 1.每個線程包含一個棧區,棧中只保存基礎數據類型的對象和自定義對象的引用(不是對象),對象都存放在堆區中 2.每個棧中的數據(原始類型和對象引用)都是私有的,其他棧不能訪問。 3.棧分為3個部分:基本類型變量區、執行環境上下文、操作指令區(存放操作指令)。
方法區: 1.又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。 2.方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量。
二、內存分區而內存分為四個區:stack segment,heap segment,data segment,code segment;stack 區存放函數參數和局部變量;heap 區存放對象;data 區存放static 的變量或者字符串常量; code 區存放類中的方法;因此,靜態變量是存放在data區的 !
內存洩漏:
靜態對象內存洩漏
static Object cc=null
在Dalvik虛擬機中,static變量所指向的內存引用,如果不把它設置為null,那麼JVM虛擬機就會在內存中一直保留這個對象,這個對象不會被垃圾回收器清理,GC是永遠不會回收這個對象的,直到應用退出。
非靜態內部類內存洩漏
1.編譯器自動為內部類添加一個成員變量, 這個成員變量的類型和外部類的類型相同, 這個成員變量就是指向外部類對象的引用
2.編譯器自動為內部類的 構造方法添加一個參數, 參數的類型是外部類的類型, 在構造方法內部使用這個參數為1中添加的成員變量賦值;
3.在調用內部類的構造函數初始化內部類對象時, 會默認傳入外部類的引用。
越來越多的內存得不到回收的時候,最終就有可能導致內存溢出,下面說一下使用staitc屬性所導致的內存洩漏的問題。
常用的Utils中的static修飾
public class ToastUtil {
private static Toast toast;
public static void show(Context context, String message) {
if (toast == null) {
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
} else {
toast.setText(message);
}
toast.show();
}
}
分析:
static修飾的toast對象,在show方法中這個對象同時對context持有了引用。toast是static修飾的,意味著toast將不會從內存中消失,那麼其持有的引用對象context將一直保持強引用,也不會在內存中消失。如果傳個佔用大內存的Activity的context進來,那麼將會導致大量的內存洩漏。
解決方案:
將context改為context.getApplicationContext(),由於ApplicationContext是一個全局的context,會在app運行期間一直存在,就不存在內存洩露的問題了
Handler造成的內存洩漏
public class MainActivity extends AppCompatActivity
{
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...更新UI操作
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
....
....
initDatas();
}
private void initDatas()
{
//...子線程獲取數據,在主線程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
分析:
這種創建Handler的方式會造成內存洩漏,由於mHandler是Handler的非靜態匿名內部類的實例,持有外部類Activity的引用。消息隊列是在一個Looper線程中不斷輪詢處理消息,如果當前Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,而mHandler又持有Activity的引用,導致Activity的內存資源無法回收,引發內存洩漏。而別的匿名內部類比如new OnClickListener()因為得到及時回收並非一直被觸發,所以會被回收。
解決方案:(新建內部類handler)最後:
mHandler.removeCallbacksAndMessages(null);
public class MainActivity extends AppCompatActivity
{
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null)
{
//...更新UI操作
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
...
initDatas();
}
private void initDatas()
{
//...子線程獲取數據,在主線程中更新UI
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
//移除消息隊列中所有消息和所有的Runnable
mHandler.removeCallbacksAndMessages(null);
}
}
靜態內部類:
1.靜態內部類是不需要依賴於外部類的
2.它不能使用外部類的非static成員變量或者方法(在沒有外部類的對象的情況下,可以創建靜態內部類的對象,如果允許訪問外部類的非static成員就會產生矛盾,因為外部類的非static成員必須依附於具體的對象)
非靜態內部類:
1.成員內部類可以無條件訪問外部類的所有成員屬性和成員方法,包括private成員和靜態成員。
2.當成員內部類擁有和外部類同名的成員變量或者方法時,外部類中的屬性和方法會發生隱藏現象,即默認情況下訪問的是成員內部類的成員,可以通過下面這種形式對外部類的屬性和方法進行訪問,外部類.this.成員變量,外部類.this.成員方法。
3.外部類中如果要訪問成員內部類的成員,必須先創建一個成員內部類的對象,再通過指向這個對象的引用來訪問。
4.如果要創建成員內部類的對象,前提是必須存在一個外部類的對象
局部內部類:
1.局部內部類是定義在一個方法或者一個作用域裡面的類,它和成員內部類的區別在於局部內部類的訪問僅限於方法內或者該作用域內。
內部類的應用場景:
場景一:當某個類除了它的外部類,不再被其他的類使用時。我們說這個內部類依附於它的外部類而存在,可能的原因有:1、不可能為其他的類使用;2、出於某種原因,不能被其他類引用,可能會引起錯誤。等等。這個場景是我們使用內部類比較多的一個場景。(內部類可以看成代碼隱藏機制)
場景二:當我們希望一個類必須繼承多個抽象或者具體的類時,就只能用內部類來實現多重繼承
內部類對象創建(成員內部類、局部內部類、匿名內部類和靜態內部類):
Outter outter = new Outter(); Outter.Inner inner = outter.new Inner(); //必須通過Outter對象來創建
匿名內部類:
new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
}
就是匿名內部類的使用。代碼中需要給按鈕設置監聽器對象,使用匿名內部類能夠在實現父類或者接口中的方法情況下同時產生一個相應的對象,但是前提是這個父類或者接口必須先存在才能這樣使用。當然像下面這種寫法也是可以的,跟上面使用匿名內部類達到效果相同。
匿名內部類是唯一一種沒有構造器的類。正因為其沒有構造器,所以匿名內部類的使用範圍非常有限,大部分匿名內部類用於接口回調。
補充:
1. 構造方法的特徵
它具有與類相同的名稱;
它不含返回值;
它不能在方法中用 return 語句返回一個值;
當自定義了構造方法後,編譯器將不再自動創建不帶參數的構造方法 。
在構造方法裡不含返回值的概念是不同於 「void」 的,在定義構造方法時加了 「void」 ,結果這個方法就不再被自動調用了。
JVM是Java Virtual Machine(Java虛擬機)的縮寫
synchronized 關鍵字,代表這個方法加鎖,相當於不管哪一個線程(例如線程A),運行到這個方法時,都要檢查有沒有其它線程B(或者C、 D等)正在用這個方法(或者該類的其他同步方法),
1、線程安全:
指多個線程在執行同一段代碼的時候採用加鎖機制,使每次的執行結果和單線程執行的結果都是一樣的,不存在執行程序時出現意外結果。
2、線程不安全:
是指不提供加鎖機制保護,有可能出現多個線程先後更改數據造成所得到的數據是髒數據。
Android開發過程中的版本適配問題?
Android4.4適配:
uri轉path需要適配
Android5.0適配:
分包適配 -〉在5.0及以上在app的gradle文件中配置multiDexEnabled true即可,但是5.0以下需要倒入jar,然後在Application的attch方法中進行初始化
Android6.0:
權限適配 -〉敏感權限動態申請;
Android7.0:
Uri.fromFile()適配 -〉使用FileProvider進行適配;
Android出於安全考慮關閉了網絡/拍照/錄像系統廣播;
Android8.0:
Service啟動方式適配 -〉需要使用startForegroundService()啟動服務;
Notification適配 -〉添加了渠道和組的概念;
軟體安裝適配 -〉Android8.0去掉了「允許未知來源」選項,需要用戶手動確定,所以安裝程序需要在AndroidManifest.xml文件中添加REQUEST_INSTALL_PACKAGES權限;
廣播適配 -〉AndroidManifest.xml中註冊的廣播不能使用隱式,需要明確指定。
權限適配-〉讀寫權限分離
關於協程的概念
簡單的介紹:協程又稱微線程,是一個線程執行。協程看上去也是子程序,但是不同的是可以在子程序內部中斷轉而去執行其他子程序,然後在合適的時候再返回中斷位置繼續執行。
協程特點:
執行效率高:沒有多線程的線程間切換的開銷;
不需要多線程的鎖機制:因為只有一個線程,所以不需要
面試題3.
synchronized和lock的區別?
synchronized會主動釋放鎖,而lock需要手動調用unlock釋放鎖;
synchronized是java內置的關鍵字,而lock是個java類;
面試題4.
Handler機制如何保證消息不錯亂?消息延遲是如何實現的?Handler、Looper、MessageQueue三者對應關係?內存洩漏如何避免?Looper中的死循環為什麼不會引器主線程ANR?
1.handler機制中多個handler共有一個looper不會錯亂是因為在handler 發送消息的時候,會將當前的handler對象綁定到message的target屬性上,然後在Looper取到消息後通過msg.target拿到之前的handler對象,然後調用handler的handleMessage方法。
2.消息延遲的原理:handler發送延遲消息,會將當前的延遲時間綁定到msg的when屬性上,然後在循環MessageQUeue獲取msg時判斷如果當前有延遲就進行阻塞,通過計時器計算時間,時間通過系統啟動計算時間,然後等待阻塞時間結束之後將其喚醒,在阻塞過程中會將之後的消息放在消息隊列的頭部去處理。
3.同一個線程中可以有多個Handler,只有一個Looper,而MessageQueue在looper中初始化的,所以也只有一個MessageQueue。因此對應關係是:Handler:Looper = 多對一,Looper:MeesageQueue = 一對一,Handler:MessageQueue = 多對一。
4.Handler的內存洩漏是由於Handler持有外部類的引用,使其無法釋放。
解決辦法:(1)定義成靜態內部類,使其不持有外部類的引用;(2)可以使用弱引用;
還需要在外部類銷毀的時候,移除所有的消息。
5.可以說整個應用的生命周期都是在looper.loop()控制之下的(在應用啟動的入口main函數中初始化ActivityThread,Handler,Looper,然後通過handler和looper去控制初始化應用)。而looper.loop採用的是Linux的管道機制
在沒有消息的時候會進入阻塞狀態,釋放CPU執行權,等待被喚醒。
真正會卡死主線程的操作是在回調方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,
looper.loop本身不會導致應用卡死。
面試題5.
開發過程中如果想替換第三方jar中的某個class文件,或者在開發時你的class文件與jar中的重名,但是你想使用自己的應該如何解決?如果你替換掉某個方法又該怎麼解決?
可以獲取到jar的源碼或者將jar反編譯獲取到java項目,然後替換掉自己想要的.java文件或者方法;
方式二:可以通過類加載器將目標class替換成自己的class;
面試題6.
IO與NIO的區別?
第一點:IO是面向流的,NIO是面向緩衝區的。
IO面向流意味著每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。
NIO是面向緩存的。數據讀取到一個緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數據。而且要確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區中未處理的數據。
第二點:IO的各種流是阻塞的。這意味著,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情。NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,而不是保持線程阻塞,在數據可讀之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閒時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
面試題7.
單例模式有幾種寫法以及各自的優劣?
1.餓漢式:
public class SingleInstance {
private static SingleInstance mInstance = new SingleInstance();
private SingleInstance(){}
public static SingleInstance getInstance(){
return mInstance;
}
}
缺點:存在內存損耗問題,如果當前類沒有用到也會被實例化
2.懶漢式:
public class SingleInstance {
private static SingleInstance mInstance = null;
private SingleInstance(){}
public static SingleInstance getInstance(){
if(mInstance==null){
synchronized (SingleInstance.class){
if(mInstance==null){
mInstance = new SingleInstance();
}
}
}
return mInstance;
}
}
缺點:加了synchronized鎖會影響性能
有次被問到為什麼要有兩次空判斷?
第一次空判斷和好理解,可以很大程度上減少鎖機制的次數;
第二次判空是因為,如果a,b兩個線程都到了synchronized處,而假設a拿到了鎖,進入到代碼塊中創建了對象,然後釋放了鎖,由於b線程在等待鎖,所以a釋放後,會被b拿到,因此此時判空就保證了實例的唯一性。
3.靜態內部類:
public class SingleInstance {
private SingleInstance(){}
public static SingleInstance getInstance(){
return Builder.mInstance;
}
private static class Builder{
private static SingleInstance mInstance = new SingleInstance();
}
}
優點:解決了內存浪費問題,同時也避免了加鎖性能問題
為什麼這種寫法是線程安全的?
因為類加載過程是安全的,而靜態變量是隨著類的加載進行初始化的。
4.枚舉形式:
public enum SingleInstance {
INSTANCE;
}
優點:不存在反射和反序列化的問題。
缺點:通過查看枚舉類生成的class文件發現,有多少變量,就會在靜態代碼塊中創建多少對象,所以不建議使用。
定義一些有意義的常量,如果不用枚舉,怎麼解決?
可以使用註解的形式,例如:
@IntDef({DataType.INT, DataType.STRING, DataType.FLOAT, DataType.DOUBLE, DataType.OBJECT})
public @interface DataType {
int INT = 0;
int STRING = 1;
int FLOAT = 2;
int DOUBLE = 3;
int OBJECT = 4;
}
面試題8.
ArrayList 和LinketList區別?hashmap的實現原理?hashmap與hashtable的區別?
ArrayList與LinketList差別:
ArrayList基於數組實現,所以get,set操作效率較高;
LinketList基於鍊表實現(雙向鍊表),所以add,remove操作效率較高;
如何實現高效率的查詢和插入結構?
二叉樹或者散列表
HashMap實現原理:
hashmap是由數組+鍊表結構現實的。獲取到key的hashcode,然後對數組長度取餘,找到對應的數組位置index,然後在對應的鍊表中判斷是否有當前key,從而進行查詢/添加/替換等操作。
HashMap與HashTable區別:
面試題9.
gson序列化數據時如何排除某個欄位?
方式一:給欄位加上 transient 修飾符
方式二:排除Modifier指定類型的欄位。這個方法需要用GsonBuilder定製一個GSON實例。
方式三:使用@expose(揭露,闡述,暴露)註解。沒有被 @expose 標註的欄位會被排除
面試題10.
ButterKnife與Xutils註解的區別?以及Retrofit中的註解是如何處理的?
ButterKnife採用的是編譯時註解,在編譯時生成輔助類,在運行時通過輔助類完成操作。編譯時註解運行效率較高,不需要反射操作。
XUtils採用的是運行時註解,在運行時通過反射進行操作。運行時註解相對效率較低。
Retrofit與EventBus採用的都是運行時註解,也就是通過反射技術處理的。
面試題11.
jvm的類加載機制?
類加載分類:
BootstrapClassLoader(負責加載java_home中的jre/lib/rt.jar中的class,不是ClassLoader的子類)
ExtensionClassLoader(負責加載java平臺中擴展的一些jar中的class)
AppClassLoader(負責加載classpath中指定的jar或class文件)
CustomClassLoader(自定義的classloader)
JVM的類加載機制採用的是雙親委派模型。
類加載過程:
由底層類加載器開始查找是否已經加載,如果底層已經加載,則視為已經加載
上層就無需再加載,避免重複加載。
如果沒有加載,則向上層類加載器查找,以此類推,直到頂層類加載器。如果最後發現頂層類加載器也沒有加載,則先交由頂層類加載器嘗試加載,如果無法加載,則交由下層類加器加載,直至底層類加載器
如果還是無法加載,則JVM會拋出相應的類加載異常。
面試題12.
列舉一些git版本控制的常用操作符?
面試題13.
AsyncTask內部也是通過線程池+Handler的方式實現的
AsyncTask的原理以及弊端?AsyncTask為什麼要求在主線程加載,對象為什麼要在主線程創建?
面試題14.
Android開發中的屏幕適配方案?
sw(smallestWidth最小寬度)適配;
通過修改系統的density值進行適配;
面試題15.
多線程中sleep和wait的區別?
sleep是Thread的靜態方法;wait是Object中的方法;
sleep過程中不會釋放鎖,不會讓出系統資源;wait會釋放鎖資源,將其放入等待池中,讓出系統資源,讓cpu可以執行其他線程;
sleep之後可以主動釋放鎖;wait需要手動去notify;
面試題16.
輸出字符串中的第一個不重複的字符,例如:
「hello」輸出 『h』
「abbac」輸出 『c』
「abdabe」輸出『d』
利用LinketHashMap數組的有序性和鍵的唯一性來處理:
private void printChar(String source) {
if (source == null) {
return;
}
String soureTrim = source.replaceAll(" ", "");//去掉字符串中的所有空格
char[] chars = soureTrim.toCharArray();//拿到字符串對應的char[]
int length = chars.length;
//用map鍵的唯一性去處理記錄重複數據,而選擇LinkedHashMap是為了保證有序
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
//循環檢測或插入數據,然後通過value的值記錄當前字符出現次數
for (int i = 0; i < length; i++) {
char key = chars[i];
Integer value = map.get(key);
if (value == null) {
map.put(key, 1);
} else {
map.put(key, value+1);
}
}
//value=1,說明只出現一次
Set<Character> keys = map.keySet();
for (Character key : keys) {
Integer integer = map.get(key);
if (integer == 1) {
System.out.println("current frist only char is = " + key);
break;
}
}
}
面試題17.
對有序int數組去重,並輸出去重後的長度,並列印出來,要求時間複雜度為O(n),空間複雜度為O(1)。
例如:int[] array = {-1,0,0,2,4,4,4,6};
長度為:5,列印結果為:-1,0,2,4,6
面試題18.
假設有A,B,C三個線程,在A線程的執行過程中去執行B線程,並且等待B線程的執行結果,然後去執行C線程,然後當C線程執行完成後,返回結果給A線程。不阻塞線程,如何實現?(相關描述我也記不太清了,可能有些不準確,考點就是Future)
面試題19.
ThreadLocal作用?
ThreadLocal是一個線程內的數據存儲類,可以通過它在指定線程中存儲數據,並且只有在當前線程可以獲取到存儲的數據。通常當某些數據以線程為作用域並且不同線程具有不同的數據副本時使用。
通過查看源碼可以知道,set方法會通過values()方法拿到當前線程的ThreadLocal數據(Thread類中有個成員變量專門存儲ThreadLocal數據:ThreadLocal.Values localValues),在localValues內部有個數組Object[] table,用於存儲ThreadLocal的值,而位置存儲在ThreadLocal的reference的下一個位置。
而get方法就是通過當前線程的reference拿到localValues中table的位置,然後index+1獲取數據。