由下圖可知
onSaveInstanceState()是當生命週期進入onStop()時會被呼叫的callback
所以每當app進入後台時,便會執行onSaveInstanceState(),可視爲一個安全措施
onSaveInstanceState()可打包少量的資訊,於當app恢復時使用
在MainActivity覆寫
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Timber.i("onSaveInstanceState Called")
}
執行app並收進後台
logcat顯示onSaveInstanceState()確實在onStop()後呼叫
fun onSaveInstanceState(outState: Bundle)
參數outState是Bundle型態,此型態爲key-value的集合
key固定爲String,value可存放基本型態
因爲系統將bundle存放在RAM內,且有容量限制
通常存放的容量應遠小於100k,否則易導致crash
在MainActivity外層,宣告幾個固定常數字串作爲key
const val KEY_REVENUE = "revenue_key"
const val KEY_DESSERT_SOLD = "dessert_sold_key"
const val KEY_TIMER_SECONDS = "timer_seconds_key"
class MainActivity : AppCompatActivity() {...}
onSaveInstanceState()內將revenue(價格,整數型態)加入bundle
outState.putInt(KEY_REVENUE,revenue) //putInt(key,value)
//以及數量,timer計數等都加入
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
outState.putInt(KEY_TIMER_SECONDS, dessertTimer.secondsCount)
以上就完成將資料打包的動作
接著看onCreate()
override fun onCreate(savedInstanceState: Bundle?) {...}
onCreate()的預設用法一直都是有bundle參數
所以前面打包好後,就可以到onCreate()內取出來
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
}
常用模式爲檢查bundle不爲null再往下執行if (savedInstanceState != null)
然後加入bundle是putInt,取出就是getInt
執行app,異動內容後,將app移到後台並使用terminal關閉
檢查app的內容狀態有保留(實際有時還是會歸零,不知道是否因存在記憶體的bundle也被清掉)
但可以看到進後台時,是甜甜圈的狀態,回前台又變成杯子蛋糕
原因是甜點圖片由這個函數showCurrentDessert()依照dessertsSold的數值,控制現在該顯示哪張圖片
但這個函數是點擊圖片才觸發,因此當app回到前台尚未點擊時
就須在if (savedInstanceState != null)檢查中加入
若bundle有資料,表示showCurrentDessert()也該執行
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
dessertTimer.secondsCount =
savedInstanceState.getInt(KEY_TIMER_SECONDS, 0)
showCurrentDessert()
}
額外參考: If the activity is being re-created, the onRestoreInstanceState() callback is called after onStart(), also with the bundle. Most of the time, you restore the activity state in onCreate(). But because onRestoreInstanceState() is called after onStart(), if you ever need to restore some state after onCreate() is called, you can use onRestoreInstanceState()
有一些情況屬於裝置重大的配置改變時,activity / fragment都會被destroy然後重新onCreate
例如改變手機的語系設定,外接螢幕或鍵盤,旋轉手機等等
因爲這些配置改變時,app的內容可能有需要跟著調整的地方
系統爲了符合app的顯示,最便利的方式就是重新onCreate()
所以當預期app可能會需要在配置改變時,仍然保持相同狀態
就可利用onSaveInstanceState(),將所需的資料存入bundle
以便在重新onCreate()時,恢復先前狀態
p.s
在使用模擬機測試時,發現有時調回app,有存入bundle,但還是顯示歸零
這部分還不太清楚什麼問題,請教導師說可先將手機所有其它在後台的程式都清除再試
就沒有再遇到
開一個Google sheet,工作表1更名爲Sheet1
權限設定公開檢視
連結網址/ /的中間那串就是這個資料表的SheetID
https://docs.google.com/spreadsheets/d/1uoTGeYfi8SdhxPgS3nFWDb-3-KJMibDpErfIm4c8ZH0/edit#gid=0
例如:1uoTGeYfi8SdhxPgS3nFWDb-3-KJMibDpErfIm4c8ZH0
到 https://console.developers.google.com/flows/enableapi?apiid=sheets.googleapis.com
建立Google Api 參考
如果出現OAuth的東西先不用理它
選建立憑證
選API金鑰
完成後就會看到出現API金鑰
可以設定限制
有了憑證權限就可以將如下網址
與資料範圍{Range},{Sheet ID},{API Key}合併
一起以瀏覽器開啓,就會看到JSON格式
https://sheets.googleapis.com/v4/spreadsheets/{Sheet ID}/values/{Range}?key={API Key}
也可以丟到 https://jsoneditoronline.org/ 解析
開一個android studio專案,build.gradle加入dependencies
//Import okHttp dependencies
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
implementation 'com.squareup.okhttp3:okhttp:3.4.1'
//Import Retrofit dependencies
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:retrofit:2.1.0'
網路存取權限也別忘了,要加<uses-permission android:name="android.permission.INTERNET" />
有了JSON就再使用這個Kotlin data class from JSON
套件協助轉成data class吧
建立一個BookData.kt檔,Generate
data class BookData(
@SerializedName("majorDimension")
val majorDimension: String = "", // ROWS
@SerializedName("range")
val range: String = "", // Sheet1!A2:B4
@SerializedName("values")
val values: List<List<String>> = listOf()
)
然後建立api
internal object APIClient {
lateinit var retrofit: Retrofit
val client: Retrofit
get() {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(2, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.build()
retrofit = Retrofit.Builder()
.baseUrl("https://sheets.googleapis.com/v4/spreadsheets/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit
}
val apiInterface: ApiInterface = APIClient.client.create(ApiInterface::class.java)
}
interface ApiInterface {
@GET("{SheetId}/values/{Range}?key={API Key}")
fun getBooks() : Call<BookData>
}
到MainActivity呼叫
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val call = apiInterface.getBooks()
call.enqueue(object : Callback<BookData> {
override fun onResponse(call: Call<BookData>, response: Response<BookData>) {
Log.d("Success!", response.toString())
if (response.isSuccessful) {
response.body()?.values?.forEach { bookList ->
Log.d("api", "${bookList[0]} ${bookList[1]}")
}
}
}
override fun onFailure(call: Call<BookData>, t: Throwable) {
Log.e("Failed Query :(", t.toString())
}
})
}
}
取得Google sheet的資料了