這一章學到的:
- 全局獲取Context的技巧
- 使用Intent傳遞對象
- Serialiable方式
- Parcelable方式
- 定制自己的日志工具
- 調試Android程序

13.1 全局獲取Context的技巧
當應用程序的架構逐漸開始復雜起來的時候,很多的邏輯代碼都將脫離Activity類,但此時你又恰恰需要使用Context,也許這個時候你就會感到有些傷腦經(jīng)了。
在某些情況下,獲取Context并非是那么一件事,下面我們就來學習一種技巧,讓你在項目的任何地方都能夠輕松獲取到Context。
Android提供了一個Application類,每當應用程序啟動的時候,系統(tǒng)就會自動將這個類進行初始化。而我們可以定制一個自己的Aoolication類,以便于管理程序內一些全局的狀態(tài)信息,比如說全局Context。
定制一個自己的Application其實并不困難,首先我們需要創(chuàng)建一個MyApplication類繼承自Application。
代碼如下:
public class MyApplication extends Application
{
private static Context mContext;
@Override
public void onCreate()
{
mContext = getApplicationContext();
}
public static Context getContext()
{
return mContext;
}
}
這里我們重寫了父類的onCreate()方法,并通過調用getApplicationContext()方法得到了一個應用程序級別的Context,然后又提供了一個靜態(tài)的getContext()方法,在這里將剛才獲取到的Context進行返回。
注意:getContext()返回的也要是一個static的變量,所以mContext要加static。
接下來我們需要告知系統(tǒng),當程序啟動的時候應該初始化MyApplication類,而不是默認的Application類。這一步也很簡單,在AndroidManifest.xml文件的<application>標簽下進行指定就可以了。
代碼如下所示:
<application
········
android:name="com.example.wumen.getcontext.MyApplication">
·········
</application>
注意這里在指定MyApplication的時候,一定要加上完整的包名,不然系統(tǒng)將無法找到這個類。
這樣我們就已經(jīng)實現(xiàn)了一種全局獲取Context的機制,之后不管你想在項目的任何地方使用Context,只需要調用一下MyApplication.getContext()就可以了。
任何一個項目都只能配置一個Application。
13.2 使用Intent傳遞對象
我們可以借助Intent來啟動活動,發(fā)送廣播,啟動服務等。在進行上述操作的時候,我們還可以在Intent中添加一些附加數(shù)據(jù),已達到傳值的效果。
putExtra()方法中所支持的數(shù)據(jù)類型是有限的,雖然常用的一些數(shù)據(jù)類型它都會支持,但是當你想去傳遞一些自定義對象的時候,就會發(fā)現(xiàn)無從下手。
13.2.1 Serializable方式(誰瑞來日報)
Serializable是序列化的意思,表示將一個對象轉換成可存儲或可傳輸?shù)臓顟B(tài)。序列化后的對象可以在網(wǎng)絡上進行傳輸,也可以存儲到本地。至于序列化的方法也很簡單,只需要讓一個類去實現(xiàn)Serializable這個借口就可以了。
比如說Person類:
public class Person implements Serializable
{
private String name;
private int age;
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
讓Person類去實現(xiàn)了Serializable接口,這樣所有的Person對象就都是可序列化的了。
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
這里我們創(chuàng)建了一個Person的實例,然后就直接將它傳入到putExtra()方法中了。由于Person類實現(xiàn)了Serializable接口,所以才可以這樣寫。
Intent intent = getIntent();
Person person = (Person) intent.getSerializableExtra("person_data");
這里調用了getSerializableExtra()方法來獲取通過參數(shù)傳遞過來的序列化對象,接著再將它向下轉型成Person對象,這樣我們就成功實現(xiàn)了使用Intent來傳遞對象的功能了。
13.2.2 Parcelable方式(撲瑞賽了包)
不同于將對象進行序列化,Parcelable方式的實現(xiàn)原理是將一個完整的對象進行分解,而分解后的每一部分都是Intent所支持的數(shù)據(jù)類型,這樣也就實現(xiàn)傳遞對象的功能了。
public class Person implements Parcelable
{
private String name;
private int age;
············
@Override
public int describeContents()
{
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(name);//寫出name
dest.writeInt(age);//寫出age
}
public static final Creator<Person> CREATOR = new Creator<Person>()
{
@Override
public Person createFromParcel(Parcel in)
{
Person person = new Person();
person.name = in.readString();//讀取name
person.age = in.readInt();//讀取age
return person;
}
@Override
public Person[] newArray(int size)
{
return new Person[size];
}
};
}
Parcelable的實現(xiàn)方式要復雜一些。首先我們讓Person類去實現(xiàn)了Parcelable接口,這樣就必須重寫describeContents()和writeToParcel()這兩個方法。其中describeContents()方法直接返回0就可以了,而writeToParcel()方法中我們需要調用Parcel的writeXxx()方法,將Person類中的字段一一寫出。注意,字符串型數(shù)據(jù)就調用writeString()方法,整型數(shù)據(jù)就調用writeInt()方法,依此類推。
除此之外,我們還必須在Person類中提供一個名為CREATOR的常量,這里創(chuàng)建了Parcelable.Creator接口的一個實現(xiàn),并將泛型指定為Person。接著需要重寫createFromParcel()和newArray()這兩個方法,在createFromParcel()方法中我們要去讀取剛才寫出的name和age字段,并創(chuàng)建一個Person對象進行返回,其中name和age都是調用Parcel的readXxx()方法讀取到的,注意這里讀取的順序一定要和剛才寫出的順序完全一致。而newArray()方法中的實現(xiàn)就簡單多了,只需要new出一個Person數(shù)組,并使用方法中傳入的size作為數(shù)組大小就可以了。
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);
我們仍然可以使用相同的代碼來傳遞Person對象,只不過在獲取對象的時候需要稍加改動。
Intent intent = getIntent();
Person person = (Person) intent.getParcelableExtra("person_data");
對比一下,Serializable的方式較為簡單,但由于會把整個對象進行序列化,因此效率會比 Parcelable方式低一些,所以在通常情況下還是更加推薦使用 Parcelable的方式來實現(xiàn)Intent傳遞對象的功能。
13.3 定制自己的日志工具
最理想的情況是能夠自由地控制日志的打印,當程序處于開發(fā)階段時就讓日志打印出來,當程序上線了之后就把日志屏蔽掉。
看起來好像是挺高級的一個功能,其實并不復雜,我們只需要定制一個自己的日志工具就可以輕松完成了。比如新建一個LogUtil類,代碼如下所示:
public class LogUtil
{
public static final int VERBOSE = 1;
public static final int DEBUG = 2;
public static final int INFO = 3;
public static final int WARN = 4;
public static final int ERROR = 5;
public static final int NOTHING = 6;
public static final int level = VERBOSE;
public static void v(String tag,String msg)
{
if (level <= VERBOSE)
{
Log.v(tag,msg);
}
}
public static void d(String tag,String msg)
{
if (level <= DEBUG)
{
Log.d(tag,msg);
}
}
public static void i(String tag,String msg)
{
if (level <= INFO)
{
Log.i(tag,msg);
}
}
public static void w(String tag,String msg)
{
if (level <= WARN)
{
Log.w(tag,msg);
}
}
public static void e(String tag,String msg)
{
if (level <= ERROR)
{
Log.e(tag,msg);
}
}
}
我們在LogUtil中先是定義了VERBOSE,DEBUG,INFO,WARN,ERROR,NOTHING這6個整形常量,并且它們對應的值都是遞增的。然后又定義了一個靜態(tài)變量level,可以將它的值指定為上面6個常量中的任意一個。
接下里我們提供了v(),d(),i(),w(),e()這5個自定義的日志方法,在其內部分別調用了Log.v(),Log.d(),Log.i(),Log.w(),Log.e()這5個方法來打印日志,只不過在這些自定義的方法中我們都加入了一個if判斷,只有當level的值小于或等于對應日志級別值的時候,才會將日志打印出來。
這樣就把一個自定義的日志工具創(chuàng)建好了,之后在項目里我們可以像使用普通的日志工具一樣使用LogUtil,比如打印一行DEBUG級別的日志就可以這樣寫:
LogUtil.d("Tag","debug log");
然后我們只需要修改level變量的值,就可以自由地控制日志的打印行為了。比如讓level等于VERBOSE就可以把所有的日志都打印出來,讓level等于WARN就可以只打印警告以上級別的日志,讓level等于NOTHING就可以把所有日志都屏蔽掉。
13.4 調試Android程序
當開發(fā)過程中遇到一些奇怪的bug,但又遲遲定位不出來原因是什么的時候。最好的解決辦法就是調試了。調試允許我們逐行地執(zhí)行代碼,并可以實時觀察內存中的數(shù)據(jù),從而能夠比較輕易地查出問題的原因。
調試工作的第一步肯定是添加斷點,這里由于我們要調試登陸部分的問題,所以斷點可以加在登錄按鈕的點擊時間里。添加斷點的方法也很簡單,只需要在相應代碼行的左邊點擊一下就可以了。

