編輯:關於Android編程
最近有幾位朋友給我留言,讓我談一下對Activity啟動模式的理解。我覺得對某個知識點的理解必須要動手操作才能印象深刻,所以今天寫一篇博文,結合案例理解Activity啟動模式。由於之前看過“區長”的一篇博文(文章結尾處有鏈接)深受啟發,因此本文是在那篇文章的基礎上更加全面的講解。
眾所周知當我們多次啟動同一個Activity時,系統會創建多個實例,並把它們按照先進後出的原則一一放入任務棧中,當我們按back鍵時,就會有一個activity從任務棧頂移除,重復下去,直到任務棧為空,系統就會回收這個任務棧。但是這樣以來,系統多次啟動同一個Activity時就會重復創建多個實例,這種做法顯然不合理,為了能夠優化這個問題,Android提供四種啟動模式來修改系統這一默認行為。
進入正題,Activity的四種啟動模式如下:
standard、singleTop、singleTask、singleInstance
接下來,我們一邊講理論一邊結合案例來全面學習這四種啟動模式。
為了打印方便,定義一個基礎Activity,在其onCreate方法和onNewIntent方法中打印出當前Activity的日志信息,主要包括所屬的task,當前類的hashcode,以及taskAffinity的值。之後我們進行測試的Activity都直接繼承該Activity
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
* 方便打印的基礎Activity
*/
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("WooYun", "*****onCreate()方法******");
Log.i("WooYun", "onCreate:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i("WooYun", "*****onNewIntent()方法*****");
Log.i("WooYun", "onNewIntent:" + getClass().getSimpleName() + " TaskId: " + getTaskId() + " hasCode:" + this.hashCode());
dumpTaskAffinity();
}
protected void dumpTaskAffinity(){
try {
ActivityInfo info = this.getPackageManager()
.getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.i("WooYun", "taskAffinity:"+info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}
這個模式是默認的啟動模式,即標准模式,在不指定啟動模式的前提下,系統默認使用該模式啟動Activity,每次啟動一個Activity都會重寫創建一個新的實例,不管這個實例存不存在,這種模式下,誰啟動了該模式的Activity,該Activity就屬於啟動它的Activity的任務棧中。這個Activity它的onCreate(),onStart(),onResume()方法都會被調用。
配置形式:
使用案例:
對於standard模式,android:launchMode可以不進行聲明,因為默認就是standard。
StandardActivity 的代碼如下,入口Activity中有一個按鈕進入該Activity,這個Activity中又有一個按鈕啟動StandardActivity。
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
* Standard模式
*/
public class ActivityStandard extends BaseActivity {
private Buttonjump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_standard);
jump= (Button) findViewById(R.id.btn_standard);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivityStandard.this, ActivityStandard.class);
startActivity(intent);
}
});
}
}
我們首先進入StandardActivity,進入後再點擊進入Standard的按鈕,再按四次返回鍵不斷返回。
輸出的日志如下:
vcjV1r7K5LP2wcvLxLTOU3RhbmRhcmRBY3Rpdml0ebXEus3Su7TOTWFpbkFjdGl2aXR5tcSjrLTTTWFpbkFjdGl2aXR5vfjI61N0YW5kYXJkQWN0aXZpdHnSu7TOo6y688C0ztLDx9PWsLTBy8j9tM6wtMWlo6zX3Lmyy8S0zlN0YW5kYXJkQWN0aXZpdHm1xMjV1r6jrLKix9LL+cr0tcTIzs7x1bu1xGlktrzKxzIwODejrNXi0rLR6dakwcs8c3Ryb25nPsutxvS2r8HLuMPEo8q9tcRBY3Rpdml0eaOsuMNBY3Rpdml0eb7NyvTT2sb0tq/L/LXEQWN0aXZpdHm1xMjOzvHVu9bQPC9zdHJvbmc+1eK+5Luwo6zS8s6qxvS2r1N0YW5kYXJkQWN0aXZpdHm1xMrHTWFpbkFjdGl2aXR5o6y2+E1haW5BY3Rpdml0ebXEdGFza0lkyscyMDg3o6zS8rTLxvS2r7XEU3RhbmRhcmRBY3Rpdml0edKy06a4w8r009ppZM6qMjA4N7XE1eK49nRhc2ujrLrz0Pi1xDO49lN0YW5kYXJkQWN0aXZpdHnKx7G7U3RhbmRhcmRBY3Rpdml0edXiuPa21M/zxvS2r7XEo6zS8rTL0rLTprjDu7nKxzIwODejrMv50tR0YXNrSWS2vMrHMjA4N6GjsqLH0sO/0ru49kFjdGl2aXR5tcRoYXNoY29kZba8yseyu9K70fm1xKOsy7XD98v7w8fKx7K7zay1xMq1wP2jrLy0JmxkcXVvO8O/tM7G9Lav0ru49kFjdGl2aXR5try74dbY0LS0tL2o0ru49tDCtcTKtcD9JnJkcXVvOzwvcD4NCjxoMiBpZD0="singletop-棧頂復用模式">singleTop-棧頂復用模式
這個模式下,如果新的activity已經位於棧頂,那麼這個Activity不會被重寫創建,同時它的onNewIntent方法會被調用,通過此方法的參數我們可以去除當前請求的信息。如果棧頂不存在該Activity的實例,則情況與standard模式相同。需要注意的是這個Activity它的onCreate(),onStart()方法不會被調用,因為它並沒有發生改變。
配置形式:
使用案例:
ActivitySingleTop.java
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
* SingleTop模式
*/
public class ActivitySingleTop extends BaseActivity {
private Button jump,jump2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singletop);
jump = (Button) findViewById(R.id.btn_singletop);
jump2 = (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivitySingleTop.this, ActivitySingleTop.class);
startActivity(intent);
}
});
jump2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivitySingleTop.this, OtherTopActivity.class);
startActivity(intent);
}
});
}
OtherTopActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
*/
public class OtherTopActivity extends BaseActivity {
private Button jump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other);
jump= (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(OtherTopActivity.this, ActivitySingleTop.class);
startActivity(intent);
}
});
}
}
操作和standard模式類似,直接貼輸出日志
我們看到,除了第一次進入SingleTopActivity這個Activity時,輸出的是onCreate方法中的日志,後續的都是調用了onNewIntent方法,並沒有調用onCreate方法,並且四個日志的hashcode都是一樣的,說明棧中只有一個實例。這是因為第一次進入的時候,棧中沒有該實例,則創建,後續的三次發現棧頂有這個實例,則直接復用,並且調用onNewIntent方法。那麼假設棧中有該實例,但是該實例不在棧頂情況又如何呢?
我們先從MainActivity中進入到SingleTopActivity,然後再跳轉到OtherActivity中,再從OtherActivity中跳回SingleTopActivity,再從SingleTopActivity跳到SingleTopActivity中,看看整個過程的日志。
我們看到從MainActivity進入到SingleTopActivity時,新建了一個SingleTopActivity對象,並且task id與MainActivity是一樣的,然後從SingleTopActivity跳到OtherActivity時,新建了一個OtherActivity,此時task中存在三個Activity,從棧底到棧頂依次是MainActivity,SingleTopActivity,OtherActivity,此時如果再跳到SingleTopActivity,即使棧中已經有SingleTopActivity實例了,但是依然會創建一個新的SingleTopActivity實例,這一點從上面的日志的hashCode可以看出,此時棧頂是SingleTopActivity,如果再跳到SingleTopActivity,就會復用棧頂的SingleTopActivity,即會調用SingleTopActivity的onNewIntent方法。這就是上述日志的全過程。
對以上內容進行總結
standard啟動模式是默認的啟動模式,每次啟動一個Activity都會新建一個實例不管棧中是否已有該Activity的實例。
singleTop模式分3種情況
standard和singleTop啟動模式都是在原任務棧中新建Activity實例,不會啟動新的Task,即使你指定了taskAffinity屬性。
那麼什麼是taskAffinity屬性呢,可以簡單的理解為任務相關性。
很重要的一點taskAffinity屬性不對standard和singleTop模式有任何影響,即時你指定了該屬性為其他不同的值,這兩種啟動模式下不會創建新的task(如果不指定即默認值,即包名)
指定方式如下:
這個模式十分復雜,有各式各樣的組合。在這個模式下,如果棧中存在這個Activity的實例就會復用這個Activity,不管它是否位於棧頂,復用時,會將它上面的Activity全部出棧,並且會回調該實例的onNewIntent方法。其實這個過程還存在一個任務棧的匹配,因為這個模式啟動時,會在自己需要的任務棧中尋找實例,這個任務棧就是通過taskAffinity屬性指定。如果這個任務棧不存在,則會創建這個任務棧。
配置形式:
使用案例:
ActivitySingleTask.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
* SingleTask模式
*/
public class ActivitySingleTask extends BaseActivity {
private Button jump,jump2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_task);
jump = (Button) findViewById(R.id.btn_task);
jump2 = (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivitySingleTask.this, ActivitySingleTask.class);
startActivity(intent);
}
});
jump2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(ActivitySingleTask.this, OtherTaskActivity.class);
startActivity(intent);
}
});
}
}
OtherTaskActivity.java
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
/**
* Created by huangshuai on 2016/5/23.
* Email:[email protected]
*/
public class OtherTaskActivity extends BaseActivity {
private Button jump;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_other_task);
jump= (Button) findViewById(R.id.btn_other);
jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(OtherTaskActivity.this, ActivitySingleTask.class);
startActivity(intent);
}
});
}
}
現在我們先不指定任何taskAffinity屬性,對它做類似singleTop的操作,即從入口MainActivity進入SingleTaskActivity,然後跳到OtherActivity,再跳回到SingleTaskActivity。看看整個過程的日志。
當我們從MainActiviyty進入到SingleTaskActivity,再進入到OtherActivity後,此時棧中有3個Activity實例,並且SingleTaskActivity不在棧頂,而在OtherActivity跳到SingleTaskActivity時,並沒有創建一個新的SingleTaskActivity,而是復用了該實例,並且回調了onNewIntent方法。並且原來的OtherActivity出棧了,具體見下面的信息,使用命令adb shell dumpsys activity activities可進行查看
可以看到當前棧中只有兩個Activity,即原來棧中位於SingleTaskActivity 之上的Activity都出棧了。
我們看到使用singleTask啟動模式啟動一個Activity,它還是在原來的task中啟動。其實是這樣的,我們並沒有指定taskAffinity屬性,這說明和默認值一樣,也就是包名,當MainActivity啟動時創建的Task的名字就是包名,因為MainActivity也沒有指定taskAffinity,而當我們啟動SingleTaskActivity ,首先會尋找需要的任務棧是否存在,也就是taskAffinity指定的值,這裡就是包名,發現存在,就不再創建新的task,而是直接使用。當該task中存在該Activity實例時就會復用該實例,這就是棧內復用模式。
這時候,如果我們指定SingleTaskActivity 的taskAffinity值。
還是之前的操作。但是日志就會變得不一樣。
我們看到SingleTaskActivity所屬的任務棧的TaskId發生了變換,也就是說開啟了一個新的Task,並且之後的OtherActivity也運行在了該Task上
打印出信息也證明了存在兩個不同的Task
如果我們指定MainActivity的taskAffinity屬性和SingleTaskActivity一樣,又會出現什麼情況呢。
沒錯,就是和他們什麼都不指定是一樣的。
這時候,就有了下面的結論
singleTask啟動模式啟動Activity時,首先會根據taskAffinity去尋找當前是否存在一個對應名字的任務棧
然後啟動一個應用,讓他跳轉到該Activity後,再按home鍵後台,啟動另一個應用再進入該Activity,看日志
我們看到,指定了相同的taskAffinity的SingleTaskActivity和OtherActivity被啟動到了同一個task中,taskId都為2169。
該模式具備singleTask模式的所有特性外,與它的區別就是,這種模式下的Activity會單獨占用一個Task棧,具有全局唯一性,即整個系統中就這麼一個實例,由於棧內復用的特性,後續的請求均不會創建新的Activity實例,除非這個特殊的任務棧被銷毀了。以singleInstance模式啟動的Activity在整個系統中是單例的,如果在啟動這樣的Activiyt時,已經存在了一個實例,那麼會把它所在的任務調度到前台,重用這個實例。
配置形式:
使用案例:
增加一個Activity如下:
ActivitySingleInstance.java
import android.os.Bundle;
/**
* Created by huangshuai on 2016/5/24.
* Email:[email protected]
* SingleInstance模式
*/
public class ActivitySingleInstance extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleinstance);
}
}
配置屬性如下:
使用下面的方式分別在兩個應用中啟動它
Intent intent = new Intent();
intent.setAction("com.castiel.demo.singleinstance");
startActivity(intent);
做的操作和上一次是一樣的,查看日志
我們看到,第一個應用啟動SingleInstanceActivity時,由於系統中不存在該實例,所以新建了一個Task,按home鍵後,使用另一個App進入該Activity,由於系統中已經存在了一個實例,不會再創建新的Task,直接復用該實例,並且回調onNewIntent方法。可以從他們的hashcode中可以看出這是同一個實例。因此我們可以理解為:SingleInstance模式啟動的Activity在系統中具有全局唯一性。
引言以前剛剛入門的時候,聽別人說Adapter是一個好東西,一時沒法體會……結合最近項目中有個需求,就是在界面顯示已經配對了的設備的圖標並且可
最近到4412最基本的都調了 然後覺得沒事做了 所以增加一個HDMI的設置 不能閒著 以下使用的是廣州斯道的icool210開發板 源碼修改如下 最開始我是先在設置裡加上
一、分析1、最簡單的布局:只有一個ListView如果整個頁面只有一個ListView的話,那麼由於ListView本身帶有滾動效果,所以當加載的數據超過頁面顯示的范圍時
RecyclerView是什麼?筆者個人看法,RecyclerView只是一個對ListView的升級版,這個升級的主要目的是為了讓這個view的效率更高,並且使用更加方