關於Android中的Context詳解

Android基礎概念Context的作用

 

Context字面意思上下文,位於framework package的android.content.Context中,其實該類爲LONG型,類似Win32中的Handle句柄,很多方法需要通過Context才能識別調用者的實例,比如說Toast的第一個參數就是Context,一般在Activity中我們直接用this代替,代表調用者的實例爲Activity,而到了一個button的onClick(View view)等方法時,我們用this時就會報錯,所以我們可能使用ActivityName.this來解決,主要原因是因爲實現Context的類主要有Android特有的幾個模型,Activity、Service以及BroadcastReceiver。 常規需要Context實例的方法主要有各種Service實現的類,比如說SensorManager在實例化時需要getSystemService(String)方法就必須由Context的實例執行,還有一些私有的文件系統I/O比如說openFileInput以及常用的Toast的makeText方法。

android context理解
 

在android中context可以作很多操作,但是最主要的功能是加載和訪問資源。在android中有兩種context,一種是 application context,一種是activity context,通常我們在各種類和方法間傳遞的是activity context。
比如一個activity的onCreate:
protected void onCreate(Bundle state) {
        super.onCreate(state);

        TextView label = new TextView(this); //傳遞context給view control
        label.setText("Leaks are bad");

        setContentView(label);
}
把activity context傳遞給view,意味着view擁有一個指向activity的引用,進而引用activity佔有的資源:view hierachy, resource等。
這樣如果context發生內存泄露的話,就會泄露很多內存。
這裏泄露的意思是gc沒有辦法回收activity的內存。

Leaking an entire activity是很容易的一件事。

當屏幕旋轉的時候,系統會銷燬當前的activity,保存狀態信息,再創建一個新的。

比如我們寫了一個應用程序,它需要加載一個很大的圖片,我們不希望每次旋轉屏 幕的時候都銷燬這個圖片,重新加載。實現這個要求的簡單想法就是定義一個靜態的Drawable,這樣Activity 類創建銷燬它始終保存在內存中。
實現類似:
public class myactivity extends Activity {
        private static Drawable sBackground;
        protected void onCreate(Bundle state) {
                super.onCreate(state);

                TextView label = new TextView(this);
                label.setText("Leaks are bad");

                if (sBackground == null) {
                        sBackground = getDrawable(R.drawable.large_bitmap);
                }
        label.setBackgroundDrawable(sBackground);//drawable attached to a view

        setContentView(label);
        }
}
這段程序看起來很簡單,但是卻問題很大。當屏幕旋轉的時候會有leak(即gc沒法銷燬activity)。
我們剛纔說過,屏幕旋轉的時候系統會銷燬當前的activity。但是當drawable和view關聯後,drawable保存了view的 reference,即sBackground保存了label的引用,而label保存了activity的引用。既然drawable不能銷燬,它所 引用和間接引用的都不能銷燬,這樣系統就沒有辦法銷燬當前的activity,於是造成了內存泄露。gc對這種類型的內存泄露是無能爲力的。

避免這種內存泄露的方法是避免activity中的任何對象的生命週期長過activity,避免由於對象對 activity的引用導致activity不能正常被銷燬。我們可以使用application context。application context伴隨application的一生,與activity的生命週期無關。application context可以通過Context.getApplicationContext或者Activity.getApplication方法獲取。

避免context相關的內存泄露,記住以下幾點:
1. 不要讓生命週期長的對象引用activity context,即保證引用activity的對象要與activity本身生命週期是一樣的
2. 對於生命週期長的對象,可以使用application context
3. 避免非靜態的內部類,儘量使用靜態類,避免生命週期問題,注意內部類對外部對象引用導致的生命週期變化

根據packageName構造Context  

 

 

通常情況下獲取當前應用的context的方法是getApplicationContext,
但是通過根據其他的packageName如何構造 Context呢? 
Android平臺的應用實例其實還可以通過其他方式構造。
比如代碼   
     try 
    {
            Context ctx= createPackageContext("com.android123.Cwj", 0);

            //ctx已經是com.android123.cwj的實例
    } 
      catch (NameNotFoundException e) 
      {
        //可能由於 pacakgeName不存在所以必須處理該異常        
      }
