Android 常見面試 知識點小結

前言

根據筆者自己的閱讀以及項目經驗總結而言,不同於網上的copy來copy去。很多內容加上了自己的理解,難免有錯誤不當之處,煩請指出。

Handler

在子線程裏面創建 Handler 對象會拋出異常Can't create handler inside thread that has not called Looper.prepare()

但是加上Looper.prepare();不會拋出異常, 這個因爲 Handler 對應一個 Looper,一個 Looper 對應一個線程。

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        handler2 = new Handler();
    }
}).start();

用 ThreadLocal 保存 Looper 對象
ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

通過Looper.prepare()來創建Looper對象(消息隊列封裝在Looper對象中),並且保存在sThreadLoal中,然後通過Looper.loop()來執行消息循環,這兩步通常是成對出現的!!

xml解析

個人理解:
Dom把xml全部加載,比較耗內存。
Sax是事件驅動,但是不支持隨機讀取,不能只處理部分元素。
Pull是Sax的改進,可以提前結束讀取,Sax不能提前結束。

基本的解析方式包含兩種,一種是事件驅動的(代表SAX),另一種方式是基於文檔結構(代表DOM)。其他的只不過語法不一樣而已。

Dom 方式解析xml,比較簡單,並可以訪問兄弟元素,但是需要將整個xml文檔加載到內存中,對於android設備來說,不推薦使用dom的方式解析xml。

//    接收一個xml的字符串來解析xml,Document代表整個xml文檔
Document document = builder.parse(inputStream);
//    得到xml文檔的根元素節點
Element personsElement = document.getDocumentElement();
//    得到標籤爲person的Node對象的集合NodeList
NodeList nodeList = personsElement.getElementsByTagName("person");

Dom解析加載整個xml文檔的樹形結構,可以隨時訪問兄弟節點,父節點等。Sax只能向前遍歷(單遍解析),使它不能支持隨機訪問。

sax和pull都是基於事件驅動的xml解析器,在解析xml時並不會加載整個的xml文檔,佔用內存較少,因此在android開發中建議使用sax或者pull來解析xml文檔。

SAX,全稱Simple API for XML,既是一種接口,也是一種軟件包。它是一種XML解析的替代方法。SAX不同於DOM解析,它逐行掃描文檔,一邊掃描一邊解析。由於應用程序只是在讀取數據時檢查數據,因此不需要將數據存儲在內存中,這對於大型文檔的解析是個巨大優勢。

繼承 extends DefaultHandler 需要複寫下面幾個方法。這幾個方法是回調方法,解析時,若發生事件(文檔開頭結尾 元素開頭結尾)的話會被調用這些回調方法。

  • startDocument() and endDocument() – Method called at the start and end of an XML document.
  • startElement() and endElement() – Method called at the start and end of a document element.
  • characters() – Method called with the text contents in between the start and end tags of an XML document element.

pull和sax的區別

Pull解析器和SAX解析器雖有區別但也有相似性。他們的區別爲:SAX解析器的工作方式是自動將事件推入註冊的事件處理器進行處理,因此你不能控制事件的處理主動結束;

而Pull解析器的工作方式爲允許你的應用程序代碼主動從解析器中獲取事件,正因爲是主動獲取事件,因此可以在滿足了需要的條件後不再獲取事件,結束解析。這是他們主要的區別。

Android 系統架構

  1. Linux 內核層,爲 Android 設備提供了底層的驅動,如藍牙驅動、照相機驅動等等
  2. 系統運行庫層,這些曾通過一些 C/C++ 庫爲 Android 系統提供了主要的特性支持,如 SQlite 提宮數據庫,Webkit 提高了瀏覽器內核
    需要注意的是這一層也有 Android 運行時候需要的核心庫
    Android 運行時庫包括了 Dalvik 虛擬機
  3. 應用框架層,這一層提供了編寫 App 時候需要用到的 Api
  4. 應用層,所有安裝在手機上的 App 都屬於這一層,包括短信、QQ 等

四大組件

