Activity中被忽略的存在

安靜的存在角落,從不打擾,從不離去。時刻準備着在你需要的時候。
至:activity中一直存在卻被我忽略的方法。

沒有存在的必要早就被幹掉了,如叢林法則,如職場法則。

被忽略的方法

  1. onSaveInstanceState()在進行橫豎屏幕切換的時候需要進行使用
  2. Intent的隱式傳遞
  3. Intent傳遞對象,Serializable Parcelable
  4. 管理自己的棧
  5. 活動的四種啓動模式

activity橫豎屏幕
android開發對於Activity的優化一直在改進,extends ActionBarActivity目前是extends AppCompatActivity,android開發團隊一直在進行優化,對於開發者的影響不是很大,使用的方法一如從前。有興趣的朋友可以baidu這些底層的內容。

1.此圖來自博客查詢的,Activity生命週期

Activity生命週期

好了,生命週期的模式如此,接下去就是自己的實踐了,對於這寫方法在繼承的Activity中都會有用到,自己可以試試。

2.關於手機的重力感應,橫(land)豎(port)屏的切換

方法1:AndroidManifest.xml中設置activity中通過android:screenOrientation屬性值來實現。

android:screenOrientation=”portrait” 豎屏

android:screenOrientation=”landscape”,橫屏

方法2:Java代碼中通過類似如下代碼來設置
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)

解釋:若你的程序是固定的需求,一定要爲橫屏或者豎屏時進行這樣的設置,比如遊戲的橫屏。一般情況下,你開發出來的程序應該支持重力感應由用戶來進行橫豎屏幕的切換。

但是,說說容易,做起來難,所以微信的開發者真是令人敬佩,已經是APP霸主依然考慮盡善盡美,永遠值得學習。

1.實現這樣的功能以及適配,是需要你考慮到很多的事情,有些時候對於一些圖片需要UI的配合,給予你橫屏和豎屏兩種圖,還要考慮對不同手機分辨率的問題。

2.另外界面在豎屏的時候所有界面正常就可以顯示,但是一轉橫屏就完了,所以你就需要進行使用ScrollView或者NestedScrollView佈局來進行所有的數據和界面可以滑動顯示。

3.在界面旋轉的時候,手機會對該界面進行destory()之後再執行oncreate(),如下Activity每次橫豎屏切換都會重新調用

onPause-> onStop-> onDestory-> onCreate->onStart->onResume,

這裏就需要一些數據的存儲,爲了不讓程序Crash

上面3點是我們開發的時候必須考慮到的,也許老闆根本不在乎你處理過程有多麼複雜或者說你的困難有多少,需求就是這樣,這也是開發者本身的工作不是嗎?難也要做。

1、2點比較好解決,你就麻煩一下UI,順便自己在建立不同的佈局或者進行代碼中的判斷

1)在res目錄下建立layout-landlayout-port目錄,相應的layout文件名不變,比如main.xml。layout-land是橫屏的layout,layout-port是豎屏的layout,其他的不用管,橫豎屏切換時程序爲調用Activity的onCreate方法,從而加載相應的佈局。

2)通過java代碼來判斷當前是橫屏還是豎屏然後來加載相應的xml佈局文件。因爲當屏幕變爲橫屏的時候,系統會重新呼叫當前Activity的onCreate方法,你可以把以下方法放在你的onCreate中來檢查當前的方向,然後可以讓你的setContentView來載入不同的layout xml。

if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE)
{  
     Log.i("info", "landscape"); // 橫屏 
}  else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT)
 {  
    Log.i("info", "portrait"); // 豎屏 
 }

對於第3點:

方法1:需要用到

 @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }

方法進行一些數據的存儲(該方法只在你切換橫豎屏幕的時候會進行調用,所以存儲必要的數據,防止Crash,然後在onCreate()方法中應用)

測試結果

方法2:

關於android橫豎屏幕切換的問題(這種方法親自測試無用,如果有人實踐了請留言告訴我,感激不盡)

我實踐的方法是按照上述的步驟,但是在 onConfigurationChanged()方法中根本沒有進行執行,所以還是使用之前的 onSaveInstanceState()方法

