溫故而知新——Android基本知識(2016-08-22)


一、 Linux 常用指令

mkdir 創建文件夾
rmdir 刪除文件夾
rm 刪除文件
mv 移動文件
cp 拷貝文件
cat 查看文件
tail 查看文件尾部
more 分頁查看文件
cd 切換當前目錄
ls 列出文件清單
reboot 重啓
date 顯示日期
cal 顯示日曆
ps 查看系統進程相當於 windows 的任務管理器
ifconfig 配置網絡


學習網址:初窺Linux 之 我最常用的20條命令


2、書寫出 android 工程的目錄結構

src 源文件
gen 生成的文件 R 文件就在此
android. jar 依賴的 android sdk
assets 資源文件
bin 生成的字節碼 apk 在此
libs 依賴 jar 和 so
res 資源文件
drawable
drawable-hdpi
layout
menu
values
AndroidManifest.xml
project.properties


3、 什麼是 ANR 如何避免它?

ANR分類?

ANR 產生原因? 主線程有哪些? 非主線程有哪些?

如何避免ANR ? 

參考:Android ANR產生原因和解決辦法


5、 一條最長的短信息約佔多少 byte?

中文 70 (包括標點),英文 160, 160 個字節。

衆所周知,一條短信是可以輸入70箇中文字符的。一個字符是2個字節(Byte)。一個字節是8位(bit).所以通用的計量是 一條短信可以輸入70*2*8=1120 bit
一個ASCII碼是7bit(128個字符的標準ASCII碼),如果你輸入的是標準ASCII碼的話你,就可以在一條短信中輸入1120/7=160個ASCII字符。


發送短信的方法,如何發送長短信,羣發短信。

參考:android發送短信的兩種方式,發送長短信的兩種方式,羣發短信


7、 如何判斷是否有 SD 卡?

通過如下方法:
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
如果返回 true 就是有 sdcard,如果返回 false 則沒有。

注意:

getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir() 區別和使用

參考:getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir() 解析


啓動應用後,改變系統語言,應用的語言會改變麼?


這個一般是不會改變的,一般需要重啓應用才能改變應用語言。

如果應用做了國際化處理,則改變系統語言,應用語言會改變。但是如果沒有做國際化處理,系統語言再怎麼變,應用語言也不會變。


ddms 和 traceview 的區別
簡單的說 ddms 是一個程序執行查看器,在裏面可以看見線程和堆棧等信息,traceView 是程序性能分析器。


 Activity 生命週期


Activity 從創建到銷燬有多種狀態,從一種狀態到另一種狀態時會激發相應的回調方法,這些回調方法包括:
onCreate   onStart  onResume  onPause  onStop  onDestroy

其實這些方法都是兩兩對應的,onCreate 創建與 onDestroy 銷燬;
onStart 可見與 onStop 不可見;onResume 可編輯(即焦點)與 onPause;

這 6 個方法是相對應的,那麼就只剩下一個 onRestart 方法了,這個方法在什麼時候調用呢?
答案就是:在 Activity 被 onStop 後,但是沒有被 onDestroy,在再次啓動此 Activity 時就調用 onRestart(而不再調用 onCreate)方法;
如果被 onDestroy 了,則是調用 onCreate 方法。


Activity異常情況下的生命週期分析 

情況1:資源相關的系統配置發生改變導致activity被殺死並重新創建

比如旋轉屏幕

此時生命週期如下圖所示:




其 OnPasuse 、onStop 、onDestroy 方法均被調用。onSaveInstanceState 方法會在onStop之前執行,它和onPause沒有特定順序,可前可後。

Activity 調用onSaveInstanceState 保存數據,然後Activity委託window去保存,接着window再委託頂級容器保存數據。DecorView再去通知子View保存數據。

基本的保存activity狀態代碼示例:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (null != savedInstanceState) { //注意要判空
			String test = savedInstanceState.getString("extra_test");
			Log.d(TAG, "[onCreate]restore extra_test:" + test);
		}
    }
    @Override
    protected void onSaveInstanceState(Bundle outState) {
    	super.onSaveInstanceState(outState);
    	Log.d(TAG, "onSaveInstanceState" );
    	outState.putString("extra_test", "test");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
    	super.onRestoreInstanceState(savedInstanceState);
    	String test = savedInstanceState.getString("extra_test");//不需要判空,如果走這個方法,則savedInstanceState一定不爲空。建議用這個方法恢復數據。
		Log.d(TAG, "[onRestoreInstanceState]restore extra_test:" + test);
    	
    }