activity和service默認是運行在應用進程的主線程中,四大組件默認都是和activity運行在同一個主線程中的

  • Activity 活動
  • Service 服務
    Service的Oncreate()方法只有第一次執行,而OnStartCommand()每次啓動服務都會調用
  • Broadcast 廣播接收器
    最常用的廣播就是android.provider.Telephony.SMS_RECEIVED
// AndroidManifest.xml
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
<application ...>
    <receiver android:name=".SMSReceiver"> 
      <intent-filter> 
        <action android:name="android.provider.Telephony.SMS_RECEIVED" /> 
      </intent-filter> 
    </receiver>  
</application>

// SMSReceiver.java
public class SMSReceiver extends BroadcastReceiver 
{ 
    @Override 
    public void onReceive(Context context, Intent intent) { 
        Log.i(TAG, "SMS received.");
        ....
    }
}
  • ContentProvider 內容提供者

    注意如果自己定義一個ContentProvider暴露爲其他 App 使用,通常用SQLite或者文件實現,也可以用 Json 或者 Xml 實現等等。


4大組件都需要在 xml 文件裏註冊,注意它們是平級的關係。

存儲化技術

  • 文件存儲 存儲一些圖片、視頻、doc 等等,保密性不強
  • SharedPreference 多用於存儲用戶配置、密碼等等
    也可在裏面存儲視頻當前播放的位置,按home鍵返回後重新從當前位置讀取
  • 數據庫 Sqlite 存儲一些格式複雜的數據,比如短信中 聯繫人和對應的短信記錄

文件的操作模式

  • MODE_APPEND 追加內容
  • MODE_PRIVATE 覆蓋文件中的內容
  • MODE_WORLD_READABLE 已經在4.2廢棄
  • MODE_WORLD_WRITEABLE 已經在4.2廢棄

打開文件示例代碼

