Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android 面試題總結(二)

Android 面試題總結(二)

編輯:關於android開發

Android 面試題總結(二)


前言

筆者最近離職找工作快兩周了,這段時間陸陸續續也見識了北上廣這邊跟西部城市對待技術理念的差異和學習深度.俗話說:知恥而後勇,在經歷了面試被虐得體無完膚的過程後,我也找到了作為一名開發者應有的職業素養和今年的發展規劃. 俗話也說的好,王侯將相寧有種乎,我不信我從今天開始認認真真對待每一個技術細節,認真刷題.,在深圳這座城市沒有我的立足之地. 好了,雞湯和廢話也不多說了,依舊記錄今日面試的題目.

正文

含義:一個對象已經不需要再使用了,但是因為其他的對象持有該對象的引用,導致它的內存不能被回收.
危害:只有一個,那就是虛擬機占用內存過高,導致OOM(內存溢出),程序出錯.
內存洩露的主要問題可以分為以下幾種類型:
1.靜態變量引起的內存洩露:
在java中靜態變量的生命周期是在類加載時開始,類卸載時結束.換句話說,在android中其生命周期是在進程啟動時開始,進程死亡時結束.所以在程序的運行期間,如果進程沒有被殺死,靜態變量就會一直存在,不會被回收掉.如果靜態變量強引用了某個Activity中變量,那麼這個activity就同樣也不會被釋放,即便是該activity執行了onDestroy.
解決方案:
1.尋找與該靜態變量生命周期差不多的替代對象.
2.若找不到,將強引用方式改成弱引用.
案例:單例引起的context內存洩露
public static IMManager getInstance(Context context) {
        if (mInstance == null) {
            synchronized (IMManager.class) {
                if (mInstance == null)
                    mInstance = new IMManager(context);
            }
        }
        return mInstance;
    }

當調用getInstance時,如果傳入的context是Activity的context。只要這個單例沒有被釋放,這個Activity也不會被釋放。
解決方案:傳入Application的context.
2.非靜態內部類引起的內存洩露:
在java中,創建一個非靜態的內部類實例,就會引用它的外圍實例.如果這個非靜態內部類實例做了一些耗時的操作.就會造成外圍對象不會被回收,從而導致內存洩露.
解決方案:
1.將內部類變成靜態內部類
當將內部類變成靜態內部類後,便不再持有外部類對象的引用,導致程序不允許你在handler中操作activity的對象了,因此我麼需要在handler中增加一個對A/ctivity的弱引用(WeakReference);
注:為什麼使用靜態內部類就不會造成內存洩露了?
答:靜態內部類不會持有對外部對象的引用
2.如果有強引用Activity中的屬性,則將該屬性的引用方式改為弱引用.
使用WeakReference包裝該對象.
3.在業務允許的情況下,當activity執行onDestory時,結束這些耗時任務

static class MyHandler extends Handler {
    WeakReference mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

3.資源未關閉引起的內存洩露
當使用了BroadcastReceiver\Cursor\Bitmap等資源的時候,當不需要使用時,需要及時釋放掉,若沒有釋放,則會引起內存洩露.

4.**增加:在使用Bitmap時,出現內存溢出的解決方案:**1.調用系統的GC回收,System.gc();2.壓縮圖片質量大小.

小結

雖然靜態類與非靜態類之間的區別並不大,但是對於Android開發者而言卻是必須理解的.至少我們要清楚,如果一個內部類實例的生命周期比Activity更長,那麼我們千萬不要使用非靜態的內部類.最好的做法是,使用靜態內部類,然後在該類裡面使用弱引用來指向所在的Activity

2.Java基礎之弱引用\強引用\軟引用\虛引用
強引用(StrongReference):是使用最普遍的引用.如果一個對象具有強引用,那GC回收器絕不會回收它.

    Object o=new Object();   //  強引用  
    public void test(){  
        Object o=new Object();  
        // 省略其他操作  
    }  

當內存不足時,java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題.如果不使用時,要通過如下方式來弱化引用.

在方法內部有一個強引用,這個引用保存在棧中,而真正得引用內容(Object )保存在堆中.當這個方法運行完成後,就會推出方法棧,則引用內容的引用不存在,這個Object會被回收.

但是如果這個o是全局的變量時,就需要在不用這個對象時復制為null,因為強引用不會被垃圾回收。
軟引用(SoftReference): 如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