1、 不設置 Activity 的 android:configChanges 時,切屏會重新調用各個生命週期 默認首先銷燬當前 activity,然後重新加載。


2、 設置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize"時,切屏不會重新調用各個生命週期,只會執行onConfigurationChanged 方法。


情況2:資源內存不足導致低優先級的activity被殺死



兩個 Activity 之間跳轉時必然會執行的是哪幾個方法?

一般情況下比如說有兩個 activity,分別叫 A,B,當在 A 裏面激活 B 組件的時候, A 會調用 onPause()方法,然後 B 調用 onCreate() ,onStart(), onResume()。
這個時候 B 覆蓋了窗體, A 會調用 onStop()方法. 如果 B 是個透明的,或者是對話框的樣式, 就不會調用 A 的onStop()方法。


如何退出 Activity?如何安全退出已調用多個 Activity 的 Application?


1、通常情況用戶退出一個 Activity 只需按返回鍵,我們寫代碼想退出 activity 直接調用 finish()方法就行。


2、記錄打開的 Activity:(自定義Activity任務棧)
每打開一個 Activity,就記錄下來。在需要退出時,關閉每一個 Activity 即可。


//僞代碼
List<Activity> lists ;// 在 application 全局的變量裏面
lists = new ArrayList<Activity>();
lists.add(this);
for(Activity activity: lists)
{
activity.finish();
}
lists.remove(this);


3、發送特定廣播:
在需要結束應用時,發送一個特定的廣播,每個 Activity 收到廣播後,關閉即可。
//給某個 activity 註冊接受接受廣播的意圖
registerReceiver(receiver, filter)
//如果過接受到的是 關閉 activity 的廣播 就調用 finish()方法 把當前的 activity finish()掉


4、遞歸退出
在打開新的 Activity 時使用 startActivityForResult,然後自己加標誌,在 onActivityResult 中處理,遞歸關閉


5、其實 也可以通過 intent 的 flag 來實現intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一個新的 activity。此時如果該任務棧中已經有該 Activity,那麼系統會把這個 Activity 上面的所有 Activity 幹掉。其實相當於給 Activity 配置的啓動模式爲 SingleTop。

	Intent intent = new Intent(Intent.ACTION_MAIN);
				intent.addCategory(Intent.CATEGORY_HOME);
				intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
				startActivity(intent);
				android.os.Process.killProcess(Process.myPid());

Service 是否在 main thread 中執行, service 裏面是否能執行耗時的操作?

默認情況,如果沒有顯示的指 servic 所運行的進程, Service 和 activity 是運行在當前 app 所在進程的 main  thread(UI 主線程)裏面。


service 裏面不能執行耗時的操作(網絡請求,拷貝數據庫,大文件 )


特殊情況 ,可以在清單文件配置 service 執行所在的進程 ,讓 service 在另外的進程中執行
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>


IntentService


IntentService 是一種特殊的Service ,它繼承了Service,並且有一個抽象類。

IntentService 封裝了HandlerThread 和 Handler ,從源碼可以看出

@Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
 @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }



Service 的缺點:

Service 不會專門啓動一條單獨的進程,Service 與它所在應用位於同一個進程中即UI線程;
Service 也不是專門一條新線程,因此不應該在 Service 中直接處理耗時的任務;


IntentService有以下特點:

(1)  它創建了一個獨立的工作線程來處理所有的通過onStartCommand()傳遞給服務的intents。

(2)  創建了一個工作隊列,來逐個發送intent給onHandleIntent()。

(3)  不需要主動調用stopSelft()來結束服務。因爲,在所有的intent被處理完後,系統會自動關閉服務。

(4)  默認實現的onBind()返回null

(5)  默認實現的onStartCommand()的目的是將intent插入到工作隊列中


使用方法及詳解 參考:Android IntentService完全解析 當Service遇到Handler


Service 裏面可以彈Toast嗎?

可以的。彈吐司有個條件就是得有一個 Context 上下文,而 Service 本身就是 Context 的子類,因此在 Service裏面彈吐司是完全可以的。比如我們在 Service 中完成下載任務後可以彈一個吐司通知用戶。


BroadCastReceiver

廣播分兩種:有序廣播和無序廣播。
內部通信實現機制:通過 Android 系統的 Binder 機制實現通信。

無序廣播:完全異步,邏輯上可以被任何廣播接收者接收到。優點是效率較高。缺點是一個接收者不能將處理結果傳遞給下一個接收者,並無法終止廣播 intent 的傳播。

