第一行代碼讀書筆記 13 -- Android 中的一些高級(jí)技巧

本篇文章主要介紹以幾下個(gè)知識(shí)點(diǎn):

  • 獲取全局 Context
  • 使用 Intent 傳遞對(duì)象
  • 定制日志工具
  • 創(chuàng)建定時(shí)任務(wù)
  • 多窗口模式編程
圖片來源于網(wǎng)絡(luò)

13.1 全局獲取 Context 的技巧

??在某些情況下,獲取 Context 并非是一件容易事,下面就來學(xué)習(xí)讓你在項(xiàng)目的任何地方都能輕松獲取到 Context 的一種技巧。

??Android 提供了一個(gè) Application 類,每當(dāng)應(yīng)用程序啟動(dòng)時(shí),系統(tǒng)就會(huì)自動(dòng)將這個(gè)類進(jìn)行初始化。我們可以定制一個(gè)自己的 Application 類,以便管理程序內(nèi)一些全局的狀態(tài)信息,比如全局的 Context。

??定制一個(gè)自己的 Application 并不復(fù)雜,首先需要?jiǎng)?chuàng)建一個(gè) MyApplication 類繼承自 Application,如下:

public class MyApplication extends Application {

    private static Context context;

    @Override
    public void onCreate() {
        context = getApplicationContext();
    }

    public static Context getContext(){
        return context;
    }
}

??接下來在 AndroidManifest.xml 文件的<application>標(biāo)簽下進(jìn)行指定就可以了,如下:

 <application
     android:name=".MyApplication"
     android:allowBackup="true"
     android:icon="@mipmap/ic_launcher"
     android:label="@string/app_name"
     android:supportsRtl="true"
     android:theme="@style/AppTheme">
     . . .
</application>

??這樣就實(shí)現(xiàn)了一種全局獲取 Context 的機(jī)制,之后在項(xiàng)目的任何地方想要獲取 Context,只需調(diào)用 MyApplication.getContext() 就可以了,如彈吐司:

Toast.makeText(MyApplication.getContext(),"提示內(nèi)容",Toast.LENGTH_SHORT).show();

??任何一個(gè)項(xiàng)目都只能配置一個(gè) Application,當(dāng)引用第三方庫如 LitePal 時(shí)要配置 LitePalApplication 就會(huì)起沖突了,這種情況就要在自己的 Application 中去調(diào)用 LitePal 的初始化方法,如下:

public class MyApplication extends Application {

    private static Context context;

    @Override
    public void onCreate() {
        context = getApplicationContext();
        // 調(diào)用 LitePal 的初始化方法
        LitePal.initialize(context);
    }

    public static Context getContext(){
        return context;
    }
}

??使用上面的寫法,把 Context 對(duì)象通過參數(shù)傳遞給 LitePal,效果和在 AndroidManifest.xml 中配置 LitePalApplication 是一樣的。

??當(dāng)然,我個(gè)人是更習(xí)慣于通過獲取全局類實(shí)例的方法來定制自己的 Application 類,如下:

public class MyApplication extends Application {

    private static MyApplication mInstance;
    
    @Override
    public void onCreate() {
        super.onCreate();
        mInstance = this;
        // 調(diào)用 LitePal 的初始化方法
        LitePal.initialize(this);        
    }

    /**
     * Singleton main method. Provides the global static instance of the helper class.
     * @return The MyApplication instance.
     */
    public static synchronized MyApplication getInstance() {
        return mInstance;
    }
}

13.2 使用 Intent 傳遞對(duì)象

??使用 Intent 時(shí),可以在 Intent 中添加一些附加數(shù)據(jù),以達(dá)到傳值的效果,如在第一個(gè)活動(dòng)中添加如下代碼:

Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("string_data","hello");
intent.putExtra("int_data",100);
startActivity(intent);

??然后在第二個(gè)活動(dòng)中就可以獲得這些值了,如下:

getIntent().getStringExtra("string_data");
getIntent().getIntExtra("int_data",0);

??但上面的 putExtra() 方法中所支持的數(shù)據(jù)類型是有限的,若要傳遞一些自定義對(duì)象時(shí)就無從下手了,下面就來學(xué)習(xí)下用 Intent 來傳遞對(duì)象的技巧:SerializableParcelable

13.2.1 Serializable 方式

??Serializable 是序列化的意思,表示將一個(gè)對(duì)象轉(zhuǎn)化成可儲(chǔ)存或可傳輸?shù)臓顟B(tài)。序列化的對(duì)象可在網(wǎng)絡(luò)上傳輸也可存儲(chǔ)到本地。將一個(gè)類序列化只要去實(shí)現(xiàn) Serializable 接口就可以了。

??比如一個(gè) Person 類,將它序列化可以這樣寫:

public class Person implements Serializable{
    
    private String name;
    
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

??接下來在第一個(gè)活動(dòng)中的寫法非常簡單:

Person person = new Person();
person.setName("Tom");
person.setAge(18);
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("person_data",person);
startActivity(intent);

??然后在第二個(gè)活動(dòng)中獲取對(duì)象也非常簡單:

Person person = (Person) getIntent().getSerializableExtra("person_data");

??這樣就實(shí)現(xiàn)了使用 Intent 傳遞對(duì)象了。

13.2.2 Parcelable 方式

??Parcelable方式的實(shí)現(xiàn)原理是將一個(gè)完整的對(duì)象進(jìn)行分解,而分解后的每一部分都是 Intent 所支持的數(shù)據(jù)類型,這樣也就實(shí)現(xiàn)傳遞對(duì)象的功能了。

??Parcelable 的實(shí)現(xiàn)方式要稍微復(fù)雜一些,修改 Person 中的代碼如下:

public class Person implements Parcelable {

    private String name;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // 寫出數(shù)據(jù):將 Person 類中的字段一一寫出
        dest.writeString(name);// 寫出 name
        dest.writeInt(age);// 寫出 age
    }

    public static final Parcelable.Creator<Person>CREATOR = new Parcelable.Creator<Person>(){

        @Override
        public Person createFromParcel(Parcel source) {
            // 讀取數(shù)據(jù):讀取的順序要和寫出的順序完全相同
            Person person = new Person();
            person.name = source.readString();//讀取 name
            person.age = source.readInt();//讀取 age
            return person;
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
}

??接下來在第一個(gè)活動(dòng)中的寫法不變,在第二個(gè)活動(dòng)中獲取對(duì)象稍加改動(dòng):

Person person = (Person)getIntent().getParcelableExtra("person_data");

??這樣就也實(shí)現(xiàn)使用 Intent 傳遞對(duì)象了。

??對(duì)比一下,Serializable 的方式較為簡單,但由于把整個(gè)對(duì)象進(jìn)行序列化,效率會(huì)比 Parcelable 方式低。一般推薦使用 Parcelable 的方式來實(shí)現(xiàn) Intent 傳遞對(duì)象的功能。

13.3 定制自己的日志工具

??開發(fā)一個(gè)項(xiàng)目時(shí),定制一個(gè)自己的日志工具能夠自由的控制日志的打印,當(dāng)程序處于開發(fā)階段時(shí)讓日志打印出來,當(dāng)程序上線后把日志屏蔽。

??新建日志工具類 LogUtils 如下:

public class LogUtils {

    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 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);
        }
    }
}

??上述代碼提供了5個(gè)自定義的日志方法,其內(nèi)部分別調(diào)用了 Android 自帶的打印日志方法,在項(xiàng)目里使用就像使用普通日志工具一樣,如打印一行 DEBUG 級(jí)別的日志可以這樣寫:

LogUtils.d("TAG","debug log");

??值得注意的是,LogUtils 定義了一個(gè)靜態(tài)變量 level,在開發(fā)階段將 level 指定成 VERBOSE,當(dāng)項(xiàng)目正式上線時(shí)將 level 指定成 NOTHING,將所有日志屏蔽。

13.4 創(chuàng)建定時(shí)任務(wù)

??Android 中的定時(shí)任務(wù)一般有兩種實(shí)現(xiàn)方式,一種是使用 Java API 里提供的 Timer 類(不太適用于需要長期在后臺(tái)運(yùn)行的定時(shí)任務(wù)),一種是使用 Android 的 Alarm 機(jī)制(具有喚醒 CPU 功能,可以保證大多數(shù)情況下執(zhí)行定時(shí)任務(wù)時(shí) CPU 能正常工作)。

13.4.1 Alarm 機(jī)制

??Alarm 機(jī)制的用法不復(fù)雜,主要是借助 AlarmManager 類來實(shí)現(xiàn)的。比如想要設(shè)定一個(gè)任務(wù)在 10 秒鐘后執(zhí)行,可寫成:

// 獲取 AlarmManager 的實(shí)例
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        
// 設(shè)置觸發(fā)時(shí)間
// SystemClock.elapsedRealtime() 獲取系統(tǒng)開機(jī)至今所經(jīng)歷時(shí)間的毫秒數(shù)
// SystemClock.currentTimeMillis() 獲取1970年1月1日0點(diǎn)至今所經(jīng)歷時(shí)間的毫秒數(shù)
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
        
// 3個(gè)參數(shù):指定 AlarmManager 的工作類型、定時(shí)任務(wù)的觸發(fā)時(shí)間、PendingIntent
// 其中AlarmManager 的工作類型有四種:
// ELAPSED_REALTIME 定時(shí)任務(wù)的觸發(fā)時(shí)間從系統(tǒng)開機(jī)開始時(shí)算起,不會(huì)喚醒 CPU
// ELAPSED_REALTIME_WAKEUP 系統(tǒng)開機(jī)開始時(shí)算起,會(huì)喚醒 CPU
// RTC 從1970年1月1日0點(diǎn)開始算起,不會(huì)喚醒 CPU
// RTC_WAKEUP 從1970年1月1日0點(diǎn)開始算起,會(huì)喚醒 CPU
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pandingIntent);

??舉個(gè)例子,實(shí)現(xiàn)一個(gè)長時(shí)間在后臺(tái)定時(shí)運(yùn)行的服務(wù),首先新建一個(gè)普通的服務(wù) LongRunningService,將觸發(fā)定時(shí)任務(wù)的代碼寫到 onStartCommand() 方法中,如下:

public class LongRunningService extends Service {
    public LongRunningService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 在這里執(zhí)行具體的邏輯操作
            }
        }).start();
        AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
        int anHour = 60 * 60 * 1000;//1小時(shí)的毫秒數(shù)
        long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
        Intent i = new Intent(this,LongRunningService.class);
        PendingIntent pi = PendingIntent.getService(this,0,i,0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
        return super.onStartCommand(intent, flags, startId);
    }
}

??然后,要啟動(dòng)定時(shí)服務(wù)時(shí)調(diào)用如下代碼即可:

Intent intent = new Intent(context,LongRunningService.class);
context.startService(intent);

??值得注意的是,從 Android 4.4開始,由于系統(tǒng)在耗電方面的優(yōu)化,Alarm 任務(wù)的觸發(fā)時(shí)間變得不準(zhǔn)確,可能會(huì)延遲一段時(shí)間后再執(zhí)行。當(dāng)然,使用 AlarmManager 的 setExact() 方法來替代 set() 方法,基本上可以保證任務(wù)準(zhǔn)時(shí)執(zhí)行。

13.4.2 Doze 模式

??在 Android 6.0中,谷歌加入了一個(gè)全新的 Doze 模式,可以極大幅度地延長電池的使用壽命。下面就來了解下這個(gè)模式,掌握一些編程時(shí)注意事項(xiàng)。

??在 6.0 及以上系統(tǒng)的設(shè)備上,若未插接電源,處于靜止?fàn)顟B(tài)(7.0中刪除了這一條件),且屏幕關(guān)閉了一段時(shí)間之后,就會(huì)進(jìn)入到 Doze 模式。在 Doze 模式下,系統(tǒng)會(huì)對(duì) CPU、網(wǎng)絡(luò)、Alarm 等活動(dòng)進(jìn)行限制,從而延長電池的使用壽命。

??當(dāng)然,系統(tǒng)不會(huì)一直處于 Doze 模式,而是會(huì)間歇性的退出一小段時(shí)間,在這段時(shí)間應(yīng)用可以去完成它們的同步操作、Alarm 任務(wù)等,其工作過程如下:

Doze 模式的工作過程

??Doze 模式下受限的功能有:
?(1)網(wǎng)絡(luò)訪問被禁止
?(2)系統(tǒng)忽略喚醒CPU或屏幕操作
?(3)系統(tǒng)不再執(zhí)行WIFI掃描
?(4)系統(tǒng)不再執(zhí)行同步任務(wù)
?(5)Alarm 任務(wù)將會(huì)在下次退出 Doze 模式時(shí)執(zhí)行