    String str=new String("abc");                                     // 強引用  
    SoftReference softRef=new SoftReference(str);     // 軟引用    

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
弱引用:
弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。

    String str=new String("abc");      
    WeakReference abcWeakRef = new WeakReference(str);  
    str=null;    

當你想引用一個對象,但是這個對象有自己的生命周期,你不想介入這個對象的生命周期,這時候你就是用弱引用。
虛引用(PhantomReference):
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器准備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。
這裡寫圖片描述

3.Java中static,final等關鍵字:
static關鍵字:
被static修飾的成員變量和成員方法獨立於該類的任何對象。也就是說,它不依賴類特定的實例,被類的所有實例共享。只要這個類被加載,Java虛擬機就能根據類名在運行時數據區的方法區內找到他們。因此,static對象可以在它的任何對象創建之前訪問,無需引用任何對象。
1.static修飾方法:
當static關鍵字修飾方法時,這個方法就稱為靜態方法。靜態方法屬於類而不屬於實例對象。那麼靜態方法如何進行調用?可以通過類名.方法名來直接調用,也可以通過實例對象.方法名來調用。
使用static關鍵字修飾的方法在類加載的時候就會加載到內存中。是被指向所屬的類而不是實例。
可以看出,這並不是覆蓋,其實靜態方法只可以被隱藏,而不可以覆蓋。當t1為Person類型時,則調用Person類的靜態方法,因為靜態方法只屬於這個類,而當t2類型為Test02時,調用子類的靜態方法,當子類沒有時,調用父類的方法,這就叫做繼承父類的方法了。
2.static修改屬性:
當static修飾屬性時,與類一樣,都是在類加載時就加載到了內存。
3.static修飾塊
加static修飾的塊也會隨著類的加載而進入內存,所以可以在這裡初始化一些靜態成員變量。
4.使用static注意事項:
當類被虛擬機加載時,按照聲明順序先後初始化static成員字段和static語句塊
static所修飾的方法和變量只屬於類,所有對象共享。
在static所修飾的方法和語句塊中不能使用非static成員字段。
靜態成員不是對象的特性,只是為了找一個容身之處,所以需要放到一個類中而已,把“全局變量”放在內部類中就是毫無意義的事情,所以 被禁止
在Java不能直接定義全局變量,是通過static來實現的
在Java中沒有const,不能直接定義常量,通過static final來實現
final 關鍵字:
1.final關鍵字的使用:引用本身的不變和引用指向的對象不變。
修飾類:*如果一個類被聲明為final,意味著它不能再派生出新的子類,不能作為父類被繼承*。因此一個類不能同時聲明為abstract final或者interface final的。
修飾方法 :將方法聲明為final,可以保證它們在使用中不被改變。被聲明為final的方法也同樣只能使用,不能重載。但是子類可以繼承父類的final方法。
修飾變量 :表示屬性值第一次初始化後不能被修改。final屬性可以直接初始化或在構造函數中初始化.如果屬性是直接初始化,則其值不能被其它函數(包括構造函數)修改。
修飾方法參數 :參數值不能被修改
修飾方法中的局部變量 : 局部變量被第一次初始化後不能被修改
注:任何在interface裡聲明的成員變量,默認為public static final。
使用final的意義:
第一,為方法“上鎖”,防止任何繼承類改變它的本來含義和實現。設計程序時,若希望一個方法的行為在繼承期間保持不變,而且不可被覆蓋或改寫,就可以采取這種做法。
第二,提高程序執行的效率,將一個方法設成final後,編譯器就可以把對那個方法的所有調用都置入“嵌入”調用裡(內嵌機制)。
finally關鍵字:
在異常處理時提供 finally 塊來執行任何清除操作。如果拋出一個異常,那麼相匹配的 catch 子句就會執行,然後控制就會進入 finally 塊。 所以說finally塊是一定會被執行的。
finally 語句塊是在 try 或者 catch 中的 return 語句之前執行的。更加一般的說法是,finally 語句塊應該是在控制轉移語句之前執行,控制轉移語句除了 return 外,還有 break 和 continue。 return 和 throw 把程序控制權轉交給它們的調用者(invoker),而 break 和 continue 的控制權是在當前方法內轉移。
finalize 方法名:
finalize()方法是在垃圾收集器刪除對象之前對這個對象調用的。

4.Java多線程線程池 - 線程池原理:

    public interface Executor {  
        void execute(Runnable command);  
    }  

Executors方便的創建線程池.
(1).newCachedThreadPool :該線程池比較適合沒有固定大小並且比較快速就能完成的小任務,它將為每個任務創建一個線程。那這樣子它與直接創建線程對象(new Thread())有什麼區別呢?看到它的第三個參數60L和第四個參數TimeUnit.SECONDS了嗎?好處就在於60秒內能夠重用已創建的線程。下面是Executors中的newCachedThreadPool()的源代碼: 

    //基本的無界值(如 Integer.MAX_VALUE),則允許池適應任意數量的並發任務
    public static ExecutorService newCachedThreadPool() {  
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue());  
        }  

(2).newFixedThreadPool:使用的Thread對象的數量是有限的,如果提交的任務數量大於限制的最大線程數,那麼這些任務將排隊,然後當有一個線程的任務結束之後,將會根據調度策略繼續等待執行下一個任務。下面是Executors中的newFixedThreadPool()的源代碼:  

    public static ExecutorService newFixedThreadPool(int nThreads) {  
          return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());  
      }  

(3).newSingleThreadExecutor:就是線程數量為1的FixedThreadPool,如果提交了多個任務,那麼這些任務將會排隊,每個任務都會在下一個任務開始之前運行結束,所有的任務將會使用相同的線程。下面是Executors中的newSingleThreadExecutor()的源代碼: 

    public static ExecutorService newSingleThreadExecutor() {  
            return new FinalizableDelegatedExecutorService  
                (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));  
        }  

(4).newScheduledThreadPool:創建一個固定長度的線程池,而且以延遲或定時的方式來執行任務。
ThreadPoolExecutor構造方法:

 public ThreadPoolExecutor(int corePoolSize,  //核心線程
                     int maximumPoolSize,  // 線程池維護的最大線程數量 
                     long keepAliveTime,  //線程池維護線程所允許的空閒時間
                     TimeUnit unit,  
                     BlockingQueue workQueue //// 阻塞隊列  ) {  
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
        Executors.defaultThreadFactory(), defaultHandler);  
}  

ExecutorService:為了解決執行服務的生命周期問題,Executor擴展了EecutorService接口,添加了一些用於生命周期管理的方法。

ThreadPoolExecutor線程池實現類:

    private final BlockingQueue workQueue;              // 阻塞隊列  
    private final ReentrantLock mainLock = new ReentrantLock();   // 互斥鎖  
    private final HashSet workers = new HashSet();// 線程集合.一個Worker對應一個線程  
    private final Condition termination = mainLock.newCondition();// 終止條件  
    private int largestPoolSize;           // 線程池中線程數量曾經達到過的最大值。  
    private long completedTaskCount;       // 已完成任務數量  
    private volatile ThreadFactory threadFactory;     // ThreadFactory對象,用於創建線程。  
    private volatile RejectedExecutionHandler handler;// 拒絕策略的處理句柄  
    private volatile long keepAliveTime;   // 線程池維護線程所允許的空閒時間  
    private volatile boolean allowCoreThreadTimeOut;  
    private volatile int corePoolSize;     // 線程池維護線程的最小數量,哪怕是空閒的  
    private volatile int maximumPoolSize;  // 線程池維護的最大線程數量  

處理任務的優先級為:

核心線程corePoolSize > 任務隊列workQueue > 最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。 當池中的線程數大於corePoolSize的時候,多余的線程會等待keepAliveTime長的時間,如果無請求可處理就自行銷毀。

workQueue :線程池所使用的緩沖隊列,該緩沖隊列的長度決定了能夠緩沖的最大數量,緩沖隊列有三種通用策略:
(1) 直接提交,工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們.
(2) 無界隊列,使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列.
(3) 有界隊列,當使用有限的 maximumPoolSizes 時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折中:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量.