有序廣播:按照被接收者的優先級順序,在被接收者中依次傳播。比如有三個廣播接收者 A,B,C,優先級是 A >B > C。那這個消息先傳給 A,再傳給 B,最後傳給 C。每個接收者有權終止廣播,比如 B 終止廣播,C 就無法接收到。此外 A 接收到廣播後可以對結果對象進行操作,當廣播傳給 B 時,B 可以從結果對象中取得 A 存入的數據。

有序廣播的接收者們將按照事先生命的優先級依次接收,數越大優先級越高(取值範圍:-1000~10000),優先級可以聲明在<intent-filter android:priority="n".../>,也可以調用IntentFilter對象的setPriority設置。並且接收者可以終止傳播(調用abortBroadcast()方法即可終止),一旦終止後面接收者就無法接受廣播。另外,接受者可以將處理結果存入數據(可通過setResultExtras(Bundle)方法將數據存入Broadcast),當做Broadcast再傳遞給下一級接收者(可通過代碼Bundle bundle = getResultExtras(true)獲取上一級傳遞過來的數據)。

在通過 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)時

我們可以指定 resultReceiver 廣播接收者,這個接收者我們可以認爲是最終接收者,通常情況下如果比他優先級更高的接收者如果沒有終止廣播,那麼他的 onReceive 會被執行兩次,第一次是正常的按照優先級順序執行,第二次是作爲最終接收者接收。如果比他優先級高的接收者終止了廣播,那麼他依然能接收到廣播。


註冊方法:

靜態註冊

        <!-- Required SDK核心功能 -->
        <receiver
            android:name="cn.jpush.android.service.PushReceiver"
            android:enabled="true"
            android:exported="false" >
            <intent-filter android:priority="1000" >
                <action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" />
 				<!-- Required  顯示通知欄 -->
                <category android:name="com.business" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.USER_PRESENT" />
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            </intent-filter>
            <!-- Optional -->
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED" />
                <action android:name="android.intent.action.PACKAGE_REMOVED" />

                <data android:scheme="com.business" />
            </intent-filter>
        </receiver>

動態註冊:

localBroadcastManager = LocalBroadcastManager.getInstance(mContext);
		IntentFilter intentFilter = new IntentFilter();
		intentFilter.addAction(REFRESH_DATA);
		intentFilter.addAction(OFF_LINE_ACTION);
		broadcastReceiver = new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				switch (intent.getAction()) {
				case REFRESH_DATA:
					String module = intent.getStringExtra("module");
					if("OrderFragment".equals(module)){
						Fragment fragment = mFragments[1];
						
						if(fragment != null && fragment instanceof OrderFragment){
							((OrderFragment)fragment).reload();
						}
					}
					break;
				case OFF_LINE_ACTION:
					offLine();
					break;
				default:
					break;
				}
				
			}
		};
		localBroadcastManager.registerReceiver(broadcastReceiver, intentFilter);

LocalBroadCastManager 註冊本地廣播接收器。


Android 的數據存儲方式

File 存儲

SharedPreference 存儲

ContentProvider 存儲
SQLiteDataBase 存儲
網絡存儲


爲什麼要用 ContentProvider?它和 sql 的實現上有什麼差別?

ContentProvider 屏蔽了數據存儲的細節,內部實現對用戶完全透明,用戶只需要關心操作數據的 uri 就可以了,ContentProvider 可以實現不同 app 之間共享。(安全、共享)


Sql 也有增刪改查的方法,但是 sql 只能查詢本應用下的數據庫。而 ContentProvider 還可以去增刪改查本地文件. xml 文件的讀取等。


ContentProvider、ContentResolver、ContentObserver 之間的關係

ContentProvider 內容提供者,用於對外提供數據

ContentResolver 內容解析者,用於獲取內容提供者提供的數據

ContentResolver.notifyChange(uri)發出消息

ContentObserver 內容監聽器,可以監聽數據的改變狀態
ContentResolver.registerContentObserver()監聽消息。


ListView 


ListView 如何提高其效率?
① 複用 ConvertView
② 自定義靜態類 ViewHolder
③ 使用分頁加載
④ 使用 WeakRefrence 引用 ImageView 對象

5、不要在getView中做耗時操作

6、onScrollListenerChanged 方法中判斷,不滾動時開啓線程做耗時操作。


