1. 程式人生 > >Android面試題5

Android面試題5

81.雙快取怎麼實現的?

答:1、在記憶體中建立一塊“虛擬畫布”:
Bitmap bmp = new Bitmap(600, 600);
2、獲取這塊記憶體畫布的Graphics引用:
Graphics g = Graphics.FromImage(bmp);
3、在這塊記憶體畫布上繪圖:
g.FillEllipse(brush, i * 10, j * 10, 10, 10);
4、將記憶體畫布畫到視窗中
this.CreateGraphics().DrawImage(bmp, 0, 0);

82.垃圾收集演算法的核心思想

答:Java語言建立了垃圾收集機制,用以跟蹤正在使用的物件和發現並回收不再使用(引用)的物件。該機制可以有效防範動態記憶體分配中可能發生的兩個危險:因記憶體垃圾過多而引發的記憶體耗盡,以及不恰當的記憶體釋放所造成的記憶體非法引用。

  垃圾收集演算法的核心思想是:對虛擬機器可用記憶體空間,即堆空間中的物件進行識別,如果物件正在被引用,那麼稱其為存活物件,反之,如果物件不再被引用,則為垃圾物件,可以回收其佔據的空間,用於再分配。垃圾收集演算法的選擇和垃圾收集系統引數的合理調節直接影響著系統性能,因此需要開發人員做比較深入的瞭解。

83.觸發主GC(Garbage Collector)的條件

答:①當應用程式空閒時,即沒有應用執行緒在執行時,GC會被呼叫。因為GC在優先順序最低的執行緒中進行,所以當應用忙時,GC執行緒就不會被呼叫,但以下條件除外。

②Java堆記憶體不足時,GC會被呼叫。當應用執行緒在執行,並在執行過程中建立新物件,若這時記憶體空間不足,JVM就會強制地呼叫GC執行緒,以便回收記憶體用於新的分配。若GC一次之後仍不能滿足記憶體分配的要求,JVM會再進行兩次GC作進一步的嘗試,若仍無法滿足要求,則 JVM將報“out of memory”的錯誤,Java應用將停止。

84.減少GC開銷的措施

答:

(1)不要顯式呼叫System.gc()

(2)儘量減少臨時物件的使用

(3)物件不用時最好顯式置為Null

(4)儘量使用StringBuffer,而不用String來累加字串

(5)能用基本型別如Int,Long,就不用Integer,Long物件

(6)儘量少用靜態物件變數

(7)分散物件建立或刪除的時間

85.gc與finalize方法

答:finalize(),其工作原理:一旦垃圾回收器準備好釋放物件佔用的儲存空間,將首先呼叫其finalize()方法,並且在下一次垃圾回收動作發生時,才會真正回收物件佔用的記憶體。所以如果用finalize()就能在垃圾回收時刻做一些重要的清理工作。

gc 只能清除在堆上分配的記憶體(純java語言的所有物件都在堆上使用new分配記憶體),而不能清除棧上分配的記憶體(當使用JNI技術時,可能會在棧上分配內 存,例如java呼叫c程式,而該c程式使用malloc分配記憶體時).因此,如果某些物件被分配了棧上的記憶體區域,那gc就管不著了,對這樣的物件進行 記憶體回收就要靠finalize().

86.ViewPager如何實現?

答:a.Viewpager是一個控制元件,用的時候直接在佈局中定義引用(Android.support.v4.view.ViewPager)即可。  b.ViewPager裡面顯示的是多個View,用LayoutInflater的物件呼叫inFlater()方法定義佈局,並把View物件新增到List<View>集合中。

    c.定義介面卡繼承PagerAdapter,設定給ViewPager。

87.請說出使用equal和==比較物件時的區別?

答:“==”比較的是2個物件的地址,而equals比較的是2個物件的內容。例如兩個String型別的物件:String s1 = new String(“str”);

String s2 = new String(“str”);

用“==”比較的時候返回的是false,因為建立了兩個物件,在記憶體中的地址不一樣,用equals則返回true。

88.什麼是Java序列化和反序列話,如何實現Java序列化?

答:1)序列化是將物件狀態轉化成可保持或傳輸的格式的過程。與序列化相反的是反序列化,它是將流轉化為物件。

2)實現序列化必須要實現Serializable介面,目的是宣告此類是可以被序列化的,而且繼承此類的子類也可以自動的被序列化。

89.請解釋下android程式執行時許可權與檔案系統許可權的區別?

答:apk程式是執行在虛擬機器上的,對應的是Android中獨有的許可權機制,是由Dalvik授權的,而檔案系統許可權是由linux核心授權的,只有體現到檔案系統上時才使用linux的許可權設定。

90.講一講overload和override的區別,overloaded的方法是否可以改變返回值的型別?

答:overload是方法的過載,Java中的編譯時多型,過載的方法具有相同的方法名,但是引數列表不同(引數的型別和數量),對返回值型別沒有要求,可以改變;

    override是方法得重寫,Java中的執行時多型,為子類在繼承父類是重寫父類中的方法,方法名,返回值型別和引數列表都相同。