Android提供了在manifest中設置android:configChanges屬性,從而讓Activity不延續重建流程。在Android工程的Mainfest.xml中配置對應Activity:android:configChanges=”keyboardHidden|orientation”,橫豎屏切換之後就不會去執行OnCreat函數了,而是會去調用onConfigurationChanged()這樣就能控制橫豎屏的切換了。

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        switch (newConfig.orientation){

            case Configuration.ORIENTATION_LANDSCAPE:
                //執行橫屏的操作

                break;

            case Configuration.ORIENTATION_PORTRAIT:
                //執行豎屏的操作

                break;

        }

    }

關於Intent的隱式調用

在android中的隱式調用相信開發的工作人員應該都用過,如果你說沒有啊,我從來沒有用到過。看看下面的例子:

1.在自己的程序中調用 手機的撥打電話功能

Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:18030043775"));
            startActivity(intent);

還有使用本地的瀏覽器功能

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com/"));
startActivity(intent);

對於上述的兩種方法不陌生吧,這些都屬於隱式調用,只是ACTION/CALL等是android系統設置的動作。

我們自己也可以進行隱式的調用,接着聊:

顯示的調用不多說了:直接上代碼

Intent intent = new Intent(MainActivity.this,AboutIntentActivity.class);
//putExtra()用來傳遞數據
intent.putExtra("message","進行橫豎屏幕的切換演示");
startActivity(intent);

那我們就以上述的顯示調用改變成隱式調用吧!

現在我呢需要執行一個操作,就是點擊按鈕,在當前的MainActivity界面跳轉至AboutIntentActivity界面。

step1:在manifest.xml文件下面添加如下的代碼,如圖:

添加隱式的Intent跳轉

解釋說明一下:

<!-- action : 活動的響應 -->
<!-- category : 一些附加信息,明確的指明能夠響應的Intent中還有可能        帶有的category-->
<!-- 重點聲明:只有<action>和<category>同時匹配才能被響應 -->
<!-- 也可以自己定義響應的category-->
<!-- data : 用於更精確的指定當前活動能夠響應什麼類型的數據 -->

<!-- data標籤中可以進行配置以下的內容 -->
<!-- android:scheme  指定數據的協議部分,如 https -->
<!-- android:host    指定數據的主機名部分,如 www.baidu.com -->
<!-- android:port    指定數據的端口部分,一般緊隨在主機名之後 -->
<!-- android:path    指定主機名和端口之後的部分  如一段網址中跟在域名之後的部分 -->
<!-- android:mimeType 用於指定可以處理的數據類型,允許使用通配符 -->


<activity android:name=".aboutintent.AboutIntentActivity">
<intent-filter>
  <action android:name="" />
  <category android:name="" />
  <data android:scheme=""
        android:host=""
        android:path=""
        android:port=""
        android:mimeType=""/>
</intent-filter>
</activity>

step2: 進行隱式的調用,如圖

進行隱式的調用

你看,上面我們不是說要 action,和category 同時匹配纔可以調用成功嗎?但是這樣子的調用也不會出錯,爲什麼?

android.intent.category.DEFAULT  //是一種默認的category,在調用startActivity()時會自動將這個屬性添加到intent中。

對應的manifest.xml中的配置:

對應的配置

比如我們想要添加屬於自己的category的時候,需要這樣調用

添加自己的category

相應manifest的配置

調用自己的category

最後data,關於data運用的並不多,甚至在使用Intent的時候使用隱式的也不多,這裏直接使用一個可以打開瀏覽器的data配置

使用data

我們創建一個類AboutAndroidIntent,並進行配置如下,對

<data android:scheme = "https"/>

進行設置,標明這個是可以打開一個具備https協議的,和瀏覽器的功能一樣。

進行調用

調用

當你點擊按鈕之後,系統會顯示一個列表,來表示能夠響應這個Intent的所有 程序,之後你進行選擇即可

彈出選擇界面框


說說Intent的傳值

1.Intent的傳值方式:比較簡單的值,使用

intent.putExtra("name","wewdfrend")
//裏面的類型根據需要,比如傳遞字符串

Stirng name = getIntent().getStringExtra("name");
//在另外的界面進行使用的時候

2.傳遞對象

對於傳遞對象寫在Activity的篇幅中似乎不太合理,但是Intent可以說生來就是爲Activity提供服務的,先看看Intent到底可以傳遞什麼樣的對象:

Intent可以傳遞的對象

除了可以傳遞簡單的數據類型,還有可以傳遞對象。

那麼實現方式有兩種SerializableParcelable

Serializable

Serializable是序列化的意思,表示將一個對象轉換成可存儲或者可傳輸的狀態。

序列化後的對象可以再網絡上進行傳輸,也可以存儲到本地。

實現方法很簡單,只需要你的類 實現 Serializable接口就可以了。

//調用方法和使用簡單的傳值一樣,只不過 personInfo是一個實現Serializable的藉口類
intent.putExtra("Serializable",personInfo);


//在接收Activity中調用傳進來的內容,需要調用  getSerializableExtra()方法

PersonInfo personInfo = ((PersonInfo) getIntent().getSerializableExtra("Serializable"));

tv.setText(personInfo.getName()+"--"+personInfo.getAge()+"--"+personInfo.getAddress());

Parcelable

Parcelable也可以實現相同的效果,不過Parcelable不是進行序列化而是將一個完整的對象進行分解,而分解後的每一步都是Intent的簡單類型,比起Serializable相對複雜。

詳情請看:

需求:現在我需要將 一個人的 信息 :name,age,address以對象的形式進行傳遞,並且使用 Parcelable

1.新建類 PersonMessage,並聲明對象

//如果是 private 的變量,需要提供 get 和 set 方法
//如果是 public 的變量,則正常使用就好

2.令 PersonMessage 類 implement Parcelable 接口

2.實現該接口 需要重寫兩 方法

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        //將需要的只進行寫出,調用 Parcel的writeXxx()方法
        //需要注意數據類型要一至
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(address);      
    }

3.除此之外,還需要你提供一個名爲CREATOR的常量,並重寫兩方法

public static final Parcelable.Creator<PersonMessage> CREATOR = new Parcelable.Creator<PersonMessage>(){


        @Override
        public PersonMessage createFromParcel(Parcel source) {


            //將需要的值讀出來
            //需要注意的是讀取的時候相應的順序需要和寫出的順序一致
            PersonMessage personMessage = new PersonMessage();
            personMessage.name = source.readString();
            personMessage.age = source.readInt();
            personMessage.address = source.readString();
            return personMessage;
        }

        //返回的數量直接傳遞size
        @Override
        public PersonMessage[] newArray(int size) {
            return new PersonMessage[size];
        }
    };

至此Parcelable就算完成了

比較來看:實現 Serializable接口比Parcelable接口方便而且省事的多,但是從性能上分析來看,Serializable是將整個對象進行序列化,因此效率上是比Parcelable低了一些,在項目的長期開發中,建議還是使用Parcelable

調用方法:

btn_sendValueParcelable.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PersonMessage personMessage = new PersonMessage();
                personMessage.setName("wedfrend");
                personMessage.setAge(24);
                personMessage.setAddress("XM china");
                //傳值方法
                Intent intent = new Intent(MainActivity.this,ParcelableActivity.class);
                intent.putExtra("parcelable",personMessage);
                startActivity(intent);
            }
        });


        /**
         * 獲取數據
         */
        PersonMessage personMessage = getIntent().getParcelableExtra("parcelable");
        mEmailView.setText(personMessage.getName()+"--"+personMessage.getAge()+"--"+personMessage.getAddress());

返回數據給上一個活動

這個相對來講也是不常用的一個方法,甚至可以說我們用的不多,市場上多數的APP都是一路向前的,基本上沒有太多的APP是需要將數據返回到上面一個界面,即使有某些界面,使用的並不多,但是我們還是要掌握這種方法。當然你自己也可以寫一些靜態的調用,接口的使用等等的方法來實現相同的效果,但是使用android原生的方法並不是說我們無能,不懂得創新等等,而是android團隊在整個架構中做優化之後推出的。換句話說:你認爲你的技術比android研發團隊還牛B嗎???

將數據返回到上一個界面使用的方法是startActivityForResult()

在跳轉目標類中 調用 serResult() 來進行數據的返回

之後需要在跳轉的類中重寫 onActivityResult()方法

/**
* @params intent 
* @params requestCode:請求的判斷值
*/
startActivityForResult(Intent intent, int requestCode)

/**
* @resultCode 返回的結果判斷值
* @data 返回的附帶參數值
*/
setResult(int resultCode, Intent data) 

/**
* @params requestCode
* @params resultCode
* @params data
*/
@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    }

實踐一下:

我們就使用 Parcelable 中的示例進行延伸的展示

step1:將startActivity(intent)修改爲startActivityForResult(intent,1);

step2:在ParcelableActivity中添加一個Button,並添加點擊事件的方法,調用serResult()方法並銷燬ParcelableActivity界面

Button btn_back = (Button) findViewById(R.id.btn_back);
btn_back.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.putExtra("value","我是返回值");
                setResult(1,intent);
                finish();
            }
        });

step3:在MainActivity中重寫onActivityResult()方法

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode){

            case 0:
                if(resultCode == 1){
                    Toast.makeText(this,data.getStringExtra("value"),Toast.LENGTH_SHORT).show();
                }
                break;
        }

    }

至此,已經完成將值返回至上一個界面的效果

但是,android手機有個特例,那就是back按鈕,很奇怪,同樣是返回上一個界面,點擊button按鈕和點擊手機本身的back按鈕是兩種不同的結果,怎麼辦,這需要我們重寫一個方法

@Override
    public void onBackPressed() {
        super.onBackPressed();
        //書寫方法,和button的點擊方法一致就好了
    }

關於棧的管理

android是使用任務(Task)來管理Activity的,棧是一種先進後出的數據結構(FILO),android系統總是顯示處於棧頂的活動給用戶。(貌似所有的系統都是 這樣的吧)

那先在說說需求:棧按照 FILO的數據結構並沒有問題,android系統默認也是這樣子去管理Application的,實際情況呢?在我們開發的過程中,有事後需要在一定條件觸發後銷燬掉前面一個Activity,你怎麼辦?寫靜態方法也能實現,多了呢?你對手機的性能也提出挑戰,這樣並不是合格的開發者,那麼我麼就只能自建管理棧來管理我們的Activity做到可控的狀態。

public class ActivityCollector {

    private static final String TAG = "ActivityCollector";

    private ActivityCollector() {
    }

    private static ActivityCollector activityCollector = new ActivityCollector();

    public static ActivityCollector newInstance() {
        return activityCollector;
    }

    private Stack<Activity> stackActivity;

    /**
     * 將活動添加入棧
     *
     * @param activity
     */

    public void addActvity(Activity activity) {
        if (stackActivity == null || stackActivity.isEmpty()) {
            stackActivity = new Stack<Activity>();
        }
        Log.i(TAG, "addActivity: " + activity.getClass());
        stackActivity.add(activity);
    }


    /**
     * 將活動移除出棧
     *
     * @param activity
     */
    public void removeActivity(Activity activity) {
        if (!stackActivity.isEmpty()) {
            stackActivity.remove(activity);
        }
    }


    /**
     * 將制定的Activity移除出棧
     *
     * @param cls
     */
    public String removeAssignActivity(Class<?> cls) {

        if (!stackActivity.isEmpty()) {
            Iterator<Activity> iterator = stackActivity.iterator();
            while (iterator.hasNext()) {//執行遍歷循環
                Activity activity = iterator.next();
                if (activity.getClass().equals(cls)) {//查看是否一致
                    //一致便將該activity移除
                    iterator.remove();
                    activity.finish();
                }
            }

        }
        return checkActivity();
    }

    /**
     * 查詢當前棧中的Activity
     */
    public String checkActivity() {

        StringBuffer stringBuffer = new StringBuffer();
        if (!stackActivity.isEmpty()) {
            Iterator<Activity> iterator = stackActivity.iterator();
            while (iterator.hasNext()) {
                Activity activity = iterator.next();
                Log.d(TAG, "checkActivity: " + activity.getClass());
                stringBuffer.append(activity.getClass()+"---");
            }
        }
        return stringBuffer.toString();
    }

    public void finishApplication(){

        if (!stackActivity.isEmpty()) {
            Iterator<Activity> iterator = stackActivity.iterator();
            while (iterator.hasNext()) {//執行遍歷循環
                Activity activity = iterator.next();
                iterator.remove();
                activity.finish();
            }
            //殺掉進程
            android.os.Process.killProcess(android.os.Process.myPid());
        }
    }



}