複製代碼需要注意的是,createPackageContext方法的第二個參數可選爲CONTEXT_INCLUDE_CODE  和 CONTEXT_IGNORE_SECURITY ,
定義分別爲4和2,上面爲0。一般忽略安全錯誤問題可以通過CONTEXT_IGNORE_SECURITY 標記,
同時可能還需要處理 SecurityException 異常.

[Android] Context應該怎麼寫?  

 

如果想要彈出一個AlertDialog,要寫如下的代碼 

view sourceprint?1 AlertDialog.Builder alert =new AlertDialog.Builder(this);  

2 alert.setTitle("Warning");  

3 alert.setMessage("Wrong time!");  

4 alert.show(); 

 


這裏構造方法的原型是AlertDialog.Builder(Context arg) 需要一個Context類的對象作爲參數,一般我們都在Activity裏寫,所以用this,表示在當前的會話中彈出AlertDialog。

 


在我的一個程序裏,我自定義了一個接口Public interface CustomPickerListener,在實現這個接口的方法時我需要彈出來一個AlertDialog,這裏,參數表裏填寫this的話會提示錯誤:The constructor AlertDialog.Builder(new CustomPickerListener(){}) is undefined.


其實這個地方寫this很明顯是錯誤的,但是要寫什麼才能達成目的呢?


官方文檔上對context的解釋是Interface to global information about an application environment. This is an abstract class whose implementation is provided by the Android system. It allows access to application-specific resources and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.


於是我就試着寫上了我程序的 包.目標類.this ,如下 

view sourceprint?1 AlertDialog.Builder alert =new AlertDialog.Builder(com.android.alcoholtest.AlcoholTest.this); 

然後就成功了

 

 

Android中Activity共享變量的另一方法:Application context

 

Android中在不同Activity中傳遞變量,通常使用Intent中Bundle添加變量的操作方法。

  view plaincopy to clipboardprint?

  Intent intent = new Intent();

  intent.setClass(A.this, B.class);

  Bundle bundle = new Bundle();

  bundle.putString("Info", "Information");

  intent.putExtras(bundle);

  startActivity(intent);

  Intent

  intent = new Intent();

  intent.setClass(A.this, B.class);

  Bundle bundle = new Bundle();

  bundle.putString("Info", "Information");

  intent.putExtras(bundle);

  startActivity(intent);

  不過在多個Activity中經常使用同一變量時,使用Bundle則比較麻煩,每次調用Activity都需要設置一次。

  如想在整個應用中使用,在java中一般是使用靜態變量,而在android中有個更優雅的方式是使用Application context。

  新建一個類,繼承自Application

  view plaincopy to clipboardprint?

  class MyApp extends Application {

  private String myState;

  public String getState() {

  return myState;

  }

  public void setState(String s) {

  myState = s;

  }

  }

  class MyApp extends

  Application {

  private String myState;

  public String getState() {

  return myState;

  }

  public void setState(String s) {

  myState = s;

  }

  }

  在AndroidManifest.xml的application加個name屬性就可以了,如下面所示:

  view plaincopy to clipboardprint?

  

  < p> 

  android:name=".MyApp" android:icon="@drawable/icon"

  android:label="@string/app_name">

  使用時:

  view plaincopy to clipboardprint?

  class Blah extends Activity {

@Override

  public void onCreate(Bundle b){

  ...

  MyApp appState = ((MyApp)getApplicationContext());

  String state = appState.getState();

  ...

  }

  }

  class Blah extends

  Activity {

  @Override

  public void onCreate(Bundle b){

  ...

  MyApp appState = ((MyApp)getApplicationContext());

  String state = appState.getState();

  ...

  }

  }

  英文引用:http://stackoverflow.com/questions/708012/android-how-to-declare- global-variables

  The more general problem you are encountering is how to save stateacross several Activities and all parts of your application. A staticvariable (for instance, a singleton) is a common Java way of achievingthis. I have found however, that a more elegant way in Android is toassociate your state with the Application context.

  --如想在整個應用中使用,在java中一般是使用靜態變量,而在android中有個更優雅的方式是使用Application context。

  As you know, each Activity is also a Context, which is informationabout its execution environment in the broadest sense. Your applicationalso has a context, and Android guarantees that it will exist as asingle instance across your application.

  --每個Activity 都是Context,其包含了其運行時的一些狀態,android保證了其是single instance的。

  The way to do this is to create your own subclass of android.app.Application,and then specify that class in the application tag in your manifest.Now Android will automatically create an instance of that class andmake it available for your entire application. You can access it fromany context using the Context.getApplicationContext() method (Activityalso provides a method getApplication() which has the exact sameeffect):

  --方法是創建一個屬於你自己的android.app.Application的子類,然後在manifest中申明一下這個類,這是 android就爲此建立一個全局可用的實例,你可以在其他任何地方使用Context.getApplicationContext()方法獲取這個實例,進而獲取其中的狀態(變量)。

 

 

[小技巧]在任意位置獲取應用程序Context - [Android學習筆記]

 

Android程序中訪問資源時需要提供Context,一般來說只有在各種component中(Activity, Provider等等)才能方便的使用api來獲取Context, 而在某些工具類中要獲取就很麻煩了。爲此,我們可以自定義一個Application類來實現這種功能。

import android.app.Application;

public class MyApplication extends Application {
    private static MyApplication instance;

    public static MyApplication getInstance() {
        return instance;
    }

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        instance = this;
    }
}

然後在manifest中<application>中加入name="mypackage.MyApplication"就可以在任意類中使用MyApplication.getInstance()來獲取應用程序Context了。

Android獲取其他包的Context實例然後幹壞事

 

 

Android中有Context的概念,想必大家都知道。Context可以做很多事情,打開activity、發送廣播、打開本包下文件夾和數據庫、獲取classLoader、獲取資源等等。如果我們得到了一個包的Context對象,那我們基本上可以做這個包自己能做的大部分事情。 

         那我們能得到嗎?很高興的告訴你,能!
      Context有個createPackageContext方法,可以創建另外一個包的上下文,這個實例不同於它本身的Context實例,但是功能是一樣的。 


      這個方法有兩個參數:
1。packageName  包名,要得到Context的包名
2。flags  標誌位,有CONTEXT_INCLUDE_CODE和CONTEXT_IGNORE_SECURITY兩個選項。CONTEXT_INCLUDE_CODE的意思是包括代碼,也就是說可以執行這個包裏面的代碼。CONTEXT_IGNORE_SECURITY的意思是忽略安全警告,如果不加這個標誌的話,有些功能是用不了的,會出現安全警告。 


      下面給個小例子,執行另外一個包裏面的某個類的方法。
      另外一個包的包名是chroya.demo,類名Main,方法名print,代碼如下: 

Java代碼 
package chroya.demo;   
  
import android.app.Activity;   
import android.os.Bundle;   
import android.util.Log;   
  
class Main extends Activity {   
       
    @Override  
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
    }   
       
    public void print(String msg) {   
        Log.d("Main", "msg:"+ msg);   
    }   
}  

package chroya.demo;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

class Main extends Activity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    
    public void print(String msg) {
        Log.d("Main", "msg:"+ msg);
    }
}       本包的調用Main的print方法的代碼塊如下: 

Java代碼 
Context c = createPackageContext("chroya.demo", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);   
//載入這個類   
Class clazz = c.getClassLoader().loadClass("chroya.demo.Main");   
//新建一個實例   
Object owner = clazz.newInstance();   
//獲取print方法,傳入參數並執行   
Object obj = clazz.getMethod("print", String.class).invoke(owner, "Hello");  

Context c = createPackageContext("chroya.demo", Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
//載入這個類
Class clazz = c.getClassLoader().loadClass("chroya.demo.Main");
//新建一個實例
Object owner = clazz.newInstance();
//獲取print方法,傳入參數並執行
Object obj = clazz.getMethod("print", String.class).invoke(owner, "Hello");       ok,這樣,我們就調用了chroya.demo包的Main類的print方法,執行結果,打印出了Hello。 

      怎麼樣,這只是一個調用其他包的代碼的例子,我們獲取到Context,還可以做很多事情,當然,題目所說的壞事,還是不要做爲好。 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章