91.如何使用socket實現TCP點對點通訊?

答:伺服器端監聽:首先例項化ServerSocket,並傳入埠號(1001-65535),然後用ServerSocket物件呼叫accept()方法監聽,一旦取得連線則獲得socket連線物件的客戶端。

客戶端:例項化Socket,傳入伺服器的ip和port,一旦連線成功後,Socket物件就可以獲得輸入、輸出流,從而進行通訊。當通訊完成後,用Socket物件呼叫close()方法關閉連線,完成一次完整的 socket連線。

92.請簡述service可能被kill的場景,kill之後如何自啟?

在執行onStartCommand後service程序被kill後,系統將會再次啟動service,並傳入最後一個intent給onstartCommand。直到呼叫stopSelf(int)才停止傳遞intent。如果在被kill後還有未處理好的intent,那被kill後服務還是會自動啟動。因此onstartCommand不會接收到任何null的intent

93.請使用遞迴方式來遍歷盤下的所有檔案,並計算出所有圖片檔案的數量?

public class F3 {

public static void main(String[] args) {

File f1=new File("D://");

m1(f1);

}

 static void m1(File f1) {

// TODO Auto-generated method stub

 if(f1.exists()){

File file[]=f1.listFiles();

for(int i=0;i<file.length;i++){

if(file[i].isFile()){

System.out.println(file[i]);

}else{

m1(file[i]);

}

}

}else{

System.out.println("不存在!!!!");

}

}

}

94.現有兩個單向連結串列,我想知道這兩個連結串列的相交情況。(可以不寫實現,但思路必須寫清)?

答:方法一:直接法

直接判斷第一個連結串列的每個結點是否在第二個連結串列中,時間複雜度為O(len1*len2),耗時很大

方法二:利用計數