FileOutputStream out  = null;
BufferedWriter writer = null;
try {
    out = openFileOutput("data", Context.MODE_PRIVATE);
    writer = new BufferedWriter(new OutputStreamWriter(out));

} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    try {
        if (writer != null)
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

讀取文件 代碼示例

FileInputStream in = null;
 BufferedReader reader = null;
 StringBuilder content = null;

 try {
     in = openFileInput("data");
     reader = new BufferedReader(new InputStreamReader(in));
     String line = "";
     if ((line = reader.readLine()) != null) {
         content.append(line);
     }
 } catch (FileNotFoundException e) {
     e.printStackTrace();
 } catch (IOException e) {
     e.printStackTrace();
 } finally {
     try {
         if(reader != null)
         reader.close();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }

TestUtils

String x = "test";
TextUtils.isEmpty(x);

這兒的 isEmpty 方法可以判斷 x 爲 null 或者長度爲 0

SQlite

使用adb shell中的sqlite3打開數據庫

getWritableDatabase VS getReadableDatabase

getWritableDatabasegetReadableDatabase 取得的實例對數據庫進行讀和寫的功能,不是像字面意思上一個讀寫權限和只讀權限

兩者的區別在於
- getWritableDatabase取得的實例是以讀寫的方式打開數據庫,如果打開的數據庫磁盤滿了,此時只能讀不能寫,此時調用了getWritableDatabase的實例,那麼將會發生錯誤(異常)
- getReadableDatabase取得的實例是先調用getWritableDatabase以讀寫的方式打開數據庫,如果數據庫的磁盤滿了,此時返回打開失敗,繼而用getReadableDatabase的實例以只讀的方式去打開數據庫

intent-filter

intent-filter 可以動態註冊也可以靜態註冊
下面是靜態註冊的代碼,直接寫在xml文件裏

<application>
    <activity name=""/>
    <receiver android:name=".MyBroadcastReceiver">
        <!-- intent過濾器,指定可以匹配哪些intent, 一般需要定義action 可以是自定義的也可是系統的 --> 
        <intent-filter>
            <action android:name="com.app.bc.test"/>
        </intent-filter>
    </receiver>
</application>

//
// java code 這兒的intent也叫隱式intent
Intent intent = new Intent(“com.app.bc.test”);
sendBroadcast(intent);//發送廣播事件

動態註冊

//生成一個BroadcastReceiver對象
SMSReceiver smsReceiver = new SMSReceiver();
//生成一個IntentFilter對象
IntentFilter filter = new IntentFilter();       
filter.addAction(“android.provider.Telephony.SMS_RECEIVED”);
//將BroadcastReceiver對象註冊到系統當中
//此處表示該接收器會處理短信事件
TestBC1Activity.this.registerReceiver(smsReceiver, filter); 
  • 靜態註冊:在AndroidManifest.xml註冊,android不能自動銷燬廣播接收器,也就是說當應用程序關閉後,還是會接收廣播。
  • 動態註冊:在代碼中通過registerReceiver()手工註冊.當程序關閉時,該接收器也會隨之銷燬。當然,也可手工調用unregisterReceiver()進行銷燬。

顯示intent 和隱式intent

顯示

Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent);  

隱式,它會自動尋找能處理 intent-filter 裏面設置了處理該的 action 的Activity。

Intent intent = new Intent();
intent.setAction(“android.provider.Telephony.SMS_RECEIVED”);
startActivity(intent);

比如有手機裝了多個瀏覽器,使用隱式intent會彈出讓用戶選擇哪個瀏覽器。

Timer 和 Alarm

Timer 不適用需要長期在後臺運行的定時任務,長時間不操作,CPU會睡眠狀態,這會影響Timer的定時任務無法執行
Alarm 機制具有喚醒CPU的功能

工具方法

訪問Url,利用get請求,請返回的數據轉化為字符串

HttpURLConnection connection = null;
...
connection.setReadTimeOut(8000);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line = "";
while( (line = reader.readLine()) !=null ) {
 response.append(line);
}

怎麼更新UI

注意不能使用子線程更新 UI,可以用 Handler 或者 Activity.runOnUiThread

public final void runOnUiThread (Runnable action)

Added in API level 1
Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.

Parameters
action  the action to run on the UI thread

Adapter 4個重寫方法

BaseAdapter基本結構

  • public int getCount() :適配器中數據集中 數據的個數,即ListView需要顯示的數據個數
  • public Object getItem(int position) : 獲取數據集中與指定索引對應的數據項
  • public long getItemId(int position) : 獲取指定行對應的ID
  • public View getView(int position, View convertView, ViewGroup parent) :獲取每一個Item的顯示內容
public class MyBaseAdapter extends BaseAdapter {

    ArrayList myList = new ArrayList();
    LayoutInflater inflater;
    Context context;


    public MyBaseAdapter(Context context, ArrayList myList) {
        this.myList = myList;
        this.context = context;
        inflater = LayoutInflater.from(this.context);
    }

    @Override
    public int getCount() {
        return myList.size();
    }

    @Override
    public ListData getItem(int position) {
        return myList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyViewHolder mViewHolder;

        if (convertView == null) {
            convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
            mViewHolder = new MyViewHolder(convertView);
            convertView.setTag(mViewHolder);
        } else {
            mViewHolder = (MyViewHolder) convertView.getTag();
        }

        ListData currentListData = getItem(position);

        mViewHolder.tvTitle.setText(currentListData.getTitle());
        mViewHolder.tvDesc.setText(currentListData.getDescription());
        mViewHolder.ivIcon.setImageResource(currentListData.getImgResId());

        return convertView;
    }

    private class MyViewHolder {
        TextView tvTitle, tvDesc;
        ImageView ivIcon;

        public MyViewHolder(View item) {
            tvTitle = (TextView) item.findViewById(R.id.tvTitle);
            tvDesc = (TextView) item.findViewById(R.id.tvDesc);
            ivIcon = (ImageView) item.findViewById(R.id.ivIcon);
        }
    }
}

在使用BaseAdapter時,我們需要自己創建一個類繼承BaseAdapter,然後Eclipse會提醒我們實現上述四個方法,當給ListView設置了我們自己寫的Adapter後,ListView內部會調用上述四個方法。

參考文章

【Android】BroadCast廣播機制應用與實例

ListView using BaseAdapter – Android http://www.pcsalt.com/android/listview-using-baseadapter-android/

發佈了198 篇原創文章 · 獲贊 179 · 訪問量 91萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章