Android學習--還有一些小技巧

這些小技巧


      通過上面的這些文章,就把簡單的安卓項目總結了一遍,當然你說懂這些就可以做Android開發的話還是不行的,欠缺的還有很多,但欠缺的這些我們有只能在工作中去總結以及不斷的提高,這篇文章我們還有一些小技巧需要我們總結一下,然後在後面準備做一個完整的實驗項目,讓我們把學的這些串聯起來,這篇我們將說說下面這些技巧:

      一、獲取全局Context

      二、使用Intent傳遞對象

1、Serializable方式

           2、Parcelable方式

三、日誌控制

      四、創建定時任務

  五、聊聊Doze模式

      六、多窗口

      七、禁止多窗口模式

八、lambda表達式    這個表達式是JAVA 8 的新特性,我們直接在後面完整的Demo中使用,用到的時候再具體的說明

獲取全局Context


      這裏我們考慮這樣一個問題,我們再一個類中進行了一些異步操作,完了之後我們需要一個Toast提示,這時候我們需要Context,那我們有那麼獲取Context。

      首先就有這樣一種,我們直接在初始化這個類的時候傳遞一個Context,的確這樣是能解決問題的,但這不是最好的解決問題的辦法,最好的辦法是我們獲取一個全局的Context,下面我們總結如何獲取一個全局的Context。

package com.example.skotc.servicedemo;

import android.app.Application;
import android.content.Context;
/**
 * Created by skotc on 2018/8/20.
 */
public class MyApplication extends Application {

   private static Context context;
    @Override
    public void onCreate() {
        super.onCreate();
        
        context = getApplicationContext();
    }
    public static Context getContext() {
        return context;
    }
}

      上面的代碼我們就創建了一個MyApplication繼承自Application,然後再以後的使用中我們就可以直接調用這個方法得到全局的Context, MyApplication.getContext()方法獲取得到Context。

      還有一點需要我們注意一下的,就是在創建了MyApplication之後我們還是需要在AndroidManifest.xml中聲明一下。具體的代碼如下,主要的就是這句: android:name=".MyApplication"

 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:name=".MyApplication"
        >
        <activity
            android:name=".ServiceMainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
</application>

使用Intent傳遞對象


      Intent相信我們都比較熟悉了,我們可以使它來啓動活動,發送廣播,啓動廣播等,在進行上述操作的時候,我們還可以在Intent中添加一些附加數據,已達到傳值的效果,比如我們見過的調用 putExtra(鍵,值)方法來添加要傳遞的數據,之後通過調用 getIntent().getStringExtra(鍵)來獲取我們傳遞的值,通過這種方法我們能傳遞的對象類型是有限的,也就常見的類型,那我們有沒有想過,要是需要專遞的是一個自定義的對象的時候呢,我們該怎樣做?

      下面我們就討論一下這個問題:

      1、Serializable方式 (序列化)

      Serializable是序列化的意思,表示將一個對象轉換成可存儲或者可傳輸的狀態,序列化後的對象可以在網絡上進行傳輸,也可以存儲在本地,至於序列化的方法也是很簡單,只需要讓一個類去實現Serializable接口就可以。

      比如我們實現了一個person類,讓它實現Serializable接口:

class person implements Serializable{
    
    private String name;
    private int age;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

      接下來我們看看這個自定義對象的傳遞以及獲取:

person zhangxu = new person();
zhangxu.setAge(18);
zhangxu.setName("tiancia");

//傳遞
Intent intent = new Intent(ServiceMainActivity.this,SecondActivity.class);
intent.putExtra("person", zhangxu);
startActivity(intent);
// 獲取
person zhangxu2 = (person) getIntent().getSerializableExtra("person");

      一句話總結:我們之所以能將我們自定義的類在Intent中傳遞就是因爲我們自定義爲類實現了 Serializable 接口。

Parcelable


      Parcelable方式的實現原理是將一個完整的對象進行分解,而分解後的每一部分都將是 Intent 所支持的數據類型,這樣也就實現傳遞對象的功能。

      接下來我們修改我們的額person類,修改這個類的注意事項我們在代碼中都有加註釋

/*
* 
* 實現Parcelable接口
* 就要重寫裏面的兩個方法
* describeContents
* writeToParcel
* 
* */

class person implements Parcelable{