如果兩個連結串列相交,則兩個連結串列就會有共同的結點;而結點地址又是結點唯一標識。因而判斷兩個連結串列中是否存在地址一致的節點,就可以知道是否相交了。可以對第一 個連結串列的節點地址進行hash排序,建立hash表,然後針對第二個連結串列的每個節點的地址查詢hash表,如果它在hash表中出現,則說明兩個連結串列有共 同的結點。這個方法的時間複雜度為:O(max(len1+len2);但同時還得增加O(len1)的儲存空間儲存雜湊表。這樣減少了時間複雜度,增加 了儲存空間。

以連結串列節點地址為值,遍歷第一個連結串列,使用Hash儲存所有節點地址值,結束條件為到最後一個節點(無環)或Hash中該地址值已經存在(有環)。

再遍歷第二個連結串列,判斷節點地址值是否已經存在於上面建立的Hash表中。

這個方面可以解決題目中的所有情況,時間複雜度為O(m+n),m和n分別是兩個連結串列中節點數量。由於節點地址指標就是一個整型,假設連結串列都是在堆中動態建立的,可以使用堆的起始地址作為偏移量,以地址減去這個偏移量作為Hash函式

方法三

兩個沒有環的連結串列相交於一節點,則在這個節點之後的所有結點都是兩個連結串列所共有的。如果它們相交,則最後一個結點一定是共有的,則只需要判斷最後一個結點是否相同即可。時間複雜度為O(len1+len2)。對於相交的第一個結點,則可求出兩個連結串列的長度,然後用長的減去短的得到一個差值 K,然後讓長的連結串列先遍歷K個結點,然後兩個連結串列再開始比較。還可以這樣:其中一個連結串列首尾相連,檢測另外一個連結串列是否存在環,如果存在,則兩個連結串列相交,而檢測出來的依賴環入口即為相交的第一個

/************************************************************************/
/* 兩個不含環的單鏈表的相交
相交指的是結點的地址相同,而不是結點的值相同 */
/************************************************************************/

 

95.使用javac/c++寫一段程式,找出陣列中出現次數最多數字,並輸出出現次數,請標明演算法的時間複雜值?

import java.util.HashMap;  

import java.util.Iterator;  

public class Test   

{

    public static void main(String[] args)   

    {  

        int a [] = {6,6,6,1,2,8,8,5,5,5,12};  

        int maxnum = 0;  

        int maxvalue = 0;  

        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();  

        HashMap<Integer,Integer> map2 = new HashMap<Integer,Integer>();  

        for(int i=0;i<a.length;i++)  

        {  

            int count = 0;  

            for(int j=0;j<a.length;j++)  

            {  

                if(a[i]==a[j])  

                {  

                    count++;  

                }  

            }  

            map.put(a[i], count);//map會把重複的資料過濾掉  

        }  

        Iterator it =  map.keySet().iterator();  

        Iterator it2 = map.keySet().iterator();      

        while(it.hasNext())  

        {  

            Object key = it.next();  

            if(map.get(key)>maxnum)  

            {  

               maxnum = map.get(key);  

                maxvalue = (Integer)key;  

            }  

        }  

        map2.put(maxvalue, maxnum);  

        while(it2.hasNext())  

        {  

            Object key = it2.next();  

            if(map.get(key)==maxnum)  

            {  

                map2.put((Integer)key, maxnum);  

            }  

        }  

        Iterator it3 = map2.keySet().iterator();  

        while(it3.hasNext())  

       {  

            Object key = it3.next();  

            System.out.print("出現次數最多的為:"+key+"  ");  

            System.out.println("出現次數:"+map2.get(key));  

        }  

}}

96.什麼是OAuth,Oauth,的角色,Qauth驗證流程?

答:<1>OAuth(開放授權)是一個開放標準,允許使用者讓第三方應用訪問該使用者在某一網站上儲存的私密的資源(如照片,視訊,聯絡人列表),而無需將使用者名稱和密碼提供給第三方應用。

OAuth是個安全相關的協議,作用在於,使使用者授權第三方的應用程式訪問使用者的web資源,並且不需要向第三方應用程式透露自己的密碼。

OAuth允許使用者提供一個令牌,而不是使用者名稱和密碼來訪問他們存放在特定服務提供者的資料。每一個令牌授權一個特定的網站(例如,視訊編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相簿中的視訊)。這樣,OAuth允許使用者授權第三方網站訪問他們儲存在另外的服務提供者上的資訊,而不需要分享他們的訪問許可或他們資料的所有內容。

OAuth是OpenID的一個補充,但是完全不同的服務

<2>OAuth協議中包含了三個角色:
Service Provdier,即服務提供者,如新浪微博;
User,即普通使用者,如新浪微博使用者;
Consumer,即第三方應用,如本人開發的應用

<3>我在第三方應用A有需要帳號、密碼登入後才能訪問的一些資料,我想讓使用者登入騰訊的AlloyOS就可以訪問這些資料,但是我不想把密碼告訴騰訊,這時候就可以通過騰訊的QAuth來解決,給AlloyOS一個token,當用戶確認繫結賬號之後,使用者只要登入的AlloyOS,開啟第三方應用A時就通過token直接登入的第三方應用。

97.使用javac/c++寫一段程式,找出陣列中第K大小的數,輸出數所在的位置,請標明演算法的時間複雜度?

答:

import java.util.ArrayList;

public class Biaoshi{

public static void main(String args[]){

ArrayList<String> al=new ArrayList<String>();

 //向Java動態陣列中新增資料

 al.add("a");

 al.add("b");

 al.add("c");

 //輸出Java動態陣列

 int j=0;

 for(int i=0;i<al.size();i++)

 {

 j++;

  String k=(String)al.get(i);

  System.out.println(k+"--------"+j);

 }

 //刪除陣列中的某個元素,刪除第二個元素

 al.remove(1);

 //修改Java動態陣列,把新的元素放到第二個位置

 al.add(1,"2");

 ////輸出Java動態陣列

 for(int i=0;i<al.size();i++)

 {

  String k=(String)al.get(i);

  System.out.println(k);

 }

}

}

 

98.程序通訊和執行緒同步的方法?

答:程序通訊方法:

  1. 訊號(signal)
    2.管道(pipe):父子程序
    3.命名管道:mkfifo
    4.socket
    5.訊息佇列:msgget/msgctl/msgrcv/msgsnd
    6.共享記憶體:shmget/shmat/shmdt/shmctl
    7.訊號燈:semget/semctl/semop/
    8.鎖檔案(flock)

9. DDE是一種動態資料交換機制(Dynamic Data Exchange,DDE)。使用DDE通訊需要兩個Windows應用程式,其中一個作為伺服器處理資訊,另外一個作為客戶機從伺服器獲得資訊。客戶機應用程式向當前所啟用的伺服器應用程式傳送一條訊息請求資訊,伺服器應用程式根據該資訊作出應答,從而實現兩個程式之間的資料交換。

  1. 執行緒同步方法:
    1.互斥量Mutex
    2.訊號燈Semophore
    3.條件變數Conditions

 

99.面向物件的特徵有哪些方面?

面向物件的三個基本特徵是:封裝、繼承、多型。

封裝:封裝最好理解了。封裝是面向物件的特徵之一,是物件和類概念的主要特性。封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的資料和方法只讓可信的類或者物件操作,對不可信的進行資訊隱藏。

 

繼承:面向物件程式設計 (OOP) 語言的一個主要功能就是繼承。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴充套件。

通過繼承建立的新類稱為“子類”或“派生類”。

被繼承的類稱為基類父類超類

繼承的過程,就是從一般到特殊的過程。

要實現繼承,可以通過繼承Inheritance)和組合Composition)來實現。

在某些 OOP 語言中,一個子類可以繼承多個基類。但是一般情況下,一個子類只能有一個基類,要實現多重繼承,可以通過多級繼承來實現。

多型:多型性(polymorphisn)是允許你將父物件設定成為和一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作。簡單的說,就是一句話:允許將子類型別的指標賦值給父類型別的指標。

實現多型,有二種方式,覆蓋,過載。

覆蓋,是指子類重新定義父類的虛擬函式的做法。

過載,是指允許存在多個同名函式,而這些函式的引數表不同(或許引數個數不同,或許引數型別不同,或許兩者都不同)。