ThreadFactory:使用 ThreadFactory 創建新線程。如果沒有另外說明,則在同一個 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 創建線程,並且這些線程具有相同的 NORM_PRIORITY 優先級和非守護進程狀態。通過提供不同的 ThreadFactory,可以改變線程的名稱、線程組、優先級、守護進程狀態等等。如果從 newThread 返回 null 時 ThreadFactory 未能創建線程,則執行程序將繼續運行,但不能執行任何任務。
在DefaultThreadFactory類中實現了ThreadFactory接口並對其中定義的方法進行了實現,如下:

    static class DefaultThreadFactory implements ThreadFactory {  
        private static final AtomicInteger poolNumber = new AtomicInteger(1);  
        private final ThreadGroup group;  
        private final AtomicInteger threadNumber = new AtomicInteger(1);  
        private final String namePrefix;  

        DefaultThreadFactory() {  
            SecurityManager s = System.getSecurityManager();  
            group = (s != null) ? s.getThreadGroup() :  Thread.currentThread().getThreadGroup();  
            namePrefix = "pool-" +  poolNumber.getAndIncrement() +  "-thread-";  
        }  
        // 為線程池創建新的任務執行線程  
        public Thread newThread(Runnable r) {  
            // 線程對應的任務是Runnable對象r  
            Thread t = new Thread(group, r,namePrefix + threadNumber.getAndIncrement(), 0);  
            // 設為非守護線程  
            if (t.isDaemon())  
                t.setDaemon(false);  
            // 將優先級設為Thread.NORM_PRIORITY  
            if (t.getPriority() != Thread.NORM_PRIORITY)  
                t.setPriority(Thread.NORM_PRIORITY);  
            return t;  
        }  
    }  

RejectedExecutionHandler:
當Executor已經關閉(即執行了executorService.shutdown()方法後),並且Executor將有限邊界用於最大線程和工作隊列容量,且已經飽和時,在方法execute()中提交的新任務將被拒絕.
  在以上述情況下,execute 方法將調用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四種預定義的處理程序策略:

    1) 在默認的 ThreadPoolExecutor.AbortPolicy 處理程序遭到拒絕將拋出運行時 RejectedExecutionException;
    2) 在 ThreadPoolExecutor.CallerRunsPolicy 線程調用運行該任務的 execute 本身。此策略提供簡單的反饋控制機制,能夠減緩新任務的提交速度

    3) 在 ThreadPoolExecutor.DiscardPolicy 不能執行的任務將被刪除;

    4) 在 ThreadPoolExecutor.DiscardOldestPolicy 如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重復此過程)。

線程池默認會采用的是defaultHandler策略。
Callable接口代表一段可以調用並返回結果的代碼;Future接口表示異步任務,是還沒有完成的任務給出的未來結果。所以說Callable用於產生結果,Future用於獲取結果。

以下內容來自http://www.trinea.cn/android/java-android-thread-pool/,再此簡單記錄.
new Thread的弊端:
a.每次new Thread新建對象性能差.
b.線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能占用過多系統資源導致死機或oom.
c.缺乏更多功能,如定時執行、定期執行、線程中斷.
Java提供的四種線程池的好處:
a. 重用存在的線程,減少對象創建、消亡的開銷,性能佳。
b. 可有效控制最大並發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
c. 提供定時執行、定期執行、單線程、並發數控制等功能。
(1). newCachedThreadPool
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。線程池為無限大,當執行第二個任務時第一個任務已經完成,會復用執行第一個任務的線程,而不用每次新建線程。
(2). newFixedThreadPool
創建一個定長線程池,可控制線程最大並發數,超出的線程會在隊列中等待。定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()。
(3) newScheduledThreadPool
創建一個定長線程池,支持定時及周期性任務執行。延遲執行示例代碼如下
(4)、newSingleThreadExecutor
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。現行大多數GUI程序都是單線程的。Android中單線程可用於數據庫操作,文件操作,應用批量安裝,應用批量刪除等不適合並發但可能IO阻塞性及影響UI線程響應的操作。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved