Android基礎知識——你還應該掌握的高階技巧

2020-09-21 23:00:24

1.全域性獲得Context的技巧

在我們學習Android基礎知識的時候,你會發現在很多地方我們都會使用到Context,彈出Toast的時候需要,啟動活動的時候需要,傳送廣播的時候需要,運算元據庫的時候需要,使用通知的時候需要,等等等等。所以有時候在需要使用Context時,卻不知道該怎麼獲得Context將會是一件非常傷腦筋的事情。本節我們就來介紹一個全域性獲得Context的技巧。

使用步驟:

1.新建類繼承Application類,在其中獲取Context並定義一個用於外部獲取Context的方法。
2.給AndroidManifest.xml設定android:name屬性。也就是告知系統,當程式啟動時應該初始化MyApplication類,而不是預設的Application類。

範例:

//步驟一
public class MyApplication extends Application {
    private static Context context;

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

    public static Context getContext(){
        return context;
    }
}
//步驟二
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.temp">

    <application
        android:name=".MyApplication"//告知系統當程式啟動時初始化MyApplication類
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

這裡還有個小問題:在我們學習LitePal時我們也需要設定AndroidManifest.xml中的android:name屬性,那當你兩者都想要使用時該怎樣處理呢?其實解決方法也很簡單,我們只需在我們自己的Application中呼叫LitePal的初始化方法即可。

範例:

public class MyApplication extends Application {
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context=getApplicationContext();
        LitePal.initialize(context);
    }

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

2.使用Intent傳遞物件

Intent傳遞基礎型別的資料相信你已經比較熟悉了,可是如果我們想要用Intent傳遞一個物件的話那該怎樣處理呢?本節我們就來學習一下使用Intent傳遞物件。

2.1Serializable方式

使用步驟:

1.定義一個你想要傳遞的類,並讓該類繼承Serializable介面。
2.呼叫putExtra()方法向Intent中儲存物件,呼叫getSerializableExtra()方法從Intent中獲取物件。

Serializable詳解:Serializable是序列化的意思,表示將一個物件轉換成可儲存或可傳輸的狀態。序列化後的物件可以在網路上進行傳輸,也可以儲存到本地。

範例:

//步驟一
public class Student implements Serializable {
    String name;
    int age;

    public Student(String name,int age){
        this.name=name;
        this.age=age;
    }
}
//步驟二
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=(Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent(MainActivity.this,MainActivity2.class);
                Student student=new Student("Tom",18);
                intent.putExtra("Student",student);//向Intent中儲存物件
                startActivity(intent);
            }
        });
    }
}

public class MainActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent=getIntent();
        Student student= (Student) intent.getSerializableExtra("Student");//從Intent中獲取物件
        Toast.makeText(MainActivity2.this,student.name,Toast.LENGTH_SHORT).show();
    }
}

2.2Parcelable方式

使用步驟:

1.定義一個你想要傳遞的類,讓其繼承Parcelable介面並實現介面中的一些方法。
2.呼叫putExtra()方法向Intent中儲存物件,呼叫getParcelableExtra()方法從Intent中獲取物件。

Parcelable詳解:Parcelable方式的實現原理是將一個完整的物件進行分解,而分解後的每一部分都是Intent所支援的資料型別,這樣也就實現傳遞物件的功能了。

範例:

//步驟一
public class Student implements Parcelable {
    String name;
    int age;

    public Student(){
    }

    public Student(String name,int age){
        this.name=name;
        this.age=age;
    }

    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            Student student=new Student();
            student.name=in.readString();//讀取name(注意這裡的讀取順序要和寫入順序完全相同)
            student.age=in.readInt();//讀取int
            return student;
        }

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

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);//寫出name
        parcel.writeInt(age);//寫出age
    }
}
//步驟二
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=(Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent(MainActivity.this,MainActivity2.class);
                Student student=new Student("Tom",18);
                intent.putExtra("Student",student);//向Intent中儲存物件
                startActivity(intent);
            }
        });
    }
}

public class MainActivity2 extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        Intent intent=getIntent();
        Student student= (Student) intent.getParcelableExtra("Student");//從Intent中獲取物件
        Toast.makeText(MainActivity2.this,student.name,Toast.LENGTH_SHORT).show();
    }
}

3.客製化自己的紀錄檔工具

現在讓我們設想一個場景,你正在編寫一個比較龐大的專案,期間為了方便偵錯,在程式碼的很多地方都列印了大量的紀錄檔。最近專案已經基本完成了,但是卻有一個非常令人頭痛的問題,之前用於偵錯的那些紀錄檔,在專案正式上線之後仍然會照常列印,這樣不僅會降低程式的執行效率,還有可能將一些機密性的資料洩露出去。那可能會有人說我把列印紀錄檔的程式碼一行一行全部刪掉就行了,這顯然不是什麼好點子,不僅費事費力,而且以後你繼續維護這個專案時可能還會需要這些紀錄檔。本節我們就來學習該如何優雅的解決這個問題。

解決方法:

事實上這個問題的解決方法非常簡單,我們只需要定義一個自己的紀錄檔工具,並在裡面對列印紀錄檔條件加上限制條件即可。

範例:

public class LogUtil {

    private static final int VERBOSE=1;
    private static final int DEBUG=2;
    private static final int INFO=3;
    private static final int WARN=4;
    private static final int ERROR=5;
    private static final int NOTHING=6;
    private static int level=VERBOSE;//我們只需要更改level的值即可對不同等級的列印語句進行限制,而當我們不想列印任何紀錄檔時,我們只需將level的值設定為NOTHING即可。

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

4.建立定時任務

Android中的定時任務一般有兩種實現方式,一種是java API裡提供的Timer類,一種是使用Android的Alarm機制。而兩者的區別在於:Android手機會在長時間不操作的情況下自動讓CPU進入到睡眠狀態,這就有可能導致Timer中的定時任務無法正常執行,而Alarm則具有喚醒CPU的功能。(這裡要注意喚醒CPU和喚醒螢幕完全是兩個概念)

4.1Alarm機制

使用步驟:

1.獲取AlarmManager範例。
2.呼叫manager.set()方法建立定時任務。

範例:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button=(Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent=new Intent(MainActivity.this,MainActivity2.class);
                PendingIntent pendingIntent=PendingIntent.getActivity(MainActivity.this,0,intent,0);
                AlarmManager alarmManager= (AlarmManager) getSystemService(ALARM_SERVICE);//步驟一
                long time= SystemClock.elapsedRealtime()+1000;
                alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,time,pendingIntent);//步驟二
            }
        });
    }
}

manager.set()方法詳解:第一個引數同於設定工作型別,ELAPSED_REALTIME型:表示定時任務的觸發時間從系統開機開始算起,但不會喚醒CPU;ELAPSED_REALTIME_WAKEUP型別:表示定時任務的觸發時間從系統開機開始算起,且會喚醒CPU;RTC型別:表示定時任務的觸發時間從1970年1月1日0點開始算起,但不會喚醒CPU;RTC_WAKEUP型別:表示定時任務的觸發時間從1970年1月1日0點開始算起,且會喚醒CPU。第二個引數就是定時任務的觸發時間,以毫秒為單位(注意要與第一個引數相匹配)。第三個引數就是定時排程任務要執行的具體事件了。

注意:從Android4.4系統開始,Alarm任務的觸發事件會變得不準確,有可能會延遲一段時間後才會執行,當然這可不是Bug,這只是為了能讓手機更好的節省電量。但倘若你要求Alarm任務的執行時間必須準確無誤,那麼你只需將set()方法更改為setExact()方法即可。

4.2Doze模式

Doze模式是Android6.0系統新加入的一種省電模式。該模式下系統會對CPU,網路,Alarm等活動進行限制,從而延長電池的使用壽命。因此這種模式也就極大的影響了我們Alarm任務的執行時間。不過倘若你真的有非常特殊的需求,要求在Doze模式下Alarm任務也要必須正常執行,Android還是提供瞭解決方案的。我們只需呼叫AlarmManger的setAndAllowWhileIdle()或setExactAndAllowWhileIdle()方法即可讓你的Alarm任務在Doze模式下也能正常執行了。

5.多視窗模式程式設計

多視窗模式也就是我們現在經常說的分屏模式了。

5.1多視窗模式下的生命週期

當一個活動進入多視窗模式或橫豎屏切換時,該活動會重新建立。多視窗模式下正在與使用者互動的活動處於onResume狀態,另一個則處於onPause狀態。

另外,針對於進入多視窗模式時活動就會被重現建立,如果你想改變這一預設行為,只需在AndoridManifest.xml檔案中進行如下設定即可:

<activity android:name=".MainActivity"
    android:configChanges="orientation|keyboardHidden|screenSize|screenLayout">

加入了這行設定之後,不管是進入多視窗模式,還是橫豎屏切換,活動都不會被重新建立,而是會將螢幕發生改變的事件通知到Activity的onConfigurationChanged()方法當中。

5.2禁用多視窗模式

禁用多視窗模式的方法非常簡單,只需在AndroidManifest.xml的< application >或< activity >標籤下加入如下屬性即可:

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

其中,true表示應用支援多視窗模式,false表示應用不支援多視窗模式,該屬性預設為true。

不過上面的方法只能在targetSdkVersion為24以上時才會有用,否則這個屬性是無效的。不過Android規定,當targetSdkVersion為24以下,並且活動不允許橫豎屏切換,那麼該應用也將不支援多視窗模式。設定方法如下:

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

其中,portrait表示活動只支援豎屏,landscap表示活動只支援橫屏。