Android工具箱之Context解析

這是一個系列,咱們將其命名爲工具箱,若是你尚未看以前的文章:php

Android工具箱之文件目錄html

Android工具箱之組織你的代碼文件java

Android工具箱之理解app資源文件android

Android工具箱之Activity生命週期segmentfault

Android工具箱之遷移到AppCompat數組

Android工具箱之Android 6.0權限管理app

這幾天一直在思考一個問題,爲何國內的熱門博客和熱門教程都是好久以前的,例如我向學習EventBus,不管是鴻洋的博文仍是其餘論壇,幾乎清一色的OnEvent,或者好比我想學習Dagger2,文章數量更是少之又少,關鍵大量仍是Dagger1的內容。ide

基於此,外加上看到CodePath公司整合的Android資源正好符合實際需求,因此特地在sg開闢專欄,但願你們可以喜歡,在此申明下,由於工做量巨大,我很是有幸可以同@xixicat一塊兒翻譯這一專題,也懇請你們,如遇到任何翻譯錯誤,請指正,可評論中註明,也可電郵我,同時若是某位志趣相投人士有興趣參與翻譯,也可電郵我,我會進一步聯繫你:neuyuandaima@gmail.com。工具

廢話很少說,那麼咱們就開始吧。佈局

動機

你有多少次在StackOverflow中尋找答案時,發現其答案居然是2年前的,你又有多少次搜出的博文是幾年前的舊文呢,我相信絕大部分的你都有這樣的經歷,因此咱們爲何不能利用社交讓咱們的的Android文檔佈滿每一個細節呢。

初篇之Context初探

概述

Context對象能夠獲取應用狀態的信息,其使得activitys和Fragments以及Services可以使用資源文件,圖片,主題,以及其餘的文件夾內容。其也能夠用於使用Android自帶服務,例如inflate,鍵盤,以及content providers。

不少狀況下,當你須要用到Context的時候,你確定只是簡單的利用當前activity的實例this。當你一個被activity建立的內部對象的時候,例如adapters裏或者fragments裏的時候,你須要將activity的實例傳給它們。而當你在activity以外,例如application或者service的時候,咱們須要利用application的context對象代替。

Contex的用途

明確地啓動一個組件,例如activity或者service

Intent intent = new Intent(context, MyActivity.class);
startActivity(intent);

建立視圖

TextView textView = new TextView(context);

Contexts包含了如下信息:

  • 設備的屏幕大小以及將dp,sp轉化爲px的尺寸。

  • style屬性

  • onClick屬性

inflate xml佈局文件

咱們使用context來得到LayoutInflater,其能夠在內存中inflate xml佈局文件

LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.my_layout, parent);

發送本地廣播

咱們使用context來得到LocalBroadcastManager,其能夠發送或者註冊廣播接收。

Intent broadcastIntent = new Intent("custom-action");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);

獲取系統服務

例如當你須要發送通知,你須要NotificationManager。

NotificationManager manager = 
    (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

int notificationId = 1;

// Context is required to construct RemoteViews
Notification.Builder builder = 
    new Notification.Builder(context).setContentTitle("custom title");

notificationManager.notify(notificationId, builder.build());

在此就不一一列舉系統服務了,系統服務列表參見。

應用級別的Context和Activity級別的Context

當主題被運用在應用層面,其也可被運用在activity層面,好比當應用層面定義了一些主題,activity能夠將其覆蓋。

<application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
       <activity
           android:name=".MainActivity"
           android:label="@string/app_name"
           android:theme="@style/MyCustomTheme">

大部分視圖須要傳入activity級別的Context對象,這樣其才能獲取主題,styles,dimensions等屬性。若是某個控件沒有使用theme,其默認使用了應用的主題。

在大部分狀況下,你須要使用activity級別的Context。一般,關鍵字this表明着一個類的實例,其可被用於activity中的Context傳遞。例如:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  
        Toast.makeText(this, "hello", Toast.LENGTH_SHORT).show();
    }
}

匿名方法

當咱們使用了匿名內部類的適合,例如實現監聽,this關鍵字的使用:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        TextView tvTest = (TextView) findViewById(R.id.abc);
        tvTest.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {
                  Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
              }
          });
        }
    }

適配器

數組適配器

當你爲listview定義適配器的適合,getContext()方法被常用,其用來實例化xml佈局。

if (convertView == null) {
      convertView = 
          LayoutInflater
              .from(getContext())
              .inflate(R.layout.item_user, parent, false);
   }

注意:當你傳入的是應用級別的context,你會發現themes/styles屬性將不會被應用,因此確保你在這裏傳入的是Activity級別的context。

RecyclerView適配器

public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.ViewHolder> {

    @Override 
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = 
            LayoutInflater
                .from(parent.getContext())
                .inflate(itemLayout, parent, false);
        
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        // If a context is needed, it can be retrieved 
        // from the ViewHolder's root view.
        Context context = viewHolder.itemView.getContext();

        // Dynamically add a view using the context provided.
        if(i == 0) {
            TextView tvMessage = new TextView(context);
            tvMessage.setText("Only displayed for the first item.")

            viewHolder.customViewGroup.addView(tvMessage);
        }
    }

   public static class ViewHolder extends RecyclerView.ViewHolder {
       public FrameLayout customViewGroup;

       public ViewHolder(view imageView) {
           super(imageView);
           
           // Perform other view lookups.
           customViewGroup = (FrameLayout) imageView.findById(R.id.customViewGroup);
       }
   }
}

能夠看到,ArrayAdapter須要在其構造器裏面傳入context,RecyclerView.Adapter不須要。

RecyclerView一般將其做爲父視圖傳給RecyclerView.Adapter.onCreateViewHolder()。

若是在onCreateViewHolder()方法的外面,你須要用到context,你也可使用ViewHolder,例如viewHolder.itemView.getContext()。
itemView是一個公有,非空,final類型的成員變量。

避免內存泄露

應用級別的context一般在單例中使用,例如一個經常使用的管理類,其管理Context對象來獲取系統服務,可是其不能同時被多個activity獲取。因爲維護一個activity級別的context引用會致使內存泄露,因此你須要使用application級別的context替代。

在下面這個例子中,若是context是activity級別或者service級別,當其被destroy,其實際不會被gc, 由於CustomManager類擁有了其static應用。

pubic class CustomManager {
    private static CustomManager sInstance;

    public static CustomManager getInstance(Context context) {
        if (sInstance == null) {

            // This class will hold a reference to the context
            // until it's unloaded. The context could be an Activity or Service.
            sInstance = new CustomManager(context);
        }

        return sInstance;
    }

    private Context mContext;

    private CustomManager(Context context) {
        mContext = context;
    }
}

適當地存儲context:利用應用級別context

爲了不內存泄露,不要在其生命週期之外持有該對象。檢查你的非主線程,pending handlers或者內部類是否持有context對象。

存儲應用級別的context的最好的辦法是CustomManager.getInstance(),其爲單例,生命週期爲整個應用的進程。

public static CustomManager getInstance(Context context) {
    if (sInstance == null) {

        // When storing a reference to a context, use the application context.
        // Never store the context itself, which could be a component.
        sInstance = new CustomManager(context.getApplicationContext());
    }

    return sInstance;
}

參考

相關文章
相關標籤/搜索