在滾動狀態發生改變的方法中,有三種狀態:
手指按下移動的狀態: SCROLL_STATE_TOUCH_SCROLL: // 觸摸滑動
慣性滾動(滑翔(flgin)狀態): SCROLL_STATE_FLING: // 滑翔
靜止狀態: SCROLL_STATE_IDLE: // 靜止


ListView 可以顯示多種類型的條目嗎?

這個當然可以的,ListView 顯示的每個條目都是通過 baseAdapter 的 getView(int position, View convertView, ViewGroup parent)來展示的,理論上我們完全可以讓每個條目都是不同類型的 view,除此之外 adapter 還提供了getViewTypeCount()和 getItemViewType(int position)兩個方法。在 getView 方法中我們可以根據不同的viewtype 加載不同的佈局文件。


ListView 如何定位到指定位置
可以通過 ListView 提供的 lv.setSelection(48);方法


當在 ScrollView 中如何嵌入 ListView

方法一:重寫ListView

package 

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;

public class DisableScrollListView extends ListView {

	public DisableScrollListView(Context context, AttributeSet attrs,
			int defStyleAttr, int defStyleRes) {
		super(context, attrs, defStyleAttr, defStyleRes);
	}

	public DisableScrollListView(Context context, AttributeSet attrs,
			int defStyleAttr) {
		super(context, attrs, defStyleAttr);
	}

	public DisableScrollListView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public DisableScrollListView(Context context) {
		super(context);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
		super.onMeasure(widthMeasureSpec, expandSpec);
	}

	
	
}

方法二:手動測量ListView的高度。

lv = (ListView) findViewById(R.id.lv);
adapter = new MyAdapter();
lv.setAdapter(adapter);
getTotalHeightofListView(lv);


public static void getTotalHeightofListView(ListView listView) {
    ListAdapter mAdapter = listView.getAdapter(); 
    if (mAdapter == null) {
       return;
    }
    int totalHeight = 0;
    for (int i = 0; i <</SPAN> mAdapter.getCount(); i++) {
        View mView = mAdapter.getView(i, null, listView);
        mView.measure(
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
        //mView.measure(0, 0);
        totalHeight += mView.getMeasuredHeight();
        Log.w("HEIGHT" + i, String.valueOf(totalHeight));
    }
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (mAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();    
}


使用這個代碼來獲取listview的高度,需要注意一下幾個問題:

1、listview的item的根佈局一定要是LinearLayout;

2、調用這個方法需要在適配器數據加載更新之後:


ListView 中如何優化圖片


圖片的優化策略比較多
1、處理圖片的方式:
如果 ListView 中自定義的 Item 中有涉及到大量圖片的,一定要對圖片進行細心的處理,因爲圖片佔的內存是ListView 項中最頭疼的,處理圖片的方法大致有以下幾種
①、不要直接拿路徑就去循環 BitmapFactory.decodeFile;使用 Options 保存圖片大小、不要加載圖片到內存去。
②、對圖片一定要經過邊界壓縮尤其是比較大的圖片,如果你的圖片是後臺服務器處理好的那就不需要了
③、在 ListView 中取圖片時也不要直接拿個路徑去取圖片,而是以 WeakReference(使用 WeakReference 代替強引用。比如可以使用 WeakReference mContextRef)、SoftReference、WeakHashMap 等的來存儲圖片信息。
④、在 getView 中做圖片轉換時,產生的中間變量一定及時釋放

2、異步加載圖片基本思想:
1)、 先從內存緩存中獲取圖片顯示(內存緩衝)
2)、獲取不到的話從 SD 卡里獲取(SD 卡緩衝)
3)、都獲取不到的話從網絡下載圖片並保存到 SD 卡同時加入內存並顯示(視情況看是否要顯示)
原理:
優化一:先從內存中加載,沒有則開啓線程從 SD 卡或網絡中獲取,這裏注意從 SD 卡獲取圖片是放在子線程裏執行的,否則快速滑屏的話會不夠流暢。
優化二:於此同時,在 adapter 裏有個 busy 變量,表示 listview 是否處於滑動狀態,如果是滑動狀態則僅從內存中獲取圖片,沒有的話無需再開啓線程去外存或網絡獲取圖片。
優化三:ImageLoader 裏的線程使用了線程池,從而避免了過多線程頻繁創建和銷燬,如果每次總是 new 一個線程去執行這是非常不可取的,好一點的用的 AsyncTask 類,其實內部也是用到了線程池。在從網絡獲取圖片時,先是將其保存到 sd 卡,然後再加載到內存,這麼做的好處是在加載到內存時可以做個壓縮處理,以減少圖片所佔內存。


