這些小技巧
通過上面的這些文章,就把簡單的安卓項目總結了一遍,當然你說懂這些就可以做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"