繼承Application達到Global Variable

(2012-02-04 更新)Android軟體要達到參數傳遞必須要用Bundle或是Intent來把某個變數or物件傳給下一個Activity,這是比較一般的做法,不過還有另外一個繼承Application的做法,讓某些變數變成全域變數,在每個Activity中可以直接取用。這篇文章將對如何繼承Application來做說明。

會使用繼承Application的原因其實在於前一段時間小蛙在串接行動廣告的時候遇到多執行緒的問題,由於原始做法是一個Activity跳到另一個Activity,每個Activity中讀取一次廣告,如此一來,在來回了幾次之後就會發生OOM的情況(不一定會發生,但速度會變得很慢,從DDMS中可以看到執行緒狂開)。
小蛙得知繼承Application可以達到全域變數的功用後,異想天開的想把Google AdWhirl的AdWhirlLayout物件設成全域變數,只有第一次需要做初始化的動作,其他Activity只需要把這個變數拿回來用,如此一來就可以避免掉每個Activity都初始化一次造成的OOM。
試了之後發現還真的可以!直接看code吧!只有一個要注意的地方就是這邊傳入了前景的Activity a,會傳進來的原因是KuBanner建立的時候必須傳入Activity當參數,如果不傳入前景的Activity當參數,會造成kuAD開啟後只有banner大小的視窗以及無法顯示網頁的問題。初始化的動作小蛙把它放在主程式執行時才呼叫。

public class ApplicationBanner extends Application implements AdWhirlInterface{
    // Google AdWhirl Layout
    AdWhirlLayout adWhirlLayout;
    // 傳入主要(前景)的Activity
    Activity a;
    // kuAD banner
    KuBanner banner;
    // Google AdWhril Key
    private static final String ADWHIRL_KEY = "xxxxxxxxxxxxxxxx";
    // kuAD Key, or other keys( vpon … etc)
    private static final String KUAD_KEY        = "xxxxxxxxx";
    // 初始化 adWhirlLayout 供其他 Activity 直接取用
    public void initAdWhirlLayout(Activity a){
        this.a = a;
        adWhirlLayout = new AdWhirlLayout(a, ADWHIRL_KEY);
        AdWhirlManager.setConfigExpireTimeout(1000 * 60);
        AdWhirlTargeting.setAge(28);
        AdWhirlTargeting.setGender(AdWhirlTargeting.Gender.MALE);
        AdWhirlTargeting.setPostalCode("115");
        // 是否為測試模式,上架時要改成false
        AdWhirlTargeting.setTestMode(false);
        adWhirlLayout.setAdWhirlInterface(this);
    }
    // 在 Google AdWhirl 設定的 function
    public void showKuAD(){
        // 這邊要注意,如果這個 activity 一定要是前景視窗的
        banner = new KuBanner(a);
        banner.setAPID(KUAD_KEY);
        banner.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        // 這行改掉,換成 pushSubView
        //adWhirlLayout.handler.post(new ViewAdRunnable(adWhirlLayout, banner));
        adWhirlLayout.pushSubView(banner);
        banner.setkuADListener(new kuADListener() {
            public void onRecevie(String arg0) {
                adWhirlLayout.adWhirlManager.resetRollover();
                adWhirlLayout.rotateThreadedDelayed();
            }
            public void onFailedRecevie(String arg0) {
                banner.setkuADListener(null);
                adWhirlLayout.rollover();
            }
        });
    }
    // 印象中查到的資料這個不保證會執行,但還是放著,在軟體結束的時候,從 Activity 那邊刪程序比較保險些
    @Override
    public void onTerminate() {
        super.onTerminate();
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

接著在AndroidManifest.xml中加入剛剛建立的Application名稱。加在原本的<application>標籤中。

<application android:name="ApplicationBanner"
android:icon="@drawable/icon"
android:label="@string/app_name">
    <activity> … </activity>
</application>

Layout中加入要放置廣告的區塊,例如小蛙的區塊設定如下:

<LinearLayout
    android:id="@+id/banner"
    android:layout_width="fill_parent"
    android:layout_height="50dp"
    android:layout_alignParentBottom="true">
</LinearLayout>

最後一個步驟了,在Activity中宣告並使用Application內的全域變數。這裡有一個要注意的部分,cleanLayout()的目的在於如果不先把原本layout中的東西清除,到下一個Activity又塞東西進去,會發生錯誤。在這邊小蛙想了很久到底什麼時候要清什麼時候要塞,後來發現只要在每個Activity onPause的時候清空,onResume的時候塞回去就可以了,這邊可以思考一下有沒有更好的方法,但小蛙這樣用到現在沒有發生錯誤!

ApplicationBanner     appState;
AdWhirlLayout        adWhirlLayou;
LinearLayout         layout;
//@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    // 取得剛剛宣告的 Application
    appState = (ApplicationBanner)getApplication();
    // 呼叫內部方法初始化,只有第一個Activity需要初始化,其他Activity可以直接使用
    appState.initAdWhirlLayout(ClassName.this);
    // 取得 Google AdWhirl Layout 物件
    adWhirlLayou = appState.adWhirlLayout;
}
// 刪除 ad banner layout
public void cleanLayout(){
    if(layout != null){
        layout.removeAllViews();
    }
}
@Override
public void onPause() {
    cleanLayout();
    super.onPause();
}
@Override
public void onResume() {
    if(layout == null){
        layout = (LinearLayout)findViewById(R.id.banner);
    }
    if(layout != null){
        layout.removeAllViews();
        layout.addView(adWhirlLayou);
        layout.invalidate();
    }
    super.onResume();
}

希望這篇文章對想在軟體中加入廣告,又不想大費周章把所有寫好的Activity改成Fragment的網友有幫助!
2012-02-04
如果忘了在AndroidManifest.xml加上<activity android:name=”com.kuad.ADDisplay”/>也會造成點擊廣告之後只有banner大小的視窗。

8 則留言