因爲使用棧來進行管理,那麼我們每一個Activity都是需要進行執行添加操作,自然也有移除,但是在每一個Activity中進行是不是太過於麻煩,所以在這裏創建一個基類,BaseAppCompatActivity ,那麼所有的活動類均繼承基類,就像這樣的

XxxxActivity extends BaseAppCompatActivity

BaseAppCompatActivity extends AppCompatActivity

BaseAppcompatActivity的相應配置可以如下:

public class BaseAppCompatActivity extends AppCompatActivity {

    private static final String TAG = "BaseAppCompatActivity";
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, "onCreate: "+getClass().getName());
        Log.i(TAG, "onCreate: "+getClass().getSimpleName());
        ActivityCollector.newInstance().addActvity(this);

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.newInstance().removeActivity(this);
    }
}

實踐時間:

創建3個Activity

跳轉方式

MainActivity–>SecondActivity–>ThridActivity–>fourActivity

具體界面的點擊跳轉方法就不累述類,使用Intent跳轉就好了。

如圖,點擊 查看棧中的所有活動,顯示如下

查看當前所有活動


點擊移除SecondActivity可以看到之後剩下的Activity

點擊移除SecondActivity

activity的啓動模式

在實際的項目中我們應該根據特定的需求爲每一個活動指定恰當的啓動模式。activity的啓動模式共有四種,如圖

launchMode

standard:android中的默認啓動模式,也就是完全符合棧的數據結構,無論你的棧中是否存在相對應的Activity,只要屬於新建的類,就會進行創建。

standard

singleTop: 在進行跳轉之前,會進行檢查目標Activity是否存在棧頂,若存在,則進行加載而不會重新創建。該模式也只是檢查棧頂而已

singleTop

singleTask:在進行跳轉之前,會查詢整個棧中的Activity是否已經創建過目標Activity,如果存在會將該Activity拿出來放在棧頂重新加載,而不會再一次的創建

singleTask

singleInstance:不同於上述三種模式,這種模式會重新創建一個棧來進行管理Activity。

singleInstance

這種模式的意義解釋:想象以下情景,假設程序A中有一個活動是允許其他程序進行調用的,但是每一個程序都有自己的管理棧,所以你就需要 使用這種模式將你的活動進行分開管理了。這種模式下就會有一個單獨的返回棧來管理這個活動了,不管是哪一個應用程序來進行訪問這個活動,都共用一個返回棧。

協作開發的一些經驗

在實際情況下,大型的項目不可能是你一個人完成的,需要和同事們協同合作,那麼在Activity的跳轉的時候是需要傳遞一些值,如果是你自己一個人寫完整個APP,那我下面的建議純屬多餘。

比如 你在開發一個界面 XxxxActivity,而這個界面是需要另外一個開發者在完成某一功能之後在調用,而XxxxActivity需要一些參數值的傳遞,你的界面才能正常的執行,協同開發過程就是大家一起向目標出發,而你需要做的就是在調用你的界面是給予對方足夠的方便。

辦法:在你自己的 類中寫靜態的調用方法

public static void startXxxxActivity(Context context,String xxx,String xxxx ){

Intent intent = new Intent(context,XxxxActivity.class);
intent.putExtra("params1",xxx);
intent.putExtra("params2",xxxx);
startActivity(intent);

}

這樣對於傳遞值的命名你自己決定,之後調用也不會存在鍵值不同的問題,因爲都是你自己寫下的。而你的同事調用的時候只需要傳遞相應的參數就好了。

XxxxActivity.startXxxxActivity(this,"","");

是否很方便呢?看個人吧!

關於 Activity中的一些強大而不經常使用的方法總結完了,有些時候,自己心裏明白但就是用語言解釋不出來,還是需要提高境界啊

對於這一部分的示例,請在個人的github中下載
https://github.com/wedfrendwang/PrivateProject.git

說明一下:後面的相應總結實踐也會在這個項目中,所以關於Activity的實例我會在上傳之後開闢 名爲 activityUnit 的分支,這樣如果被需要的朋友下載只需要切換分支就可以了。


看了電影《擺渡人》,每個人都有故事,每個人都有自己的一段艱難時光,失意歲月吧。如同歌詞說的:時間向前走一定只有路口沒有盡頭。願所有人都有擺渡人。

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