那麼,多型的作用是什麼呢?我們知道,封裝可以隱藏實現細節,使得程式碼模組化;繼承可以擴充套件已存在的程式碼模組(類);它們的目的都是為了——程式碼重用。而多型則是為了實現另一個目的——介面重用!多型的作用,就是為了類在繼承和派生的時候,保證使用家譜中任一類的例項的某一屬性時的正確呼叫

100.Context裡面主要包括什麼具體的東西?

答:Context提供了關於應用環境全域性資訊的介面。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用為特徵的資源和型別。同時啟動應用級的操作,如啟動Activity,broadcasting和接收intents。

101.簡述條形碼的掃描設計思路?

   生成步驟:

1、獲取需要生成圖形的資料

2、根據Zxing提供的圖形演算法進行圖片的生成

3、展示給使用者看

掃描步驟:

1、開啟掃描,通過startActivityforResult

2、初始化surfaceView、Camera、自定義View

3、開啟DecodeThread去獲取並解析自定義View裡的圖片,通過CaptureActivityHandler來處理解析結果

4、當解析成功,handler傳送回到主執行緒,執行返回上一個頁面將解析出來的值返回,通過setResult方法返回

102. ListView非同步載入圖片實現思路(優化篇)

非同步載入圖片基本思想:

1.先從記憶體快取中獲取圖片顯示(記憶體緩衝)

2.獲取不到的話從SD卡里獲取(SD卡緩衝)

3.都獲取不到的話從網路下載圖片並儲存到SD卡同時加入記憶體並顯示(視情況看是否要顯示)

OK,先上adapter的程式碼:

public class LoaderAdapter extends BaseAdapter{

private static final String TAG = "LoaderAdapter";

private boolean mBusy = false;

public void setFlagBusy(boolean busy) {

this.mBusy = busy;

}

private ImageLoader mImageLoader;

private int mCount;

private Context mContext;

private String[] urlArrays;

public LoaderAdapter(int count, Context context, String []url) {

this.mCount = count;

this.mContext = context;

urlArrays = url;

mImageLoader = new ImageLoader(context);

}

public ImageLoader getImageLoader(){

return mImageLoader;

}

@Override

public int getCount() {

return mCount;

}

@Override

public Object getItem(int position) {

return position;

}

@Override

public long getItemId(int position) {

return position;

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ViewHolder viewHolder = null;

if (convertView == null) {

convertView = LayoutInflater.from(mContext).inflate(

R.layout.list_item, null);

viewHolder = new ViewHolder();

viewHolder.mTextView = (TextView) convertView

.findViewById(R.id.tv_tips);

viewHolder.mImageView = (ImageView) convertView

.findViewById(R.id.iv_image);

convertView.setTag(viewHolder);

} else {

viewHolder = (ViewHolder) convertView.getTag();

}

String url = "";

url = urlArrays[position % urlArrays.length];

viewHolder.mImageView.setImageResource(R.drawable.ic_launcher);

if (!mBusy) {

mImageLoader.DisplayImage(url, viewHolder.mImageView, false);

viewHolder.mTextView.setText("--" + position

+ "--IDLE ||TOUCH_SCROLL");

} else {

mImageLoader.DisplayImage(url, viewHolder.mImageView, true);

viewHolder.mTextView.setText("--" + position + "--FLING");

}

return convertView;

}

static class ViewHolder {

TextView mTextView;

ImageView mImageView;

}

}

 

關鍵程式碼是ImageLoader的DisplayImage方法,再看ImageLoader的實現

public class ImageLoader {

private MemoryCache memoryCache = new MemoryCache();

private AbstractFileCache fileCache;

private Map<ImageView, String> imageViews = Collections

.synchronizedMap(new WeakHashMap<ImageView, String>());

// 執行緒池

private ExecutorService executorService;

public ImageLoader(Context context) {

fileCache = new FileCache(context);

executorService = Executors.newFixedThreadPool(5);

}

// 最主要的方法

public void DisplayImage(String url, ImageView imageView, boolean isLoadOnlyFromCache) {

imageViews.put(imageView, url);

// 先從記憶體快取中查詢

Bitmap bitmap = memoryCache.get(url);

if (bitmap != null)

imageView.setImageBitmap(bitmap);

else if (!isLoadOnlyFromCache){

// 若沒有的話則開啟新執行緒載入圖片

queuePhoto(url, imageView);

}

}

private void queuePhoto(String url, ImageView imageView) {

PhotoToLoad p = new PhotoToLoad(url, imageView);

executorService.submit(new PhotosLoader(p));

}

private Bitmap getBitmap(String url) {

File f = fileCache.getFile(url);

// 先從檔案快取中查詢是否有

Bitmap b = null;

if (f != null && f.exists()){

b = decodeFile(f);

}

if (b != null){

return b;

}

// 最後從指定的url中下載圖片

try {

Bitmap bitmap = null;

URL imageUrl = new URL(url);

HttpURLConnection conn = (HttpURLConnection) imageUrl

.openConnection();

conn.setConnectTimeout(30000);

conn.setReadTimeout(30000);

conn.setInstanceFollowRedirects(true);

InputStream is = conn.getInputStream();

OutputStream os = new FileOutputStream(f);

CopyStream(is, os);

os.close();

bitmap = decodeFile(f);

return bitmap;

} catch (Exception ex) {

Log.e("", "getBitmap catch Exception...\nmessage = " + ex.getMessage());

return null;

}

}

// decode這個圖片並且按比例縮放以減少記憶體消耗,虛擬機器對每張圖片的快取大小也是有限制的 

private Bitmap decodeFile(File f) {

try {

// decode image size

BitmapFactory.Options o = new BitmapFactory.Options();

o.inJustDecodeBounds = true;

BitmapFactory.decodeStream(new FileInputStream(f), null, o);

// Find the correct scale value. It should be the power of 2.

final int REQUIRED_SIZE = 100;

int width_tmp = o.outWidth, height_tmp = o.outHeight;

int scale = 1;

while (true) {

if (width_tmp / 2 < REQUIRED_SIZE

|| height_tmp / 2 < REQUIRED_SIZE)

break;

width_tmp /= 2;

height_tmp /= 2;

scale *= 2;

}

// decode with inSampleSize

BitmapFactory.Options o2 = new BitmapFactory.Options();

o2.inSampleSize = scale;

return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);

} catch (FileNotFoundException e) {

}

return null;

}

// Task for the queue

private class PhotoToLoad {

public String url;

public ImageView imageView;

public PhotoToLoad(String u, ImageView i) {

url = u;

imageView = i;

}

}

class PhotosLoader implements Runnable {

PhotoToLoad photoToLoad;

PhotosLoader(PhotoToLoad photoToLoad) {

this.photoToLoad = photoToLoad;

}

@Override

public void run() {

if (imageViewReused(photoToLoad))

return;

Bitmap bmp = getBitmap(photoToLoad.url);

memoryCache.put(photoToLoad.url, bmp);

if (imageViewReused(photoToLoad))

return;

BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad);

// 更新的操作放在UI執行緒中

Activity a = (Activity) photoToLoad.imageView.getContext();

a.runOnUiThread(bd);

}

}

/**

* 防止圖片錯位

* @param photoToLoad

* @return

*/

boolean imageViewReused(PhotoToLoad photoToLoad) {

String tag = imageViews.get(photoToLoad.imageView);

if (tag == null || !tag.equals(photoToLoad.url))

return true;

return false;

}

// 用於在UI執行緒中更新介面

class BitmapDisplayer implements Runnable {

Bitmap bitmap;

PhotoToLoad photoToLoad;

public BitmapDisplayer(Bitmap b, PhotoToLoad p) {

bitmap = b;

photoToLoad = p;

}

public void run() {

if (imageViewReused(photoToLoad))

return;

if (bitmap != null)

photoToLoad.imageView.setImageBitmap(bitmap);

}

}

public void clearCache() {

memoryCache.clear();

fileCache.clear();

}

public static void CopyStream(InputStream is, OutputStream os) {

final int buffer_size = 1024;

try {

byte[] bytes = new byte[buffer_size];

for (;;) {

int count = is.read(bytes, 0, buffer_size);

I f (count == -1)

break;

os.write(bytes, 0, count);

}

} catch (Exception ex) {

Log.e("", "CopyStream catch Exception...");

}

}

}

先從記憶體中載入,沒有則開啟執行緒從SD卡或網路中獲取,這裡注意從SD卡獲取圖片是放在子執行緒裡執行的,否則快速滑屏的話會不夠流暢,這是優化一。於此同時,在adapter裡有個busy變數,表示listview是否處於滑動狀態,如果是滑動狀態則僅從記憶體中獲取圖片,沒有的話無需再開啟執行緒去外存或網路獲取圖片,這是優化二。ImageLoader裡的執行緒使用了執行緒池,從而避免了過多執行緒頻繁建立和銷燬,有的童鞋每次總是new一個執行緒去執行這是非常不可取的,好一點的用的AsyncTask類,其實內部也是用到了執行緒池。在從網路獲取圖片時,先是將其儲存到sd卡,然後再載入到記憶體,這麼做的好處是在載入到記憶體時可以做個壓縮處理,以減少圖片所佔記憶體,這是優化三。

 

而圖片錯位問題的本質源於我們的listview使用了快取convertView,假設一種場景,一個listview一屏顯示九個item,那麼在拉出第十個item的時候,事實上該item是重複使用了第一個item,也就是說在第一個item從網路中下載圖片並最終要顯示的時候其實該item已經不在當前顯示區域內了,此時顯示的後果將是在可能在第十個item上輸出影象,這就導致了圖片錯位的問題。所以解決之道在於可見則顯示,不可見則不顯示。在ImageLoader裡有個imageViews的map物件,就是用於儲存當前顯示區域影象對應的url集,在顯示前判斷處理一下即可。

下面再說下記憶體緩衝機制,本例採用的是LRU演算法,先看看MemoryCache的實現

複製程式碼程式碼如下:

public class MemoryCache {

private static final String TAG = "MemoryCache";

// 放入快取時是個同步操作

// LinkedHashMap構造方法的最後一個引數true代表這個map裡的元素將按照最近使用次數由少到多排列,即LRU

// 這樣的好處是如果要將快取中的元素替換,則先遍歷出最近最少使用的元素來替換以提高效率

private Map<String, Bitmap> cache = Collections

.synchronizedMap(new LinkedHashMap<String, Bitmap>(10, 1.5f, true));

// 快取中圖片所佔用的位元組,初始0,將通過此變數嚴格控制快取所佔用的堆記憶體

private long size = 0;// current allocated size

// 快取只能佔用的最大堆記憶體

private long limit = 1000000;// max memory in bytes

public MemoryCache() {

// use 25% of available heap size

setLimit(Runtime.getRuntime().maxMemory() / 10);

}

public void setLimit(long new_limit) {

limit = new_limit;

Log.i(TAG, "MemoryCache will use up to " + limit / 1024. / 1024. + "MB");

}

public Bitmap get(String id) {

try {

if (!cache.containsKey(id))

return null;

return cache.get(id);

} catch (NullPointerException ex) {

return null;

}

}

public void put(String id, Bitmap bitmap) {

try {

if (cache.containsKey(id))

size -= getSizeInBytes(cache.get(id));

cache.put(id, bitmap);

size += getSizeInBytes(bitmap);

checkSize();

} catch (Throwable th) {

th.printStackTrace();

}

}

/** 嚴格控制堆記憶體,如果超過將首先替換最近最少使用的那個圖片快取 */

private void checkSize() {

Log.i(TAG, "cache size=" + size + " length=" + cache.size());

if (size > limit) {

// 先遍歷最近最少使用的元素

Iterator<Entry<String, Bitmap>> iter = cache.entrySet().iterator();

while (iter.hasNext()) {

Entry<String, Bitmap> entry = iter.next();

size -= getSizeInBytes(entry.getValue());

iter.remove();

if (size <= limit)

break;

}

Log.i(TAG, "Clean cache. New size " + cache.size());

}

}

public void clear() {

cache.clear();

}

/**

* 圖片佔用的記憶體 */

long getSizeInBytes(Bitmap bitmap) {

if (bitmap == null)

return 0;

return bitmap.getRowBytes() * bitmap.getHeight();

}

}

首先限制記憶體圖片緩衝的堆記憶體大小,每次有圖片往快取里加時判斷是否超過限制大小,超過的話就從中取出最少使用的圖片並將其移除,當然這裡如果不採用這種方式,換做軟引用也是可行的,二者目的皆是最大程度的利用已存在於記憶體中的圖片快取,避免重複製造垃圾增加GC負擔,OOM溢位往往皆因記憶體瞬時大量增加而垃圾回收不及時造成的。只不過二者區別在於LinkedHashMap裡的圖片快取在沒有移除出去之前是不會被GC回收的,而SoftReference裡的圖片快取在沒有其他引用儲存時隨時都會被GC回收。所以在使用LinkedHashMap這種LRU演算法快取更有利於圖片的有效命中,當然二者配合使用的話效果更佳,即從LinkedHashMap裡移除出的快取放到SoftReference裡,這就是記憶體的二級快取,

 

103. android下大檔案分割上傳 

1.由於android自身的原因,對大檔案(如影視訊檔案)的操作很容易造成OOM,即:Dalvik堆記憶體溢位,利用檔案分割將大檔案分割為小檔案可以解決問題。

 

•檔案分割後分多次請求服務。

  //檔案分割上傳    

 public  void cutFileUpload(String fileType,String filePath){

         try{

              FileAccessI fileAccessI = new FileAccessI(filePath, 0);

              Long nStartPos = 0l;

              Long length = fileAccessI.getFileLength();

              int mBufferSize = 1024 * 100; //每次處理1024 * 100位元組

            byte[] buffer = new byte[mBufferSize];

            FileAccessI.Detail detail;

             long nRead = 0l;

           String vedioFileName = Usual.f_getUUID(); //分配一個檔名

           long nStart = nStartPos;

             int i = 0;

             while (nStart < length)

            {

                detail = fileAccessI.getContent(nStart);

                 nRead = detail.length;

                 buffer = detail.b;

                 JSONObject mInDataJson = new JSONObject();

                 mInDataJson.put("a", "282");

                mInDataJson.put("FileName", vedioFileName);

                mInDataJson.put("start", nStart); //服務端獲取開始文章進行寫檔案

                 mInDataJson.put("filetype", fileType);

                 nStart += nRead;

                 nStartPos = nStart;

               String url = UsualA.f_getXmlSOAUrl(UsualA.mServiceFastByteUrl, "n.uploadvedio", mInDataJson.toString(),

                        "282");

                 HttpFastUtil.f_httpPostByte(url, buffer, false);

             }

         }

        catch (Exception e)

        {         }

•檔案分割類

import java.io.*;

 public class FileAccessI implements Serializable {

     RandomAccessFile oSavedFile;

      long nPos;

  public FileAccessI() throws IOException

     {

         this("", 0);

     }

     public FileAccessI(String sName, long nPos) throws IOException

     {

         oSavedFile = new RandomAccessFile(sName, "rw");//建立一個隨機訪問檔案類,可讀寫模式

         this.nPos = nPos;

         oSavedFile.seek(nPos);

     }

     public synchronized int write(byte[] b, int nStart, int nLen)

     {

         int n = -1;

         try

         {

             oSavedFile.write(b, nStart, nLen);

             n = nLen;

         }

         catch (IOException e)

         {

            e.printStackTrace();

         }

         return n;

     }

     //每次讀取102400位元組

     public synchronized Detail getContent(long nStart)

     {

         Detail detail = new Detail();

         detail.b = new byte[102400];

         try

         {

            oSavedFile.seek(nStart);

            detail.length = oSavedFile.read(detail.b);

         }

         catch (IOException e)

         {

             e.printStackTrace();

        }

         return detail;

     }

     public class Detail

     {

   public byte[] b;

   public int length;

     }

  //獲取檔案長度

     public long getFileLength()

     {

         Long length = 0l;

         try

        {

             length = oSavedFile.length();

         }

         catch (IOException e)

         {

             // TODO Auto-generated catch block

             e.printStackTrace();

         }

         return length;

     }

 }

 

•服務端獲得分割的檔案,利用RandomAccessFile進行檔案整理  

/**

       * 音視訊圖片處理

       * @param mStr

       * @return

       * @throws Exception

       */

      public static String f_uploadVedio(String mStr) throws Exception

      {

          String mResult = Usual.mEmpty;

         String fileType = Usual.mEmpty;

         int startPosL = 0;

         RandomAccessFile oSavedFile = null;

         JSONObject jsonObject = new JSONObject(mStr);

         String vedioJsonStr = jsonObject.getString("VEDIO");

         byte[] vedioBytes = Usual.f_fromBase64String(vedioJsonStr);

         startPosL = (Integer) jsonObject.get("start"); //接收客戶端的開始位置(檔案讀取到的  位元組大小)

         fileType = (String)jsonObject.getString("filetype");

         String fileName = (String)jsonObject.getString("FileName");

         if(fileType.equals("picture"))

         {

             oSavedFile = new RandomAccessFile("E:\\"+fileName+".jpg","rw");

         }

         else if(fileType.equals("photo"))

         {

            oSavedFile = new RandomAccessFile("E:\\"+fileName+".jpg","rw");

         }

         else if(fileType.equals("voice"))

          {

             oSavedFile = new RandomAccessFile("E:\\"+fileName+".mp3","rw");

         }

         else if(fileType.equals("video"))

         {

             oSavedFile = new RandomAccessFile("E:\\"+fileName+".mp4", "rw");

         }

         //設定標誌位,標誌檔案儲存的位置

         oSavedFile.seek(startPosL);

         oSavedFile.write(vedioBytes);

         oSavedFile.close();

         mResult = "000";

         return mResult;

     }

104.  android listview 非同步載入圖片並防止錯位

網上找了一張圖, listview 非同步載入圖片之所以錯位的根本原因是重用了 convertView 且有非同步操作.如果不重用 convertView 不會出現錯位現象, 重用 convertView 但沒有非同步操作也不會有問題。

我簡單分析一下:

當重用 convertView 時,最初一屏顯示 7 條記錄, getView 被呼叫 7 次,建立了 7 個 convertView.當 Item1 劃出螢幕, Item8 進入螢幕時,這時沒有為 Item8 建立新的 view 例項, Item8 複用的是Item1 的 view 如果沒有非同步不會有任何問題,雖然 Item8 和 Item1 指向的是同一個 view,但滑到Item8 時刷上了 Item8 的資料,這時 Item1 的資料和 Item8 是一樣的,因為它們指向的是同一塊記憶體,但 Item1 已滾出了螢幕你看不見。當 Item1 再次可見時這塊 view 又刷上了 Item8 的資料。

但當有非同步下載時就有問題了,假設 Item1 的圖片下載的比較慢,Item8 的圖片下載的比較快,你滾上去使 Item8 可見,這時 Item8 先顯示它自己下載的圖片沒錯,但等到 Item1 的圖片也下載完時你發現Item8 的圖片也變成了 Item1 的圖片,因為它們複用的是同一個 view。 如果 Item1 的圖片下載的比Item8 的圖片快, Item1 先刷上自己下載的圖片,這時你滑下去,Item8 的圖片還沒下載完, Item8會先顯示 Item1 的圖片,因為它們是同一快記憶體,當 Item8 自己的圖片下載完後 Item8 的圖片又刷成了自己的,你再滑上去使 Item1 可見, Item1 的圖片也會和 Item8 的圖片是一樣的,

因為它們指向的是同一塊記憶體。

最簡單的解決方法就是網上說的,給 ImageView 設定一個 tag, 並預設一個圖片

當 Item1 比 Item8 圖片下載的快時, 你滾下去使 Item8 可見,這時 ImageView 的 tag 被設成了

Item8 的 URL, 當 Item1 下載完時,由於 Item1 不可見現在的 tag 是 Item8 的 URL,所以不滿足條件,雖然下載下來了但不會設定到 ImageView 上, tag 標識的永遠是可見 view 中圖片的 URL。

關鍵程式碼如下: 

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;

    if (convertView == null) {
        holder = new ViewHolder();
        convertView = LayoutInflater.from(context).inflate(
                R.layout.list_item, null);
        holder.img = (ImageView) convertView.findViewById(R.id.userimage);

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    User user = list.get(position);

    // 給 ImageView 設定一個 tag
    holder.img.setTag(user.getImgUrl());
    // 預設一個圖片
    holder.img.setImageResource(R.drawable.ic_launcher);

    final String tmpImageUrl = user.getImgUrl();

    if (user.getImgUrl() != null && !user.getImgUrl().equals("")) {
        Bitmap bitmap = imageLoader.loadImage(holder.img, user.getImgUrl(),
                new ImageDownloadCallBack() {

                    @Override
                    public void onImageDownloaded(ImageView imageView, Bitmap bitmap) {
                        // 通過 tag 來防止圖片錯位
                        if (imageView.getTag() != null && 
			imageView.getTag().equals(tmpImageUrl)) { //非user.getImgUrl()
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                });

        if (bitmap != null) {
            holder.img.setImageBitmap(bitmap);
        }
    }

    return convertView;
}

我參考網上資料寫了一個 listview 非同步載入圖片的 DEMO:

(1) 使用執行緒池

 沒有執行緒池,當圖片非常多,快速滑動  listview 時由於下載每個圖片都開了一個執行緒,

 可能出現 OOM (out of memory)。

(2) 記憶體、檔案雙快取

 這裡也使用 SoftReference 軟引用  

**
 * 圖片非同步載入類
 * 
 * @author Leslie.Fang
 * @company EnwaySoft
 */
public class AsyncImageLoader {
    // 最大執行緒數
    private static final int MAX_THREAD_NUM = 10;
    private Map<String, SoftReference<Bitmap>> imageCaches = null;
    private FileUtil fileUtil;
   
 private ExecutorService threadPools = null; // 執行緒池

    public AsyncImageLoader(Context context) {
        imageCaches = new HashMap<String, SoftReference<Bitmap>>();
        fileUtil = new FileUtil(context);
    }

    public Bitmap loadImage(final ImageView imageView, final String imageUrl,
            final ImageDownloadCallBack imageDownloadCallBack) {
        final String filename = imageUrl
                .substring(imageUrl.lastIndexOf("/") + 1);
        final String filepath = fileUtil.getAbsolutePath() + "/" + filename;

        // 先從軟引用中找
        if (imageCaches.containsKey(imageUrl)) {
            SoftReference<Bitmap> reference = imageCaches.get(imageUrl);
            Bitmap bitmap = reference.get();

            // 軟引用中的 Bitmap 物件可能隨時被回收
            // 如果軟引用中的 Bitmap 已被回收,則從檔案中找
            if (bitmap != null) {
                Log.i("aaaa", "cache exists " + filename);

                return bitmap;
            }
        }

        // 從檔案中找
        if (fileUtil.isBitmapExists(filename)) {
            Log.i("aaaa", "file exists " + filename);
            Bitmap bitmap = BitmapFactory.decodeFile(filepath);

            // 重新加入到記憶體軟引用中
            imageCaches.put(imageUrl, new SoftReference<Bitmap>(bitmap));

            return bitmap;
        }

        // 軟引用和檔案中都沒有再從網路下載
        if (imageUrl != null && !imageUrl.equals("")) {
            if (threadPools == null) {
                threadPools = Executors.newFixedThreadPool(MAX_THREAD_NUM);
            }

            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    if (msg.what == 111 && imageDownloadCallBack != null) {
                        Bitmap bitmap = (Bitmap) msg.obj;
                        imageDownloadCallBack.onImageDownloaded(imageView,
                                bitmap);
                    }
                }
            };

            Thread thread = new Thread() {
                @Override
                public void run() {
                    Log.i("aaaa", Thread.currentThread().getName()
                            + " is running");
                    InputStream inputStream = HTTPService.getInstance()
                            .getStream(imageUrl);
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

                    // 圖片下載成功重新快取並執行回撥重新整理介面
                    if (bitmap != null) {
                        // 加入到軟引用中
                        imageCaches.put(imageUrl, new SoftReference<Bitmap>(
                                bitmap));
                        // 快取到檔案系統
                        fileUtil.saveBitmap(filepath, bitmap);

                        Message msg = new Message();
                        msg.what = 111;
                        msg.obj = bitmap;
                        handler.sendMessage(msg);
                    }
                }
            };

            threadPools.execute(thread);
        }

        return null;
    }

    public void shutDownThreadPool() {
        if (threadPools != null) {
            threadPools.shutdown();
            threadPools = null;
        }
    }

    /**
     * 圖片下載完成回撥介面
     * 
     */
    public interface ImageDownloadCallBack {
        void onImageDownloaded(ImageView imageView, Bitmap bitmap);
    }
}

五篇部落格,104道Android面試題,希望可以幫到大家。