如果想要取消這個斷點,對著它再次點擊就可以了。
添加好了斷點,接下來就可以對程序進行調試了,點擊Android Studio頂部工具欄中的Debug按鈕(最右邊的按鈕),就會使用調試模式來啟動程序。

在輸入框中輸入賬號和密碼,并點擊Login按鈕,這時Android Studio就會自動打開Debug窗口。

接下來每按一次F8鍵,代碼就會向下執(zhí)行一行,并且通過Variables視圖還可以看到內存中的數(shù)據(jù)。

調試完之后點擊Debug窗口中的Stop按鈕(最下邊的按鈕)來結束調試即可。

這種調試方式雖然完全可以正常工作,但在調試模式下,程序的運行效率將會大大地降低,如果你的斷點加在一個比較靠后的位置,需要執(zhí)行很多的操作才能運行到這個斷點,那么前面這些操作就都會有一些卡頓的感覺。
Android還提供了另外一種調試的方式,可以讓程序隨時進入到調試模式。
這次不需要選擇調試模式來啟動程序了,就使用正常的方式來啟動程序。把賬號和密碼輸入好,然后點擊Android Studio頂部工具欄的Attach debugger to Android process按鈕(最左邊的按鈕)

此時會讓彈出一個進程選擇提示框

這里目前只列出了一個進程,也就是我們當前程序的進程,選中這個進程,然后點擊Ok按鈕,就會讓這個進程進入到調試模式了。

接下來在程序中點擊Login按鈕,Android Studio同樣也會自動打開Debug窗口,之后的流程就都是相同的了。