Java 中引用類型都有哪些
Java 中對象的引用分爲四種級別,這四種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。

強引用(StrongReference)
這個就不多說,我們寫代碼天天在用的就是強引用。如果一個對象被被人擁有強引用,那麼垃圾回收器絕不會回收它。當內存空間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題。
Java 的對象是位於 heap 中的,heap 中對象有強可及對象、軟可及對象、弱可及對象、虛可及對象和不可到達對象。應用的強弱順序是強、軟、弱、和虛。對於對象是屬於哪種可及的對象,由他的最強的引用決定。如下
代碼:
String abc=new String("abc"); //1
SoftReference<String> softRef=new SoftReference<String>(abc); //2
WeakReference<String> weakRef = new WeakReference<String>(abc); //3
abc=null; //4
softRef.clear();//5

第一行在 heap 堆中創建內容爲“abc”的對象,並建立 abc 到該對象的強引用,該對象是強可及的。
第二行和第三行分別建立對 heap 中對象的軟引用和弱引用,此時 heap 中的 abc 對象已經有 3 個引用,顯然此時 abc 對象仍是強可及的。第四行之後 heap 中對象不再是強可及的,變成軟可及的。第五行執行之後變成弱可及的。

軟引用(SoftReference)
如果一個對象只具有軟引用,那麼如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,Java 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。

軟引用是主要用於內存敏感的高速緩存。在 jvm 報告內存不足之前會清除所有的軟引用,這樣以來 gc 就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。什麼時候會被收集取決於 gc 的算法和 gc 運行時可用內存的大小。當 gc 決定要收集軟引用時執行以下過程,以上面的 softRef 爲例:

1 首先將 softRef 的 referent(abc)設置爲 null,不再引用 heap 中的 new String("abc")對象。
2 將 heap 中的 new String("abc")對象設置爲可結束的(finalizable)。
3 當 heap 中的 new String("abc")對象的 finalize()方法被運行而且該對象佔用的內存被釋放, softRef
被添加到它的 ReferenceQueue(如果有的話)中。
注意:對 ReferenceQueue 軟引用和弱引用可以有可無,但是虛引用必須有。

被 Soft Reference 指到的對象,即使沒有任何 Direct Reference,也不會被清除。一直要到 JVM 內存不足且沒有 Direct Reference 時纔會清除,SoftReference 是用來設計 object-cache 之用的。如此一來SoftReference 不但可以把對象 cache 起來,也不會造成內存不足的錯誤(OutOfMemoryError)。

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

虛引用(PhantomReference)

"虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要用來跟蹤對象被垃圾回收的活動。
虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。
建立虛引用之後通過 get 方法返回結果始終爲 null,通過源代碼你會發現,虛引用通向會把引用的對象寫進referent,只是 get 方法返回結果爲 null。先看一下和 gc 交互的過程再說一下他的作用。
1 不把 referent 設置爲 null, 直接把 heap 中的 new String("abc")對象設置爲可結束的(finalizable)。
2 與軟引用和弱引用不同, 先把 PhantomRefrence 對象添加到它的 ReferenceQueue 中.然後在釋放虛可及的對象。


在 Android 中如何調用 C 語言

當我們的 Java 需要調用C 語言的時候可以通過 JNI 的方式,Java Native Interface。Android提供了對JNI 的支持,因此我們在 Android 中可以使用 JNI 調用 C 語言。

在 Android 開發目錄的 libs 目錄下添加 xxx.so 文件,不過xxx.so文件需要放在對應的 CPU 架構名目錄下,比如 armeabi,x86 等。
在Java 代碼需要通過 System.loadLibrary(libName);加載 so 文件。同時 C 語言中的方法在 java 中必須以 native 關鍵字來聲明。普通 Java 方法調用這個 native 方法接口,虛擬機內部自動調用 so 文件中對應的方法。


請介紹一下 NDK
1.NDK 是一系列工具的集合
NDK 提供了一系列的工具,幫助開發者快速開發 C(或 C++)的動態庫,並能自動將 so 和 java 應用一起打包成 apk。NDK 集成了交叉編譯器,並提供了相應的 mk 文件隔離 CPU、平臺、ABI 等差異,開發人員只需要簡單修改 mk 文件(指出“哪些文件需要編譯”、“編譯特性要求”等),就可以創建出 so。
2.NDK 提供了一份穩定、功能有限的 API 頭文件聲明
Google 明確聲明該 API 是穩定的,在後續所有版本中都穩定支持當前發佈的 API。從該版本的 NDK 中看出,這些 API 支持的功能非常有限,包含有:C 標準庫(libc)、標準數學庫(libm)、壓縮庫(libz)、Log 庫(liblog)


Android常用的兩種連接網絡對象

Android 提供了 org.apache.http.HttpClientConnection  和  java.net.HttpURLConnection 兩個連接網絡對象。(從android5.0以後,google逐漸去除Apache ,要使用HttpClientConnection ,需要自己下載lib)


生成 JSON 對象和數組

學習: Post 請求參數 數據裝載. 生成JSON
1)生成 JSON:
方法 1、創建一個 map,通過構造方法將 map 轉換成 json 對象
Map<String, Object> map = new HashMap<String, Object>();
map.put("name", "zhangsan");
map.put("age", 24);
JSONObject json = new JSONObject(map);


方法 2、創建一個 json 對象,通過 put 方法添加數據
JSONObject json=new JSONObject();
json.put("name", "zhangsan");
json.put("age", 24);


2)生成 JSON 數組:
創建一個 list,通過構造方法將 list 轉換成 json 對象
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("name", "zhangsan");
map1.put("age", 24);
Map<String, Object> map2 = new HashMap<String, Object>();
map2.put("name", "lisi");
map2.put("age", 25);
List<Map<String, Object>> list=new ArrayList<Map<String,Object>>();
list.add(map1);
list.add(map2);
JSONArray array=new JSONArray(list);
System.out.println(array.toString());


如何從網絡上加載一個圖片顯示到界面
可以通過 BitmapFactory.decodeStream(inputStream);方法將圖片轉換爲 bitmap,然後通過imageView.setImageBitmap(bitmap);將該圖片設置到 ImageView 中。這是原生的方法,還可以使用第三方開源的工具來實現,比如使用 SmartImageView 作爲 ImageView 控件,然後直接設置一個 url 地址即可。也可以使用xUtils 中的 BitmapUtils 工具。


Serializable 和 Parcelable 的區別

參考:Android 對象序列化 Serializable實現與Parcelabel實現的區別


序列化和反序列化原理

java中的序列化(serialization)機制能夠將一個實例對象的狀態信息寫入到一個字節流中,使其可以通過socket進行傳輸、或者持久化存儲到數據庫或文件系統中;然後在需要的時候,可以讀取字節流中的信息來重構一個相同的對象。序列化機制在java中有着廣泛的應用,EJB、RMI、hessian等技術都是以此爲基礎的。 


so,序列化一般用於以下場景: 
1:永久性保存對象,保存對象的字節序列到本地文件或者數據庫中
2:通過序列化以字節流的形式使對象在網絡中進行傳遞和接收
3:通過序列化在進程間傳遞對象。

例子:

 Person  model

package com.tan.test.modle;

import java.io.Serializable;

public class Person implements Serializable{

	public String name;
	public String  sex;
	
	public Person(String name, String sex) {
		super();
		this.name = name;
		this.sex = sex;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}
}

Test

package com.tan.test.main;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import com.tan.test.modle.Person;

public class Test {

	public static void main(String[] args) {
		// 創建一個對象
		Person people = new Person("張三", "男");
		try {
			// 實例化ObjectOutputStream對象
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\person.txt"));
			// 將對象寫入文件
			oos.writeObject(people);
			oos.flush();
			oos.close();

			// 實例化ObjectInputStream對象
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\person.txt"));

			try {
				// 讀取對象people,反序列化
				Person p = (Person) ois.readObject();
				System.out.println("姓名:" + p.getName());
				System.out.println("性別:" + p.getSex());
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}


我們將對象序列化並輸出。ObjectOutputStream能把Object輸出成Byte流。

代碼顯示的18行-22行


我們將Byte流暫時存儲到person.txt文件裏

而在24行-34行,我們利用反序列化,根據字節流重建對象。

從上面代碼中,我們不難看出,序列化和反序列化的倆個主要類:ObjectOutputStream、ObjectInputStream。


IntentFilter 匹配規則

參考:《Android 開發藝術探索》隨手筆記——第一章Activity生命週期和啓動模式


Fragment 跟 Activity 之間是如何傳值的


當 Fragment 跟 Activity 綁定之後,在 Fragment 中可以直接通過 getActivity()方法獲取到其綁定的 Activity對象,這樣就可以調用 Activity 的方法了。

在 Activity 中可以通過如下方法獲取到 Fragment 實例
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
Fragment fragment = fragmentManager.findFragmentById(id);

獲取到 Fragment 之後就可以調用 Fragment 的方法。也就實現了通信功能。


Fragment 生命週期

參考: Activity 和 Fragment 生命週期 圖形說明


如何對 Android 應用進行性能分析

DDMS 中 有traceviewheapallocation tracker 等工具都可以幫助我們分析應用的方法執行時間效率和內存使用情況

heap

heap 工具可以幫助我們檢查代碼中是否存在會造成內存泄漏的地方。
用heap 監測應用進程使用內存情況的步驟如下:

1. 啓動 eclipse 後,切換到DDMS 透視圖,並確認Devices 視圖、Heap視圖都是打開的;
2. 點擊選中想要監測的進程,比如 system_process 進程;
3. 點擊選中Devices 視圖界面中最上方一排圖標中的“Update Heap”圖標;
4. 點擊Heap 視圖中的“CauseGC”按鈕;
5. 此時在Heap 視圖中就會看到當前選中的進程的內存使用量的詳細情況。

說明:

a) 點擊“Cause GC”按鈕相當於向虛擬機請求了一次 gc 操作;
b) 當內存使用信息第一次顯示以後,無須再不斷的點擊“Cause GC”,Heap 視圖界面會定時刷新,在對應用的不斷的操作過程中就可以看到內存使用的變化;
c) 內存使用信息的各項參數根據名稱即可知道其意思,在此不再贅述。


如何才能知道我們的程序是否有內存泄漏的可能性呢。這裏需要注意一個值:Heap 視圖中部有一個 Type叫做data object,即數據對象,也就是我們的程序中大量存在的類類型的對象。在 data object 一行中有一列是“TotalSize”,其值就是當前進程中所有 Java 數據對象的內存總量,一般情況下,這個值的大小決定了是否會有內存泄漏。可以這樣判斷:

a) 不斷的操作當前應用,同時注意觀察 data object 的Total Size 值;
b) 正常情況下Total Size 值都會穩定在一個有限的範圍內,也就是說由於程序中的的代碼良好,沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進行 GC 的過程中,這些對象都被回收了,內存佔用量會會落到一個穩定的水平;
c) 反之如果代碼中存在沒有釋放對象引用的情況,則 data object 的 Total Size 值在每次 GC 後不會有明顯的回落,隨着操作次數的增多 Total Size 的值會越來越大,直到到達一個上限後導致進程被 kill 掉。
d) 此處以system_process 進程爲例,在我的測試環境中 system_process 進程所佔用的內存的 data object的 Total Size 正常情況下會穩定在 2.2~2.8 之間,而當其值超過 3.55 後進程就會被 kill。

總之,使用 DDMS 的Heap 視圖工具可以很方便的確認我們的程序是否存在內存泄漏的可能性。

 

allocationtracker

運行 DDMS,只需簡單的選擇應用進程並單擊 Allocation tracker 標籤,就會打開一個新的窗口,單擊“Start Tracing”按鈕;

然後,讓應用運行你想分析的代碼。運行完畢後,單擊“Get Allocations”按鈕,一個已分配對象的列表就會出現第一個表格中。

單擊第一個表格中的任何一項,在表格二中就會出現導致該內存分配的棧跟蹤信息。通過 allocation tracker,不僅知道分配了哪類對象,還可以知道在哪個線程、哪個類、哪個文件的哪一行。


TraceView  的使用分析方法 參考  《羣英傳》和《藝術開發探索》


第三方工具   MAT  的使用分析方法參考  《羣英傳》和《藝術開發探索》


Hierarchy Viewer (分析冗餘佈局)

位於 :sdk\tools\hierarchyviewer.bat


內存泄漏 OOM 等

1、資源釋放問題
程序代碼的問題,長期保持某些資源,如 ContextCursorIO流的引用,資源得不到釋放造成內存泄露。

2、對象內存過大問題
保存了多個耗用內存過大的對象(如 BitmapXML文件),造成內存超出限制。

3static關鍵字的使用問題

static Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。所以用 static修飾的變量,它的生命週期是很長的,如果用它來引用一些資源耗費過多的實例(Context的情況最多),這時就要謹慎對待了。
public class ClassName {
private static Context mContext;
//
省略
}
以上的代碼是很危險的,如果將 Activity賦值到 mContext的話。那麼即使該 Activity已經 onDestroy,但是由於仍有對象保存它的引用,因此該 Activity 依然不會被釋放。