    private String name;
    private int age;

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

    // 把數據寫入到parcel中 
    @Override
    public void writeToParcel(Parcel parcel, int i) {
       
        parcel.writeString(name);
        parcel.writeInt(age);
        
    }
    
    // 我們還必須在person類中創建一個CREATOR常量,這裏創建了一個Parcelable.Creator接口的實現
    // 並且將泛型類型指定爲person,接着重寫裏面的兩個方法
    // createFromParcel  這個方法中讀取剛纔存入的字段
    // newArray          
    public static final Parcelable.Creator<person>CREATOR = new Parcelable.Creator<person>(){

        @Override
        public person createFromParcel(Parcel parcel) {
            person person = new person();       // 讀取數據
            person.name = parcel.readString();
            person.age = parcel.readInt();
            return person;
        }

        @Override
        public person[] newArray(int i) {
            return new person[i];
        }
    };
}

      說說它的傳遞方式,傳入時候和我們之前寫的Serializable是一樣的,就不在重複,只是在讀取的時候,有一點需要我們注意一下,就是方法名改變了:

  person zhangxu3 = (person) getIntent().getParcelableExtra("person");

它們倆的區別:

serializable的方式比較簡單,但由於會把整個對象進行序列化,因此效率會比Parcelable低一些,所以在通常情況下我們還是建議使用Parcelable方式!

日誌控制


      在iOS中我們經常有用到這個日誌控制的問題,在安卓中也是,就是在debug階段我們需要大量的日誌,但是在release狀態我們是不需要的,日誌不僅僅會增加程序運行的成本,還會泄漏一些重要的信息,所以在編譯release狀態我們是需要控制日誌打印的,在安卓中我們可以寫這樣的一個類來進行處理。

class LogUntil{

    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 leven  = VERBOSE;

    public static void v(String tag,String msg){

        if (leven<=VERBOSE){
            Log.d(tag,msg);
        }
    }
    public static void d(String tag,String msg){

        if (leven<=DEBUG){
            Log.d(tag,msg);
        }
    }
    public static void i(String tag,String msg){

        if (leven<=INFO){
            Log.d(tag,msg);
        }
    }
    public static void w(String tag,String msg){

        if (leven<=WARN){
            Log.d(tag,msg);
        }
    }
    public static void e(String tag,String msg){

        if (leven<=ERROR){
            Log.d(tag,msg);
        }
    }
}

      上面的這段代碼就是我們常用的日誌控制,在我們要發佈的時候,我們設置leven的值爲NOTHING的時候我們的日誌也就不見了!和我們iOS的理解方式是一樣的,我們iOS中會用到DEBUG這個變量,具體的我也就不再多說了,有興趣的可以自己找找這方面的問題,我們直說安卓的。

創建定時任務


      在Android中,實現定時器的任務是有兩種方式的,一種是使用Java API 提供的Timer類,一種是使用Android的Alarm機制,這令中方式在大多數情況下都能實現類似的效果,但是Timer有一個致命的短板,它並不適用於那些長期在後臺運行的定時器任務,我們都知道爲了能讓電池更加耐用,每一種手機都會有自己的休眠策略,Android手機在長時間不操作的情況下會讓CPU處於睡眠狀態,就會導致Timer中的定時器任務無法正常運行,而Alarm則具有喚醒CPU的功能,它保證在大多數情況下需要執行任務的時候CPU都能正常運行。這裏需要注意喚醒CPU和喚醒屏幕完全不是同一個概念!不要混淆。

      下面我們用代碼寫一個Alarm的實際例子:

class LongRunningService extends Service{

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(new Runnable() {
            @Override
            public void run() {

            }
        }).start();

        // 獲取AlarmManager對象
        AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE);
        // 一個小時的毫秒數
        int anHour = 60*60*1000;
        // SystemClock.elapsedRealtime方法表示系統開機至今經歷的毫秒數
        // SystemClock.currentTimeMillis方法表示獲取1970年1月1日零時至今所經歷的毫秒數
        long triggerAtTime = SystemClock.elapsedRealtime()+anHour;

        // 指定處理定時任務的服務爲LongRunningService 最後在調用set方法
        Intent i = new Intent(this,LongRunningService.class);
        PendingIntent pendingIntent = PendingIntent.getService(this,0,i,0);


