A. 在一個Activity的java程序中,具體看問題描述
1、Intent
Intent是各個組件之間交互的一種重要方式,它不僅可以指明當前組件想要執行的動作,而且還能在各組件之間傳遞數據。Intent一般可用於啟動Activity、啟動Service、發送廣播等場景。
Intent大致可分為2中:
1、顯示Intent
2、隱式Intent
1.1、顯示Intent打開Activity
fun openActivity(){
val intent = Intent(this, KotlinFirstActivity::class.java)
intent.putExtra("param", "testParams")
startActivity(intent)
}
注意:KotlinFirstActivity::class.java就相當於Java中的KotlinFirstActivity.class。
1.2、隱式Intent打開程序內的Activity
相比於顯示Intent,隱式Intent並不指明啟動那個Activity而是指定了一系列的action和category,然後交由系統去分析找到合適的Activity並打開。
什麼是合適的Activity,其實就是和隱式Intent中指定的action和category完全匹配的Activity,而action和category我們可以在AdnroidManifest中指定。
在標簽中配置了action和category,只有和隱式Intent中的action和category完全匹配才能正常的打開該頁面。
val intent = Intent("com.example.abu.alertdialogdemo.ACTION_START")
startActivity(intent)
不是說action和category要完全匹配才能打開頁面嗎?這是因為android.intent.category.DEFAULT是一種默認的category,在調用startActivity()時會自動將這個category添加到Intent中。所以在Manifest中一定不要忘記配置這個默認的category:android.intent.category.DEFAULT,否則會報錯。
還有一點需要注意:Intent中只能添加一個action,但是可以添加多個category。
1.3、隱式Intent打開程序外的Activity
比如我們要打開系統的瀏覽器
fun openWeb(view: View) {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www..com")
startActivity(intent)
}
Intent.ACTION_VIEW是系統內置的動作,然後將https://www..com通過Uri.parse()轉換成Uri對象,傳遞給intent.setData(Uri uri)函數。Kotlin中intent.data=Uri.parse("https://www..com")就相當於Java中的intent.setData(Uri.parse("https://www..com")),這是Kotlin中的語法糖。
與此對應在標簽中配置一個標簽,用於更精確的指定當前Activity能夠相應的數據。標簽中主要可以配置一下內容:
1、android:scheme:用於指定數據的協議部分,如https
2、android:host:用於指定數據的主機名部分,如www..com
3、android:port:用於指定數據的埠,一般緊隨主機名後
4、android:path:用於指定數據的路徑
5、android:mimeType:用於指定支持的數據類型
只有當標簽中指定的內容和Intent中攜帶的data完全一致時,當前Activity才能響應該Intent。下面我們通過設置data,讓它也能響應打開網頁的Intent。
我們在ThirdActivity的中配置當前Activity能夠響應的action是Intent.ACTION_VIEW的常量值,而category指定了默認的category值,另外在標簽data中我們通過android:scheme="https"指定了數據的協議必須是https。另外由於AndroidStudio認為能夠響應ACTION_VIEW的Activity都應該加上BROWSABLE的category,否則會報出警告。加上BROWSABLE的category是為了實現deep link 功能和目前學習無關,所以我們在intent-filter標簽上添加tools:ignore="AppLinkUrlError"忽略警告。
然後我們就能通過隱式Intent的方法打開ThirdActivity了,代碼如下:
val intent= Intent(Intent.ACTION_VIEW)
intent.data=Uri.parse("https:")
startActivity(intent)
除了指定android:scheme為https我們也能隨意指定它的值,只需要保證AndroidManifest中設置的值和Intent中設置的data相對應即可。
2、Activity的生命周期
Activity類中定義了7個回調方法,覆蓋了Activity聲明周期的每一個環節。
1、onCreate(): 在Activity第一次創建時調用
2、onStart():在Activity可見但是沒有焦點時調用
3、onResume():在Activity可見並且有焦點時調用
4、onPause():這個方法會在准備啟動或者恢復另一個Activity時調用,我們通常在該方法中釋放消耗CPU的資源或者保存數據,但在該方法內不能做耗時操作,否則影響另一個另一個Activity的啟動或恢復。
5、onStop():在Activity不可見時調用,它和onPause主要區別就是:onPause在失去焦點時會調用但是依然可見,而onStop是完全不可見。
6、onDestory():在Activity被銷毀前調用
7、onRestart():在Activity由不在棧頂到再次回到棧頂並且可見時調用。
為了更好的理解Activity的生命周期,看下圖
Activity生命周期.png
3、體驗Activity的生命周期
下面我們通過實例更直觀的看下Activity的生命周期過程。
1、打開FirstActivity,生命周期過程如下
FirstActivity onCreate
FirstActivity onStart
FirstActivity onResume
2、在FirstActivity中打開SecondActivity,生命周期過程如下
FirstActivity onPause
SecondActivity onCreate
SecondActivity onStart
SecondActivity onResume
FirstActivity onStop
可以看到在打開SecondActivity時FirstActivity的onPause會執行,所以在onPause中是不能做耗時操作的,否則會影響SecondActivity的打開。
3、按返回鍵回到FirstActivity,生命周期過程如下
SecondActivity onPause
FirstActivity onRestart
FirstActivity onStart
FirstActivity onResume
SecondActivity onStop
SecondActivity onDestroy
可以看到在返回FirstActivity時會調用SecondActivity的onPause,如果SecondActivity的onPause中做了耗時操作的話,那麼也會影響Activity的返回。而且當FirstActivity再次回到棧頂時會調用其onRestart,此時並不會執行onCreate因為FirstActivity並沒有銷毀。
4、Activity被回收了時的生命周期
現在描述一種場景:打開ActivityA,然後在ActivityA的頁面中打開ActivityB,此時ActivityA不在棧頂了如果內存不足可能會被回收,此時從ActivityB再回到ActivityA,下面描述下整個過程的生命周期。
1、打開ActivityA,執行的生命周期
ActivityA onCreate
ActivityA onStart
ActivityA onResume
2、打開ActivityB執行的生命周期
ActivityA onPause
ActivityB onCreate
ActivityB onStart
ActivityB onResume
ActivityA onStop
3、此時因內存不足,導致ActivityA被回收了,並返回ActivityA
ActivityA onDestory
ActivityB onPause
ActivityA onCreate
ActivityA onStart
ActivityA onResume
ActivityB onStop
從上面可以看出在ActivityA被回收後再次回到ActivityA時不再調用ActivityA的onRestart了,而是調用了ActivityA的onCreate,因為ActivityA已經被銷毀了。
5、Activity被銷毀,其中的臨時數據怎麼辦
ActivityA被銷毀了,這也就說明其中的臨時數據也會丟失了,比如ActivityA中有一個EditText,我們在其中輸入了一段文字,然後啟動ActivityB,此時由於內存緊張處於停止狀態的ActivityA被回收了,返回到ActivityA你會發現EditText中輸入的文字不見了,因為ActivityA重新創建了。
為此Activity提供了onSaveInstanceState方法,該方法能確保在被回收之前被調用,我們就能在onSaveInstanceState方法中進行臨時數據的保存,然後我們可以在onCreate(savedInstanceState: Bundle?)中利用savedInstanceState進行數據的恢復。Activity被回收重新創建時onCreate(savedInstanceState: Bundle?)中的savedInstanceState不再為null否則為null,其中帶有在savedInstanceState中保存的所有數據。
具體代碼如下:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("tag", "$tag onCreate")
//恢復數據
val tempData = savedInstanceState?.getString("data") ?: ""
et.setText(tempData)
}
//保存數據
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
Log.e("tag", "$tag onSaveInstanceState")
val tempData = et.text.toString().trim()
outState?.putString("data", tempData)
}
6、onSaveInstanceState()調用時機
1、用戶按下HOME鍵
2、長按HOME鍵進入其他程序
3、按下電源鍵關閉屏幕
4、從ActivityA跳轉到一個新的Activity
5、屏幕方向切換時,如從豎屏變成橫屏
總而言之,onSaveInstanceState()是在你的Activity有可能在未經允許的情況下被回收時調用,Activity被銷毀之前onSaveInstanceState()肯定會被觸發,我們可以onSaveInstanceState()中保存臨時數據,持久化的數據可以在onPause()中保存。
另外,雖然屏幕方向切換時,會造成Activity的重建會調用onSaveInstanceState(),但是不建議使用onSaveInstanceState()進行數據的保存,我們可以禁止屏幕的旋轉或禁止屏幕旋轉時Activity的重建。
禁止屏幕旋轉
可以在Mainifest中設置屏幕固定為豎屏
android:screenOrieritation="portrait"
禁止屏幕旋轉時Activity的重建
android:configChanges="orientation丨keyboardHidden丨screenSize"
7、Activity的啟動模式
啟動模式分為4種:
1、standard
2、singleTop
3、singleTask
4、singleInstance
我們可以在Manifest中通過android:launchMode指定啟動模式。
7.1、standard模式
如果不顯示指定啟動模式,那麼Activity的啟動模式就是standard,在該模式下不管Activity棧中有無該Activity,均會創建一個新的Activity並入棧,並處於棧頂的位置。
7.2、singleTop模式
1、要啟動的Activity位於棧頂
在啟動一個ActivityA時,如果棧頂的Activity就是ActivityA,那麼就不會重新創建ActivityA,而是直接使用,此時並不會調用ActivityA的onCreate(),因為並沒有重新創建Activity,ActivityA的生命周期如下:
ActivityA onPause
ActivityA onNewIntent
ActivityA onResume
可以看到調用了onNewIntent(intent: Intent?),我們可以在onNewIntent(intent: Intent?)中通過intent來獲取新傳遞過來的數據,因為此時數據可能已經發生了變化。
2、要啟動的Activity不在棧頂
雖然我們指定了ActivityA的啟動模式為singleTop,但是如果ActivityA在棧中但是不在棧頂的話,那麼此時啟動ActivityA的話會重新創建一個新的ActivityA並入棧,此時棧中就有2個ActivityA的實例了。
7.3、singleTask模式
如果准備啟動的ActivityA的啟動模式為singleTask的話,那麼會先從棧中查找是否存在ActivityA的實例:
場景一、如果存在則將ActivityA之上的Activity都出棧,並調用ActivityA的onNewIntent()。
場景二、如果ActivityA位於棧頂,則直接使用並調用onNewInent(),此時和singleTop一樣。
場景三、 如果棧中不存在ActivityA的實例則會創建一個新的Activity並入棧。
場景一:ActivityA啟動ActivityB,然後啟動ActivityA,此時生命周期過程:
ActivityB onPause
ActivityA onRestart
ActivityA onStart
ActivityA onNewIntent
ActivityA onResume
ActivityB onStop
ActivityB onDestroy
此時ActivityA不在棧頂,ActivityA之上有ActivityB,所以在啟動ActivityA時ActivityA之上的ActivityB會出棧,ActivityA將置於棧頂,所以ActivityA的onRestart和ActivityB的onDestory會執行。
場景二:ActivityA啟動ActivityA,此時生命周期過程:
ActivityA onPause
ActivityA onNewIntent
ActivityA onResume
此時ActivityA位於棧頂,此時singleTask和singleTop作用一樣,都是直接使用ActivityA,並且會調用ActivityA的onPause、onNewIntent、onResume。
場景三:ActivityA啟動ActivityB,此時生命周期過程:
ActivityA onCreate
ActivityA onStart
ActivityA onResume
7.4、singleInstance模式
不同於另外3個模式,指定singleInstance模式的Activity會啟動一個新的返回棧來管理這個Activity(其實如果singleTask模式指定了不同的taskAffinity,也會啟動一個新的返回棧),這么做的意義是什麼?
想像一個場景:如果我們程序內的一個Activity是允許其他程序訪問的,如果想實現其他程序和我們程序能共享這個Activity實例,該怎麼實現呢?使用前面3中模式是無法做到的,因為每個應用程序都有自己的返回棧,同一個Activity在不同返回棧中肯定都創建了新的實例,而使用singleInstance就可以解決這個問題。在這種模式下,會有一個單獨的返回棧來管理這個Activity,無論哪個應用程序來訪問這個Activity,都在同一個返回棧中,也就解決了共享Activity實例的問題。
為了更好的理解下面我們實戰一下:
1、將ActivityB的啟動模式修改為singleInstance
android:name=".ActivityB"
android:launchMode="singleInstance" />
2、在ActivityA、ActivityB、ActivityC的onCreate中列印taskId
##ActivityA
val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("TAG", "$tag taskId=$taskId")
}
##ActivityB
val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(activity_second)
Log.e("TAG", "$tag taskId=$taskId")
}
##ActivityC
val tag = javaClass.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(activity_c)
Log.e("TAG", "$tag taskId=$taskId")
}
在Kotlin中javaClass是當前實例的Class對象,相當於Java的getClass().
在Kotlin中ActivityB::class.java是獲取ActivityB類的Class對象,相當於Java中的ActivityB.class。
3、啟動ActivityA->啟動ActivityB->啟動ActivityC,taskId列印結果如下:
ActivityA taskId=4163
ActivityB taskId=4164
ActivityC taskId=4163
可以看到ActivityB的taskId是不同於ActivityA 和ActivityC的,這也說明了ActivityB 是在一個單獨的棧中的,並且返回棧中只有這一個Activity。
4、在ActivityC中按返回鍵,返回到ActivityA,再按返回鍵返回到ActivityB,這是為什麼呢?其實很好理解:ActivityA 和ActivityC 在同一個返回棧中,在ActivityC 中按返回鍵ActivityC 出棧,此時ActivityA就位於棧頂了,ActivityA就展示在界面上了,在ActivityA中再按返回鍵,這是當前的返回棧已經空了,於是就展示了另一個返回棧棧頂的ActivityB了。
————————————————
版權聲明:本文為CSDN博主「保瓶兒」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_31211457/article/details/114052641
B. 為什麼支付寶付完款自動跳轉到了淘寶 怎樣關閉
【PConline資訊】今天有蘋果iPhone用戶反饋,如果在支付寶內嘗試打開淘寶APP,會自動跳轉到另外一個APP,彷彿被「劫持」了一樣。
支付寶官方回應稱,經過支付寶和淘寶技術團隊排查發現,這個APP使用了和淘寶同樣的URLScheme(類似URL網址),從而干擾iOS系統判斷,出現跳轉錯誤。
只有安裝了這個APP的iPhone手機才會出現這一跳轉錯誤問題,Android不受影響。
用戶可以手動關閉這個被打開的APP,將其卸載,就能恢復正常。
支付寶表示,已經與該APP緊急溝通,希望其不要再使用這樣的途徑來引導用戶跳轉。
事實上,這一問題的根源在於蘋果iOS系統不完善,允許不同的APP設置同一個URLScheme,事實上很多APP都曾受到困擾。
C. 自定義URL Scheme啟動應用
在使用手機時,經常會出現一種場景,當點擊某個鏈接時,會啟動已經安裝的某個應用來完成接下來的流程。比如當點擊網頁中一個淘寶的購物鏈接時,如果手機安裝了淘寶客戶端,便會導致客戶端被喚醒。
iOS中可以通過自定義URL Scheme機制來實現這種跳轉,從而帶來更好的用戶體驗。
URL Scheme跳轉機制非常簡單,只需要修改info.plist增加一個鍵值即可。
按圖中的方式創建URL Scheme,在屬性字典中添加了一個URL types鍵值對,設置內容可以參考圖中的設置方式。現在以xml文件方式打開info.plist文件,可以看出對應的信息如下:
完成上述操作後,在模擬器或真機上啟動應用,然後打開safari,在地址欄中輸入 usd:// 或 urlsd:// ,系統會彈出提示框,提示是否跳轉到應用,如果點擊確認,便會跳到自己的應用。
只要URL的scheme與應用中定義的scheme相同,便可以從外部(比如safari)打開應用。當應用打開時,該應用的方法 - application:handleOpenURL: 會被調用,而在該方法中能夠獲取到完整的URL,因此外部應用能夠通過URL向被打開的應用傳遞參數。
下圖為一個http協議的URL,其scheme為http。
比如本例中在瀏覽器中輸入 usd://scheme.demo/dir/redirect?sku=123&lang=en ,此時應用會被打開。在方法 - application:handleOpenURL: 中設置斷點,可以觀察到如下信息:
此時通過解析 query 欄位,應用便可獲取外部應用傳入的參數。
D. android用什麼樣的方式可以在用戶點擊鏈接的時候自動打開app
網頁中插入鏈接或者跳轉至鏈接:
<a href="m://my.com/">打開app</a>
在AndroidManifest的清單文件里的intent-filte中加入如下元素:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="my.com"
android:scheme="m" />
</intent-filter>
E. URL scheme啟動Android應用,原生Android瀏覽器解析不正確
1、自定義URL Scheme:
創建一個activity並加上一個<intent-filter>(如果該activity是包含其他<intent-filter>,則需新建一個<intenf-filter>,不能在原有filter上添加),內容為:
<action android:name="android.intent.action.VIEW"/><!-- 若刪除,使用startActivity啟動android.content.ActivityNotFoundException,使用HTMLViewer啟動找不到網頁-->
<category android:name="android.intent.category.BROWSABLE"/><!-- 若刪除,使用startActivity啟動ok,使用HTMLViewer啟動找不到網頁 -->
<category android:name="android.intent.category.DEFAULT"/><!-- 若刪除,使用startActivity啟動android.content.ActivityNotFoundException,使用HTMLViewer啟動找不到網頁-->
<data android:scheme="myapp"/><!-- scheme的值可自定義 -->
2、通過URL Scheme啟動Android應用
方式一:通過代碼訪問:Intent intent = new Intent();
/**parse的參數值說明如下
* 只寫myapp,啟動android.content.ActivityNotFoundException
* 寫myapp://12,成功
* 寫myapp://da?sd=ad,成功
*/
intent.setData(Uri.parse("myapp://12"));
startActivity(intent);
方式二:通過網頁訪問:
/**href的值說明如下
* 只寫myapp,找不到網頁
* 寫myapp://12,成功
* 寫myapp://da?sd=ad,成功
*/
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>通過URL Scheme啟動Android應用</title>
</head>
<body>
<form>
<a href="myapp://12">啟動</a>
</form>
</body>
</html>
創建完成後發送到手機,再點擊html選擇使用HTMLViewer打開,再點擊鏈接即可啟動應用。
3、總結:第二種方式實現有點繁瑣,最好的實現方式是直接在瀏覽器中輸入url以啟動應用。在網上搜索找到應如下操作:在瀏覽器的搜索欄輸入如下url:content://com.android.htmlfileprovider/storage/emulated/0/myapp://12,經測試無法成功。不知道是哪裡的問題,還請知道的指點一二。
F. 如何啟動 天貓客戶端 android開發
在項目中遇到了這樣一個需求:讓用戶在手機應用中,點擊一個天貓的商品鏈接(知道商品在PC瀏覽器里的地址),直接啟動天貓的客戶端並顯示這個商品。以前曾經實現過類似的功能,不過那次是xx的商品,天貓和淘寶的客戶端不同,參數也不一樣,直接套的格式就不行了。不過,總體的思路還是類似的,就是使用iOS 的URL SCHEME機制。