針對 static 的解決方案

應該儘量避免 static成員變量引用資源耗費過多的實例,比如 Context
② Context
儘量使用 ApplicationContext,因爲 Application Context的生命週期比較長,引用它不會出現內存泄露的問題。
使用 WeakReference代替強引用。比如可以使用 WeakReference<Context> mContextRef;


4、線程導致內存溢出

線程產生內存泄露的主要原因在於線程生命週期的不可控。我們來考慮下面一段代碼

public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}
private class MyThread extends Thread{
@Override
public void run() {
super.run();
//do somthing
}
}

這段代碼很平常也很簡單,是我們經常使用的形式。我們思考一個問題:假設 MyThread 的 run函數是一個很費時的操作,當我們開啓該線程後,將設備的橫屏變爲了豎屏,一 般情況下當屏幕轉換時會重新創建 Activity,按照我們的想法,老的 Activity 應該會被銷燬纔對,然而事實上並非如此。

由於我們的線程是 Activity 的內部類,所以MyThread 中保存了Activity 的一個引用,當MyThread 的 run函數沒有結束時,MyThread 是不會被銷燬的,因此它所引用的老的 Activity 也不會被銷燬,因此就出現了內存泄露的問題。


有些人喜歡用 Android 提供的AsyncTask,但事實上AsyncTask 的問題更加嚴重,Thread 只有在 run 函數不結束時纔出現這種內存泄露問題,然而 AsyncTask 內部的實現機制是運用了 ThreadPoolExcutor,該類產生的 Thread 對象的生命週期是不確定的,是應用程序無法控制的,因此如果 AsyncTask 作爲 Activity 的內部類,就更容易出現內存泄露的問題。

針對這種線程導致的內存泄露問題的解決方案:

第一、將線程的內部類,改爲靜態內部類(因爲非靜態內部類擁有外部類對象的強引用,而靜態類則不擁有)。
第二、在線程內部採用弱引用保存 Context 引用。


3、如何避免 OOM 異常


1、圖片過大導致 OOM

解決方法:
方法 1: 等比例縮小圖片

方法 2:對圖片採用軟引用,及時地進行 recyle()操作

2、界面切換導致OOMOO

有時候我們會發現這樣的問題,橫豎屏切換 N 次後OOM 了

3、查詢數據庫沒有關閉遊標

4、構造 Adapter時,沒有使用緩存的 convertView

5、Bitmap對象不再使用時調用recycle()釋放內


自定義組合控件

1. 聲明一個 View 對象,繼承相對佈局,或者線性佈局或者其他的 ViewGroup。
2. 在自定義的View 對象裏面重寫它的構造方法,在構造方法裏面就把佈局都初始化完畢。
3. 根據業務需求添加一些 api 方法,擴展自定義的組合控件;
4. 希望在佈局文件裏面可以自定義一些屬性。
5. 聲明自定義屬性的命名空間。
xmlns:itheima=http://schemas.android.com/apk/res/com.itheima.mobilesafe

Itheima 是命名空間 類似android:text    res/ 後面跟包名
6. 在 res目錄下的values 目錄下創建attrs.xml 的文件聲明我們寫的屬性。

7. 在佈局文件中寫自定義的屬性。
8. 使用這些定義的屬性。自定義 View 對象的構造方法裏面有一個帶兩個參數的構造方法佈局文件裏面定義的屬性都放在 AttributeSet attrs,獲取那些定義的屬性。


自定義VIew

參考: Android 自定義View (一)


描述一下View制流程

整個 View 樹的繪圖流程是在 ViewRoot.java 類(該類位於 Android 源碼下面:

D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的performTraversals()函數展開的,該函數做的執行過程可簡單概況爲根據之前設置的狀態,判斷是否需要重新計算視圖大小(measure)、是否重新需要安置視圖的位置(layout)、以及是否需要重繪 (draw),


AsyncTask 使用注意事項

1.異步任務的實例必須在 UI 線程中創建。
2.execute(Params... params)
方法必須在 UI線程中調用。
3.
調 onPreExecute() doInBackground(Params... params) onProgressUpdate(Progress.. values)onPostExecute(Result result)這幾個方法。
4.
不能在 doInBackground(Params... params)中更改 UI組件的信息。
5.
一個任務實例只能執行一次,如果執行第二次將會拋出異常。
.





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