        //AlarmManager.ELAPSED_REALTIME_WAKEUP 表示讓定時任務的觸發時間從系統開機算起,但是會喚醒CPU
        //AlarmManager.ELAPSED_REALTIME 表示讓定時任務的觸發時間從系統開機算起,但是不會喚醒CPU
        //AlarmManager.RTC          表示讓定時任務的觸發時間從1970,1,1算起,但是不會喚醒CPU
        //AlarmManager.RTC_WAKEUP   表示讓定時任務的觸發時間從1970,1,1算起,但是會喚醒CPU
        //triggerAtTime 時間
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
        return super.onStartCommand(intent, flags, startId);
    }
}

聊聊Doze模式


      我們說說這個Doze模式,說說到底什麼是Doze模式。當用戶的設備是6.0或者以上系統的時候,如果該設備沒有接電源,且並木關閉了一段時間之後,就會進入Doze模式。在Doze模式下,系統會對CPU,網絡,Alarm等活動進行限制,從而延長電池的使用壽命。當然系統也不會一直處於Doze模式,而是間接性的退出Doze模式一小段時間,而在這一下歐丹時間中,應用就可以完成他們的同步操作,Alarm任務等等,

      接下來看看在Doze模式下那些功能會受到影響:

      1、網絡訪問被限制

      2、系統忽略喚醒CPU或者屏幕操作

      3、系統不再執行WIFI掃描

      4、系統不再執行同步服務

      5、Alarm任務將會在下次退出Doze模式的時候執行 

多窗口


      Android在7.0之後導入了多窗口模式,在這裏我們可以大概的學習一下多窗口模式。

      在這裏我們說一下,在多窗口模式下並不會改變活動原有的生命週期,只是會將用戶最近交互過的那個活動設置爲運行狀態,而將多窗口模式下另外一個可見的活動設置爲暫停狀態,如果這時候用戶又和暫停的活動進行交互,那麼該活動就會進入運行狀態,之前處於運行狀態的活動變成暫停狀態。

      前面我們說到在多窗口模式下,活動的生命週期是不會發生改變的,那麼有一些問題我們就可以隨之考慮一下:

      比如說,在多窗口模式下,用戶任然處於可以看到暫停狀態的應用,那麼像視頻播放之類的應用在此時就應該是繼續播放視頻纔對,因此,我們最好不要在活動的onPause方法中處理視頻播放器的暫停邏輯,而是應該在onStop()方法中處理,並且在onStart方法中回覆視頻的播放。

      另外,針對進入多窗口模式時候,活動會被重新創建,如果你想改變這一默認行爲,可以在 Androidmainfest.xml中進行如下配置:

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

      加入這個配置之後,不管是進入多窗口模式,還是橫豎屏切換,活動都不會被重新創建,而是會將屏幕發生變化的事件通知到Activity的onConfigurationChanged()方法中,所以你要是想在屏幕發生改變的時候進行相應的邏輯處理,那麼在活動中重寫onConfigurationChanged()方法即可。

禁止多窗口模式


      上面我們說了一些關於多窗口模式的一些問題,現在我們再想一個場景,如果我們做的是遊戲,要是進入了多窗口模式是不是很尷尬,總不是一邊發微信一遍玩遊戲的吧,看着自己GG,當然我們也有辦法避免應用進入多窗口模式,禁止的方式也很簡單:

      Androidmainfest.xml 中這樣配置:

      android:resizeableActivity="false"  true表示支持,false表示禁止

      這樣就OK了嗎?其實還有一個問題需要我們考慮一下這個問題,這個屬性是在我們指定 targetSdkVersion 大於等於24的時候纔有效的,那小於24呢?沒有這個屬性我們怎麼處理呢?我們再這裏說一種解決方案:

      Android規定,如果項目指定的targetSdkVersion低於24,並且活動是不允許橫豎屏切換的,那麼該應用也將不支持多窗口模式。

      默認情況下,我們的應用是支持橫豎屏切換的,如果想想要讓應用不允許橫豎屏切換,那麼就需要在 Androidmainfest.xml的<activity>標籤中加如下配置:

      其中 portrait 表示豎屏   landscape 表示橫屏

android:screenOrientation="portrait"
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章