1. 程式人生 > >Android之完美退出方法(2.1-2.2-2.3SDK版本測試通過)

Android之完美退出方法(2.1-2.2-2.3SDK版本測試通過)

為什麼要寫這篇文章? 

因為網上有很多種退出方法,可是實際上很多方法都不通用(在某個版本下可用,到了另一個版本就不行),或者方法的實際效果根本就和其描述不符(也不知道那些發帖的人測沒測試過)。

但我們的需求又確實存在。在某些情況下,我們需要在應用中開啟多個Activity,但如果僅僅使用finish()方法就不能在需要的時候達到一次性退出的效果,自己作為一個Android退出問題的受害者,通過良久思考和實際測試,找到了一個比較不錯的,在2.1-2.2-2.3版本下都通用的完全退出方法(2.1版本也基本可以代表1.5~2.1版本)!

PS:測試全部在模擬器環境下進行

我首先進行一下說明,下面兩種方法效果完全相同

1,android.os.Process.killProcess(android.os.Process.myPid()) ; (這是Dalvik VM的本地方法)
2,System.exit(0);   (常規java、c#的標準退出法,返回值為0代表正常退出 )
之後我的說明全部以android.os.Process.killProcess(android.os.Process.myPid()) 方法為準。

另外,我後邊所說的程式入口即為在AndroidManifest.xml中配置為如下語句的Activity
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />

下面開始我們的Android完美退出之旅:
先從網上的一段說明說起:
A->B
B中執行android.os.Process.killProcess(android.os.Process.myPid());
  結果是結束了B,然後重新啟動A。
實際情況是這樣:A為程式入口,B中呼叫killProcess(android.os.Process.myPid())操作,實際上是將程式入口A和執行該語句的Activity B都關閉,並重新啟動新的程式入口A。
所以,如果過程是A->B->C

則實際情況是:A為程式入口,C中呼叫killProcess(android.os.Process.myPid())操作將程式入口A和執行該語句的Activity C都關閉,並重新啟動新的程式入口A(在Activity視窗歷史棧當中,舊A 被關閉,新A 仍然會被放置在 舊A 所在的棧位置,不會到達棧頂端)。

PS: 如果killProcess(android.os.Process.myPid())或System.exit(0)是在程式入口A處執行,則是將入口A關閉,不會再開啟新的A.

有人要問了,B Activity呢? B還存在著,B Activity沒有被關閉。
如何解決這個問題?
首先說明一點
android.os.Process.killProcess(android.os.Process.myPid()) ;語句執行之後,後邊的程式碼都將不再執行;
而finish();或startActivity(A.this,B.class);語句在執行完成後仍舊會執行後續的程式碼。(使用Thread.sleep多次驗證,不用擔心finish()過後不能startActivity了,相反也一樣)。
所以,我們就可以充分利用這一點,既然finish();和startActivity(A.this,B.class);語句在執行後仍然可以執行後續程式碼操作,那我們可以將之組合在一個程式碼片段中,即
      startActivity(new Intent(B.this, C.class));
      finish();

      finish();
      startActivity(new Intent(B.this, C.class));
都是可以的,我們在B中使用該程式碼段,既將B Activity關閉了,也打開了C Activity,之前的問題Done!
如果你還有D,E,F ... 那也一樣,在每次跳轉到下一個Activity時,將finish()一塊用上。使用這種方式,多餘的Activity就能夠被關閉了。

PS:很囧的是,在我自己的Android應用中,在程式入口處呼叫這種組合程式碼,會直接將新開啟的Activity也一併關閉,但在我建立的簡單工程當中卻不會有這種情況,不知道為什麼,還在尋找原因中......(也請高手指點一二)


最後 假設我們有下面一種需求,對上邊的內容進行總結:
Activity的開啟過程為 1.Index --> 2.A_Activity --> 3.B_Activity --> 4.Index,在4.Index中實現退出,Index為程式入口。

Index
退出:就是最簡單的finish();
跳轉:也是最簡單的 startActivity(new Intent(Index.this, A_Activity.class));

A_Activity
退回到首介面:分兩種情況
1,需要Index更新(我的Index就有這個需求,首頁面色彩發生變化)
使用android.os.Process.killProcess(android.os.Process.myPid()) ;關閉自己和之前的Index,建立新的Index;
2,不需要新的Index,Index無變化
使用最簡單的finish(),並且效率還要高些。
跳轉:
      finish(); 
      startActivity(new Intent(A_Activity.this, C.class));
關閉自身,開啟除 Index 之外的其它Activity。

B_Activity
操作與 A_Activity 相同。當然跳轉語句變成了
      finish(); 
      startActivity(new Intent(B_Activity.this, Index.class));



**************************************************************************
上邊的內容就是這樣了,下面我再告訴你另外一種方法,可以實現不關閉中途的Activity,而是在後邊的操作中一次性關閉前邊開啟的所有Activity,可以滿足一些人通過按返回鍵返回上一個介面的要求!
通過Android的視窗類提供的歷史棧,巧妙利用stack的原理,我們在Intent中加入標誌 Intent.FLAG_ACTIVITY_CLEAR_TOP。

    Intent intent = new Intent(); 
    intent.setClass(One.this, Two.class);  
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  
    startActivity(intent);

假定有如下需求:
1.Index --> 2.A_Activity --> 3.B_Activity

在3中設定 intent.setClass(B_Activity.this, Index.class);
跳轉後,程式會從棧頂逐個向後查詢,直到找到棧中最近的Index,然後將這一路找到的Activity全部關閉,包括1、2、3(也就不需要像我先前的方法一路finish了,也保留了途中經過的Activity),最後再自動建一個新的Index Activity放到棧頂的位置,接下來在Index視窗中使用finish方法即可退出。

如果沒有理解,看這個例子:
如果3中設定的是 intent.setClass(B_Activity.this, A_Activity.class);
則是將2,3關閉,再新建一個4.A_Activity,棧中就變成了
1.Index --> 4.A_Activity,懂了吧!
值得注意的是,在下面的情況中
1
.Index --> 2.A_Activity --> 3.B_Activity --> 4.Index --> 5.A_Activity
在5中使用intent.setClass(A_Activity.this, B_Activity.class);
結果不是
1.Index --> 2.A_Activity --> 6.B_Activity 
而是
1.Index --> 2.A_Activity --> 3.B_Activity --> 4.Index --> 5.A_Activity --> 6.B_Activity,因為4(程式入口)的存在,所以5對棧的操作不會到達3,而是發現4、5中都沒有B_Activity後,沒有關閉任何Activity,只在棧頂端新建了一個6.B_Activity。
這也間接說明了Dalvik虛擬機器的遍歷演算法只進行到最近的程式入口,就認為後邊沒有該程式的Activity了。所以良好的Android程式設計習慣是,新建一個程式入口時,一定把老程式入口關掉,這也解釋了為什麼用killProcess方法更新後的程式入口Index一定還是在棧中的老位置,而不是到棧頂端。


以上都是我通過新建的一個簡單工程測試驗證過的。
不過很囧的是,這種退出方法,我在自己的應用程式下測試,結果都是直接退出到home介面,連Index介面都沒有出現,還想請高手賜教一下這裡邊深層次的原因。


**************************************************************************
還有一種比較流行的Android經典完美退出方法,使用單例模式建立一個Activity管理物件,該物件中有一個Activity容器(具體實現自己處理,使用LinkedList等)專門負責儲存新開啟的每一個Activity,並且容易理解、易於操作,非常不錯!
MyApplication類(儲存每一個Activity,並實現關閉所有Activity的操作)
public class MyApplication extends Application {

private List<Activity> activityList = new LinkedList<Activity>(); 
private static MyApplication instance;

            private MyApplication()
            {
            }
  //單例模式中獲取唯一的MyApplication例項 
             public static MyApplication getInstance()
             {
                            if(null == instance)
                          {
                             instance = new MyApplication();
                          }
                 return instance;             
             }
 //新增Activity到容器中
             public void addActivity(Activity activity)
             {
                            activityList.add(activity);
             }
 //遍歷所有Activity並finish
             public void exit()
             {
                          for(Activity activity:activityList)
                         {
                           activity.finish();
                         }
                           System.exit(0);
            }
}
在每一個Activity中的onCreate方法裡新增該Activity到MyApplication物件例項容器中
MyApplication.getInstance().addActivity(this);
在需要結束所有Activity的時候呼叫exit方法
MyApplication.getInstance().exit();

個人非常喜歡這種方法!(非常感謝flyrabbits的幫助)


—————————————分割線—————————————————
我對其他一些退出方法進行的一點介紹和點評(不到之處還請指正):
@restartPackage(getPackageName())(具體就不介紹了)
我在SDK2.1版本下開發的一款小軟體,放到Android2.2或2.3作業系統上無法退出,因為restartPackage方法在SDK2.1以後版本已經被廢棄不用了,理由是因為它不安全,可能關閉同其他應用程式共享的Service,而這個Service別人還要用呢,你給別人關了就不對了。

@有人說的終極退出方法:
           Intent startMain = new Intent(Intent.ACTION_MAIN);
           startMain.addCategory(Intent.CATEGORY_HOME);
           startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
           startActivity(startMain);
           System.exit(0);
實際上這種方法只是返回了Home頁面,如果你再次進入應用,你會發現進入的首介面是你先前沒有關閉的Activity。

@呼叫系統隱藏forceStopPackage方法,這裡是通過對映呼叫(也有其他方法)
        Method method = null; 
        ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        try {
         method = Class.forName("android.app.ActivityManager").getMethod("forceStopPackage", String.class);
         method.invoke(manager,getPackageName());
        } catch (Exception e) {
         Log.d("force",e.getMessage());    
        }
我在SDK2.2和2.3的測試結果是,出現NULLPointerException,彈出錯誤視窗,程式被迫關閉,和預想的正常退出有差別。不過我們可以通過修改基類實現自己的Thread.UncaughtExceptionHandler介面的uncaughtException方法,這樣就不會有錯誤視窗彈出。程式完全退出。

@和上面一樣,不過這是故意製造異常退出(上邊是無意製造的異常),但我認為這畢竟是下策。