??特殊需求,要 Alarm 任務(wù)在 Doze 模式下也必須正常執(zhí)行,則可以調(diào)用 AlarmManager 的 setAndAllowWhileIdle() 或 setExactAndAllowWhileIdle() 方法。

13.5 多窗口模式編程

??Android 7.0中引入了一個(gè)非常有特色的功能——多窗口模式,允許在同一個(gè)屏幕中同時(shí)打開兩個(gè)應(yīng)用程序。

13.5.1 進(jìn)入多窗口模式

??手機(jī)的導(dǎo)航欄上有3個(gè)按鈕:左邊 Back 按鈕、中間 Home 按鈕、右邊 OverView 按鈕,如下所示:

手機(jī)導(dǎo)航欄

??OverView 按鈕的作用是打開一個(gè)最近訪問過的活動(dòng)或任務(wù)的列表界面,進(jìn)入多窗口模式需要用到 OverView 按鈕,并且有兩種方式:

  • 在 OverView 列表界面長按任意一個(gè)活動(dòng)的標(biāo)題,將該活動(dòng)拖到屏幕突出顯示的區(qū)域,則可以進(jìn)入多窗口模式。

  • 打開任意一個(gè)程序,長按 OverView 按鈕,也可以進(jìn)入多窗口模式。

??多窗口模式效果如下:

上下分屏的多窗口模式
左右分屏的多窗口模式

??可以看出多窗口模式下,應(yīng)用界面縮小很多,編寫程序時(shí)要多考慮使用 match_parent 屬性、RecyclerView、ScrollView 等控件,適配各種不同尺寸的屏幕。

13.5.2 多窗口模式下的生命周期

??多窗口模式并不會(huì)改變活動(dòng)原有的生命周期,只是會(huì)將用戶最近交互過的那個(gè)活動(dòng)設(shè)置為運(yùn)行狀態(tài),而將多窗口模式下另外一個(gè)可見的活動(dòng)設(shè)置為暫停狀態(tài)。若這時(shí)用戶又去和暫停的活動(dòng)進(jìn)行交互,那么該活動(dòng)就變成運(yùn)行狀態(tài),之前處于運(yùn)行狀態(tài)的活動(dòng)變成暫停狀態(tài)。

??進(jìn)入多窗口模式時(shí)活動(dòng)會(huì)被重新創(chuàng)建,若要改變這一默認(rèn)行為,可以在 AndroidManifest.xml 中對(duì)活動(dòng)添加如下配置:

android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"

??添加這行配置后,不管是進(jìn)入多窗口模式還是橫豎屏切換,活動(dòng)都不會(huì)被重新創(chuàng)建,而是會(huì)將屏幕發(fā)生變化的事件通知到 Activity 的 onConfigurationChanged() 方法中。因此,若要在屏幕發(fā)生變化時(shí)進(jìn)行相應(yīng)的邏輯處理,那么在活動(dòng)中重寫 onConfigurationChanged() 方法即可。

13.5.3 禁用多窗口模式

??禁用多窗口模式的方法很簡單,只需在 AndroidManifest.xml 的<application>或<activity>標(biāo)簽中加入如下屬性即可:

android:resizeableActivity=["true"|"false"]

??其中,true 表示支持多窗口模式,false 表示不支持,若不配置這屬性默認(rèn)是 true。

??雖說 android:resizeableActivity 這個(gè)屬性的用法簡單,但這個(gè)屬性只適用于 targetSdkVersion 24 或更高的時(shí)候,若低于24則無效,可能會(huì)被告知此應(yīng)用在多窗口模式下可能無法正常工作,并進(jìn)入多窗口模式。

??Android 規(guī)定,若項(xiàng)目指定的 targetSdkVersion 低于24,并且活動(dòng)是不允許橫豎屏切換時(shí)是不支持多窗口模式的。因此針對(duì)上面的情況,就需要在 AndroidManifest.xml 的<activity>標(biāo)簽中配置如下屬性:

android:screenOrientation=["portrait"|"landscape"]

??其中,portrait 表示只支持豎屏,landscape 表示只支持橫屏。

??本篇文章就介紹到這。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,801評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,442評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,800評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,996評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,233評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,702評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,991評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容