  1. 解掉了不少leak,感恩!另外發現就算APP關閉了,廣告似乎還是會在背景跑,造成參照的context無法被回收。目前我是用比較小的activity去new這些物件,減少leak的size,希望可以多少有些幫助…

    • 小蛙在試這個方法的時候,在Activity的onDestroy中加上
      android.os.Process.killProcess(android.os.Process.myPid());
      讓程式結束時把整個process砍掉,剛剛測了一下背景廣告就沒有再撥放了,
      (小蛙的AdWhirl refresh time設定為 Disabled,避免與 kuAD 的 refresh time 衝突造成多開),
      如果是需要背景執行的程式可能就要再想其他讓廣告別在背景跑的方法了!
      也謝謝您提供用較小的Acitivty去new物件的方法喔!! ^_^

    • thx~ 加了這行就沒問題了。
      KuAd的問題我有跟他們工程師反映過,好像是每次切換activity的時候都會重新reload廣告,不過最後會變得狂換廣告,閃來閃去。後來他們給了我們另一版.jar,就不會有這問題了,但不確定會不會更新到接下來的版本。

  2. 蛙大您好,先謝謝你,這個方法的確可以減少廣告不斷開啟thread,而耗盡資源。
    但我遇到了一個問題,需向您請教,
    我在一個Activity中建立了一個ListView (Adapter: BaseAdapter),當我按下ListView中的Item時,會跳至下一個Activity,按back key後會回來ListView並停留在之前點選的頁面上。
    但我發現有時回復時會回到ListView的第一筆資料,
    後來找到的問題是在按下ListView Item進入下一個activity之前,
    onPause() > cleanLayout(); > layout.removeAllViews(); 似乎會使 getview 又動作,監看position的位置則為 0 開始,依序完成第一頁的各Item。
    不知蛙大是否有碰過類似的問題,或者能提供相關的建議,可以使removeAllViews();後不使getview再次動作。
    謝謝
    SAM

    • Dear SAM:
      小蛙看了一下API,以下是removeAllView的描述,
      public void removeAllViews ()
      Call this method to remove all child views from the ViewGroup.
      layout.removeAllViews() 看起來只會移除layout中的 child views,
      如果您的ListView不屬於 layout 的 child view 應該不至於被重置才對,
      會不會是有什麼其他問題呢?或是查看DDMS有可能是記憶體GC(純猜測)
      祝您好運囉!^__^

  3. 蛙大,感謝你的回覆~
    問題已找到了,是我自已在換頁的時候,將ListView.removeAllViewsInLayout(),
    但我有將現在ListView畫面紀錄,
    position = ListView.getFirstVisiblePosition();
    在back回到ListView onRestart時,ListView.setSelection(position);
    ListView沒有回到正確的position(看來setSelection沒有動作)
    現在把ListView.removeAllViewsInLayout() 移除就不會再跳到ListView的第一筆資料了(問題是解決了,但仍不清楚為何不是每次都發生!? = =)

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *