11.《android編程權(quán)威指南》筆記一

一、Android開發(fā)初體驗(yàn)

  監(jiān)聽器使用匿名內(nèi)部類的好處:1,因?yàn)槟涿麅?nèi)部類的使用,我們可在同一處實(shí)現(xiàn)監(jiān)聽器方法,代碼更清晰可讀。2,事件監(jiān)聽器一般只在同一處使用,使用匿名內(nèi)部類可避免不必要的命名類實(shí)現(xiàn)。

二、Android與MVC設(shè)計(jì)模式
模型對(duì)象存儲(chǔ)著應(yīng)用的數(shù)據(jù)和業(yè)務(wù)邏輯。模型類通常用來映射與應(yīng)用相關(guān)的一些事物,如用戶、商店里的商品、服務(wù)器上的圖片或者一些段電視節(jié)目,抑或GeoQuiz應(yīng)用里的地理知識(shí)問題。模型對(duì)象不關(guān)心用戶界面,它存在的唯一上的就是存儲(chǔ)和管理應(yīng)用數(shù)據(jù)。
視圖對(duì)象知道如何在屏幕上繪制自己,以及如何響應(yīng)用戶的輸入,如觸摸動(dòng)作等。一個(gè)簡(jiǎn)單經(jīng)驗(yàn)法則是,凡是能夠在屏幕上看見的對(duì)象,就是視圖對(duì)象。比如xml文件。
控制器對(duì)象含有應(yīng)用的邏輯單元,是視圖與模型對(duì)象的聯(lián)系紐帶??刂破鲗?duì)象響應(yīng)視圖對(duì)象觸發(fā)的種類事件,此外還管理著模型對(duì)象與視圖間的數(shù)據(jù)流動(dòng)。一般是Activity、Fragment或Service的一個(gè)子類。
int question = mQuestonBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question); //setText(@StringRes int resid)的另一個(gè)重構(gòu)方法
公共代碼抽取方法:refactor->extract->method。
Button: android:drawableRight="@drawable...
TextView點(diǎn)擊事件
ImageButton:android:src

三、Activity的生命周期
onCreate(內(nèi)存),onStart(可視),onResume(前臺(tái)),onPause(可視),onStop(內(nèi)存),onDestroy,onRestart
Log快捷鍵:logt、logd、logm
LogCat過濾設(shè)置:選擇Edit Filter Configuration選項(xiàng),單擊+按鈕創(chuàng)建消息過濾器,在Filter Name處輸入QuizActivity,Log Tag處同樣輸入QuizActivity,單擊OK?,F(xiàn)在,LogCat窗口僅顯示Tag為QuizActivity的日志信息。
設(shè)備處于水平方向時(shí),Android會(huì)找到并使用res/layout-land目錄下的布局資源。
FrameLayout里面控件使用android:layout_gravity屬性。
設(shè)備旋轉(zhuǎn)前保存數(shù)據(jù):onSaveInstanceState / if(savedInstanceState != null)...

四、Android應(yīng)用的調(diào)試
診斷應(yīng)用異常(配合邏輯診斷)
記錄棧跟蹤日志(查看方法在哪被調(diào)用):Log.d(TAG,"Updating question text ",new Exception());
設(shè)置斷點(diǎn)(Debug而不是Run),運(yùn)行后可檢查對(duì)象,變量的值,單擊this可看到超類的值。
使用異常斷點(diǎn)(調(diào)試時(shí)會(huì)定位到異常拋出的代碼行):Run --> View Breakpoints,單擊+,選擇Java Exception Breakpoints,輸入RuntimeException并選擇。異常斷點(diǎn)影響大建議及時(shí)清除不需要的斷點(diǎn)。
使用Android Lint(會(huì)檢查項(xiàng)目中所有潛在問題):Analyze --> Inspect Code...,選擇Whole project。
R類的問題(資源編譯錯(cuò)誤):重新檢查資源文件中XML文件的有效性、清理項(xiàng)目、使用Gradle同步項(xiàng)目、運(yùn)行Android Lint
布局檢查器(查看布局樹):androd monitor --> hierarchy View
內(nèi)存分配跟蹤:點(diǎn)擊按鈕啟動(dòng)后,在前臺(tái)操作應(yīng)用時(shí),后臺(tái)就開始記錄內(nèi)存分配狀況,尋找可優(yōu)化的點(diǎn)。

五、第二個(gè)Activity
tools和tools:text命名空間可以覆蓋某個(gè)組件的任何屬性。
activity調(diào)用startActivity()方法時(shí),調(diào)用請(qǐng)求發(fā)送給了操作系統(tǒng)的ActivityManager。ActivityManager負(fù)責(zé)創(chuàng)建Activity實(shí)例并調(diào)用其onCreate()方法。
//CheatActivity
private static final String EXTRA_ANSWER_IS_TRUE = "com.bignerdranch.android.geoquiz.answer_is_true";

public static Intent newIntent(Context packageContext,boolean answerIsTrue) {
    Intent intent = new Intent(packageContext,CheatActivity.class);
    intent.putExtra(EXTRA_ANSWER_IS_TRUE,answerIsTrue);
    return intent;
}
//QuizActivity
Intent intent = CheatActivity.newIntent(QuizActivity.this,answerIsTrue);
            startActivity(intent);
//CheatActivity
    mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE,false);

啟動(dòng)應(yīng)用時(shí),實(shí)際上是啟動(dòng)了應(yīng)用的lanunch activity。
ActivityManager維護(hù)著一個(gè)非特定應(yīng)用獨(dú)享的回退棧。所有應(yīng)用的activity都共享該回退棧。這也是將ActivityManager設(shè)計(jì)成操作系統(tǒng)級(jí)的activity管理器來負(fù)責(zé)啟動(dòng)應(yīng)用activity的原因之一。顯然,回退棧是作為一個(gè)整體共享于操作系統(tǒng)及設(shè)備,而不單單用于某個(gè)應(yīng)用。

六、Android SDK版本與兼容
使用Android Lint,在老版本的系統(tǒng)上調(diào)用新版本代碼時(shí),潛在問題在編譯時(shí)就能被發(fā)現(xiàn)。也就是說,如果使用了高版本系統(tǒng)API中的代碼,Android Lint會(huì)提示編譯錯(cuò)誤。
根據(jù)系統(tǒng)版本編譯:Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLTPOP
Android文檔在SDK安裝目錄中的docs目錄中。

七、UI fragment與fragment管理器
activity托管fragment:activity在其視圖層級(jí)里提供一處位置,用來放置fragment視圖。fragment本身沒有在屏幕上顯示視圖的能力。
生命周期:(setContentView方法中調(diào)用)onAttach,inCreate,onCreateView,(創(chuàng)建)onActivityCreate,(停止)onStart,(暫停)onResume,(運(yùn)行)onPause,(暫停)onStop,(停止)onDestroyView,(activity關(guān)閉)onDestroy,onDetach,銷毀。
activity托管UI fragment有兩種方式:在activity布局中添加fragment;在activity代碼中添加fragment。一般使用第二種方式。
onCreateView(){ View v = inflater.inflate(R.layout.fragment_crime,container,false); return v;} //CrimeFragment
EditText.addTextChangedListener

FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container); //看framelayout里面是否已經(jīng)被添加過fragment。
if(fragment == null) {
  fragment = new CrimeFragment();
  fm.beginTransaction().add(R.id.fragment_container,fragment).commit();

在activity處于運(yùn)行狀態(tài)時(shí),添加fragment時(shí),F(xiàn)ragmentManager立即驅(qū)趕fragment,調(diào)用從onAttach到onResume方法,追上activity步伐(與activity的最新狀態(tài)保持同步)后,托管activity的FragmentManager就會(huì)邊接收操作系統(tǒng)的調(diào)用指令,邊調(diào)用其他生命周期方法,讓fragment與activity的狀態(tài)取得一致。

要合理使用fragment。應(yīng)用單屏最多使用2~3個(gè)fragment。在開發(fā)中盡量使用fragment,因?yàn)楹笃谔砑犹闊?
要使用支持庫版fragment,應(yīng)用的activity必須繼承FragmentActivity。AppCompatActivity是FragmentActivity子類,F(xiàn)ragmentActivity又是Activity的子類。

八、使用RecyclerView顯示列表
單例是特殊的Java類,在創(chuàng)建實(shí)例時(shí),一個(gè)單例類僅允許創(chuàng)建一個(gè)實(shí)例。應(yīng)用能在內(nèi)存里存活多久,單例就能存活多久。
要?jiǎng)?chuàng)建單例,需創(chuàng)建一個(gè)帶有私有構(gòu)造方法及get()方法的類。如果實(shí)例已存在,get()方法就直接返回它;如果實(shí)例還不存在,get()方法就會(huì)調(diào)用構(gòu)造方法創(chuàng)建它。

使用抽象activity托管fragment,把通用的建立一個(gè)fragment的activity設(shè)置成超類,寫一個(gè)createFragment的抽象方法。

使用RecyclerView:ViewHolder只做一件事,容納View視圖。RecyclerView自身不會(huì)創(chuàng)建視圖,它創(chuàng)建的是ViewHolder,而ViewHolder引用著itemView。
  Adapter是一個(gè)控制器對(duì)象,從模型層獲取數(shù)據(jù),然后提供給RecyclerView顯示,是溝通的橋梁。它負(fù)責(zé)創(chuàng)建必要的ViewHolder和綁定ViewHolder至模型層數(shù)據(jù)。RecyclerView需要顯示視圖對(duì)象時(shí),就會(huì)去找它的Adapter。
  首先調(diào)用Adapter的getItemCount方法,詢問數(shù)組列表中包含多少個(gè)對(duì)象;接著RecyclerView調(diào)用Adapter的onCreateViewHolder方法創(chuàng)建ViewHolder及其要顯示的視圖;最后,Recycler會(huì)傳入ViewHolder及其位置,調(diào)用onBindViewHolder方法,Adapter會(huì)找到目標(biāo)位置的數(shù)據(jù)并將其綁定到ViewHolder的視圖上。所謂綁定,就是使用模型數(shù)據(jù) 填充視圖。
  需要注意的是,相對(duì)于onBindViewHoler方法,onCreateViewHolder方法的調(diào)用并不頻繁。一旦有了夠用的ViewHolder,RecyclerView就會(huì)停止調(diào)用onCreateView方法,隨后,它會(huì)回收利用舊的ViewHoler以節(jié)約時(shí)間和內(nèi)存。
  Recycler類不會(huì)新版擺放屏幕上的列表項(xiàng),實(shí)際上,擺放的任務(wù)被委托給了LayoutManager。LayoutManager還負(fù)責(zé)定義屏幕滾動(dòng)行為。
mCrimeRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
列表滾動(dòng)流暢歸功于onBindViewHolder方法,任何時(shí)候,都要確保這個(gè)方法輕巧、高效。
private class CrimeHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
        private Crime mCrime;

        private TextView mTitleTextView;
        private TextView mDateTextView;

        public CrimeHolder(LayoutInflater inflater,ViewGroup parent) {
            super(inflater.inflate(R.layout.list_item_crime,parent,false));
            itemView.setOnClickListener(this);

            mTitleTextView = (TextView) itemView.findViewById(R.id.crime_title);
            mDateTextView = (TextView) itemView.findViewById(R.id.crime_date);
        }

        public void bind(Crime crime) {
            mCrime = crime;
            mTitleTextView.setText(mCrime.getTitle());
            mDateTextView.setText(mCrime.getDate().toString());
        }

        @Override
        public void onClick(View v) {
            Toast.makeText(getActivity(),mCrime.getTitle() + " clicked!",Toast.LENGTH_SHORT).show();
        }
    }

    private class CrimeAdapter extends RecyclerView.Adapter<CrimeHolder> {
        private List<Crime> mCrimes;

        public CrimeAdapter(List<Crime> crimes) {
            mCrimes = crimes;
        }

        @Override
        public CrimeHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater layoutInflater = LayoutInflater.from(getActivity());

            return new CrimeHolder(layoutInflater,parent);
        }

        @Override
        public void onBindViewHolder(CrimeHolder holder, int position) {
            Crime crime = mCrimes.get(position);
            holder.bind(crime);
        }

        @Override
        public int getItemCount() {
            return mCrimes.size();
        }
    }
RecyclerView可代替ListView和GridView。
單例能方便地存儲(chǔ)和控制模型對(duì)象。但是無法做到持久存儲(chǔ),也不利于單元測(cè)試(用依賴注入工具解決)。

RecyclerView ViewType:可在RecyclerView中創(chuàng)建不同類的列表項(xiàng)。
定義三種item的xml視圖,對(duì)應(yīng)的需要定義三種不同的ViewHolder。 然后根據(jù)不同的需求,在Adapter里面識(shí)別并運(yùn)用這些ViewHolder,Adapter里面已經(jīng)定義好了一些方法,只需要重寫getItemViewType(int position)方法,給每個(gè)固定的position上的item返回一個(gè)固定的類型(ViewType)就能方便的表明每個(gè)item需要的ViewHolder:
class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    //User是一個(gè)自定義類,代表封裝的數(shù)據(jù)類型
    private List<User> mUsers;

    //三種不同的ViewType類型,事先用常量定義好
    public static final int VIEW_TYPE_ONE = 1;
    public static final int VIEW_TYPE_TWO = 2;
    public static final int VIEW_TYPE_THREE = 3;

    public MyAdapter(List<User> users) {
      mUsers = users;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

      RecyclerView.ViewHolder myViewHolder = null;
    //根據(jù)不同的ViewType類型,來返回不同的ViewHolder
      switch (viewType) {

        case VIEW_TYPE_ONE:
          myViewHolder = new ViewHolderOne
              (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_one, parent, false));
        break;

        case VIEW_TYPE_TWO:
          myViewHolder = new ViewHolderTwo
              (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_two, parent, false));
          break;

        case VIEW_TYPE_THREE:
          myViewHolder = new ViewHolderThree
              (LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item_three, parent, false));
          break;
      }

      return myViewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    //根據(jù)不同的ViewType類型,進(jìn)行不同的數(shù)據(jù)綁定操作
      switch (holder.getItemViewType()) {

        case VIEW_TYPE_ONE:
          ViewHolderOne holderOne = (ViewHolderOne) holder;
          holderOne.mUserAvatar.setImageResource(R.mipmap.ic_launcher);
          holderOne.mUserName.setText(mUsers.get(position).getUserName());
          holderOne.mUserInfo.setText(mUsers.get(position).getInfo());
          break;

        case VIEW_TYPE_TWO:
          ViewHolderTwo holderTwo = (ViewHolderTwo) holder;
          holderTwo.mUserAvatar.setImageResource(R.mipmap.user_avatar);
          holderTwo.mUserName.setText(mUsers.get(position).getUserName());
          holderTwo.mUserInfo.setText(mUsers.get(position).getInfo());
          break;

        case VIEW_TYPE_THREE:
          ViewHolderThree holderThree = (ViewHolderThree) holder;
          holderThree.mUserAvatar.setImageResource(R.mipmap.ic_launcher);
          holderThree.mUserName.setText(mUsers.get(position).getUserName());
          holderThree.mUserInfo.setText(mUsers.get(position).getInfo());
          break;
      }
    }

    @Override
    public int getItemCount() {
      if (null != mUsers) return mUsers.size();
      else return 0;
    }
    //重寫方法,給每個(gè)position上的item返回一個(gè)固定的ViewType類型
    //如果返回的ViewType類型不固定,則會(huì)出現(xiàn)各種item布局變化的情況,可能還會(huì)觸發(fā)bug
    @Override
    public int getItemViewType(int position) {
      return mUsers.get(position).getType();
    }
  }

九、使用布局與組件創(chuàng)建用戶界面
ConstraintLayout:添加四個(gè)方向上的約束可擺放組件位置,組件大小有三個(gè)選擇(組件自己決定wrap_content、手動(dòng)調(diào)整、充滿約束布局)。
選中組件,在組件的屬性視圖容器設(shè)置大小,有三中:固定大小|--|、包裹內(nèi)容>>>、動(dòng)態(tài)適應(yīng)。先把兩個(gè)組件全設(shè)為wrap_content,然后拖ImageView組件到crime_date下面。在預(yù)覽界面,拖住ImageView組件頂部的約束柄,將其拖向ConstraintLayout組件頂部,直到約束柄變綠,并彈出Release to Create Top Constraint這樣的提示時(shí),再松開鼠標(biāo),然后依次設(shè)置下部、右部約束。在xml中形式為:
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
對(duì)兩個(gè)TextView設(shè)置約束和邊距,寬度為動(dòng)態(tài)適應(yīng)(0dp),高度為wrap_content

樣式(style)是XML資源文件,含有用來描述組件行為和外觀屬性定義。主題是各種樣式的集合,從結(jié)構(gòu)上說,主題本身也是一種樣式資源,只不過它的屬性指向了其他樣式資源。使用主題引用,可將預(yù)定義的應(yīng)用主題樣式添加給指定組件,并確定組件在應(yīng)用中擁有正確一致的顯示風(fēng)格。

邊距屬性的默認(rèn)使用值是16dp或8dp。

十、使用fragment argument
直接獲取extra信息的缺點(diǎn):破壞了fragment的封裝,不再是可復(fù)用的構(gòu)建單元。更好的做法是使用fragment argument bundle。
每個(gè)fragment實(shí)例都可附帶一個(gè)Bundle對(duì)象。該bundle包含鍵-值對(duì),可以像附加extra到Activity的intent中那樣使用它們。一個(gè)鍵-值對(duì)即一個(gè)argument。
創(chuàng)建fragment argument:先創(chuàng)建Bundle對(duì)象,然后使用put方法,將argument添加到bundle中。然后調(diào)用Fragment.setArguments方法把a(bǔ)rgument bundle附加給fragment:
```
public static CrimeFragment newInstance(UUID crimeId) { //Fragment
Bundle args = new Bundle();
args.putSerializable(ARG_CRIME_ID,crimeId);

    CrimeFragment fragment = new CrimeFragment();
    fragment.setArguments(args);
    return fragment;
}

protected Fragment createFragment() { //Activity
UUID crimeId = (UUID) getIntent().getSerializableExtra(EXTRA_CRIME_ID);
return CrimeFragment.newInstance(crimeId);
}

  
    列表數(shù)據(jù)變化: mAdapter.notifyDataSetChanged();
    高效刷新:mAdapter.notifyItemChange(int);

十一、使用ViewPager
    ViewPager在某種程度上類似于RecyclerView,不過,相較于RecyclerView與Adapter間的協(xié)同工作,ViewPager與PagerAdapter間的配合要復(fù)雜得多,所以,一般使用Google提供的PagerAdapter的子類FragmentStatePagerAdapter:

mViewPager.setAdapter(new FragmentStatePagerAdapter(fragmentManager) {
@Override
public Fragment getItem(int position) {
Crime crime = mCrimes.get(position);
return CrimeFragment.newInstance(crime.getId());
}

        @Override
        public int getCount() {
            return mCrimes.size();
        }
    });

for (int i = 0;i < mCrimes.size();i++) {
if (mCrimes.get(i).getId().equals(crimeId)) {
mViewPager.setCurrentItem(i);
break;
}
}

    FragmentStatePagerAdapter在卸載不需要的fragment時(shí)會(huì)銷毀它。FragmentPagerAdapter會(huì)調(diào)用事務(wù)的detach而不是remove,只是銷毀了fragment的視圖,而實(shí)例還保存在FragmentManager中。前者更省內(nèi)存,后者適合用戶需要少量固定的fragment的情形。

    當(dāng)需要ViewPager托管非fragment視圖時(shí),需要自己實(shí)現(xiàn)PagerAdapter接口,

    不推薦使用代碼方式創(chuàng)建視圖,不過如果只需一個(gè)視圖時(shí),可用代碼創(chuàng)建。


十二、對(duì)話框
    建議將AlertDialog封裝在DialogFragment實(shí)例中使用,使用FragmentManager管理對(duì)話框,可以更靈活地顯示對(duì)話框,旋轉(zhuǎn)設(shè)備后對(duì)話框也不會(huì)消失:

public class DatePickerFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.date_picker_title)
.setPositiveButton(android.R.string.ok,null)
.create();
}
}

FragmentManager manager = getFragmentManager();
DatePickerFragment dialog = new DatePickerFragment();
dialog.show(manager,DIALOG_DATE);


    同一個(gè)activity托管的fragment間的數(shù)據(jù)傳遞:

public static DatePickerFragment newInstance(Date date) {
Bundle args = new Bundle();
args.putSerializable(ARG_DATE,date);

    DatePickerFragment fragment = new DatePickerFragment();
    fragment.setArguments(args);
    return fragment;
}

DatePickerFragment dialog = DatePickerFragment.newInstance(mCrime.getDate());
dialog.show(manager,DIALOG_DATE);

dialog.setTargetFragment(CrimeFragment.this,REQUEST_DATE);

public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {
return;
}

    if (requestCode == REQUEST_DATE) {
        Date date = (Date) data.getSerializableExtra(DatePickerFragment.EXTRA_DATE);
        mCrime.setDate(date);
        mDateButton.setText(mCrime.getDate().toString());
    }
}

private void sendResult(int resultCode,Date date) {
if (getTargetFragment() == null) {
return;
}

    Intent intent = new Intent();
    intent.putExtra(EXTRA_DATE,date);

    getTargetFragment()
            .onActivityResult(getTargetRequestCode(),resultCode,intent);
}

.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int year = mDatePicker.getYear();
int month = mDatePicker.getMonth();
int day = mDatePicker.getDayOfMonth();
Date date = new GregorianCalendar(year,month,day).getTime();
sendResult(Activity.RESULT_OK,date);
}
})

    編寫同樣的代碼用于全屏fragment或?qū)υ捒騠ragment時(shí),可選擇覆蓋DialogFragment.onCreateView方法,而非oncreateDialog方法,以實(shí)現(xiàn)不同設(shè)備上的信息呈現(xiàn)。


十三、工具欄
    使用AppCompat庫;
      android:theme="@style/AppTheme"
      <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

    res下new-->android resource file,選擇menu類型,命名為fragment_crime_list。
  <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/new_crime"
        android:icon="@android:drawable/ic_menu_add"
        android:title="@string/new_crime"
        app:showAsAction="ifRoom|withText" />
</menu>

    使用Android Studio內(nèi)置的Android Asset Studio工具為工具欄創(chuàng)建或定制圖片:右鍵單擊drawable,選擇New-->Image Asset,生成各類圖標(biāo)。

    創(chuàng)建菜單:

public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.fragment_crime_list,menu);
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}

public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.new_crime:
Crime crime = new Crime();
CrimeLab.get(getActivity()).addCrime(crime);
Intent intent = CrimePagerActivity.newIntent(getActivity(),crime.getId());
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}

    實(shí)現(xiàn)層級(jí)導(dǎo)航:android:parentActivityName=".CrimeListActivity"

    修改標(biāo)題:

private void updateSubtitle() {
CrimeLab crimeLab = CrimeLab.get(getActivity());
int crimeCount = crimeLab.getCrimes().size();
String subtitle = getString(R.string.subtitle_format,crimeCount);

    AppCompatActivity activity = (AppCompatActivity) getActivity();
    activity.getSupportActionBar().setSubtitle(subtitle);
}

case R.id.show_subtitle:
updateSubtitle();
return true;

    復(fù)數(shù)字條串資源:

<plurals name="subtitle_plural">
<item quantity="one">%1d crime</item> <item quantity="other">%1d crimes</item>
</plurals>

int crimeSize = crimeLab.getCrimes().size();
String subtitle = getResources().getQuantityString(R.plurals.subtitle_plural,crimeSize,crimeSize);


    用于RecyclerView的空視圖:在布局中添加一個(gè)textview,默認(rèn)不可見,當(dāng)數(shù)據(jù)為零時(shí)顯示可見。

十四、SQLite數(shù)據(jù)庫
    應(yīng)用上下文在應(yīng)用的全周期存在,而activity不一定存在,所以在單例中使用應(yīng)用上下文:mContext = context.getApplicationContext();

十五、使用隱式intent
    檢查可響應(yīng)任務(wù)的activity:
PackageManager packageManager = getActivity().getPackageManager();
        if (packageManager.resolveActivity(pickContact,
                PackageManager.MATCH_DEFAULT_ONLY) == null) {
            mSuspectButton.setEnabled(false);
        }

    過濾器驗(yàn)證代碼:pickContact.addCategory(Intent.CATEGORY_HOME);

    ShareCompat類有個(gè)內(nèi)部類IntentBuilder,使用這個(gè)類創(chuàng)建發(fā)送消息的Intent略微方便一些。


十六、使用intent拍照
    使用FileProvider:

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.lewanjiang.criminalintent.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/files/"/>
</provider>

<paths>
<files-path
name="crime_photo"
path="."/>
</paths>

    使用相機(jī)intent:

final Intent captureImage = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
boolean canTakePhoto = mPhotoFile != null &&
captureImage.resolveActivity(packageManager) != null;
mPhotoButton.setEnabled(canTakePhoto);
mPhotoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri uri = FileProvider.getUriForFile(getActivity(),
"com.lewanjiang.criminalintent.fileprovider",mPhotoFile);

            List<ResolveInfo> cameraActivities = getActivity()
                    .getPackageManager().queryIntentActivities(captureImage,
                            PackageManager.MATCH_DEFAULT_ONLY);

            for (ResolveInfo activity:cameraActivities) {
                getActivity().grantUriPermission(activity.activityInfo.packageName,
                        uri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            }

            startActivityForResult(captureImage,REQUEST_PHOTO);
        }
    });
    縮放的顯示位圖:

public class PictureUtils {
public static Bitmap getScaledBitmap(String path,int destWidth,int destHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path,options);

    float srcWidth = options.outWidth;
    float srcHeight = options.outHeight;
    
    int inSampleSize = 1;
    if (srcHeight > destHeight || srcWidth > destWidth) {
        float heightScale = srcHeight / destHeight;
        float widthScale = srcWidth / destWidth;
        
        inSampleSize = Math.round(heightScale > widthScale ? heightScale : widthScale);
    }
    
    options = new BitmapFactory.Options();
    options.inSampleSize = inSampleSize;
    
    return BitmapFactory.decodeFile(path,options);
}

public static Bitmap getScaledBitmap(String path, Activity activity) {
    Point size = new Point();
    activity.getWindowManager().getDefaultDisplay().getSize();
    
    return getScaledBitmap(path,size.x,size.y);
}

}

else if (requestCode == REQUEST_PHOTO) {
Uri uri = FileProvider.getUriForFile(getActivity(),
"com.lewanjiang.criminalintent.fileprovider",
mPhotoFile);
getActivity().revokeUriPermission(uri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
updatePhotoView();
}

private void updatePhotoView() {
if (mPhotoFile == null || !mPhotoFile.exists()) {
mPhotoView.setImageDrawable(null);
} else {
Bitmap bitmap = PictureUtils.getScaledBitmap(mPhotoFile.getPath(),getActivity());
mPhotoView.setImageBitmap(bitmap);
}
}

    優(yōu)化縮略圖加載:ViewTreeObserver


十七、雙版面主從用戶界面
@LayoutRes
    protected int getLayoutResId() {
        return R.layout.activity_fragment;
    }
setContentView(getLayoutResId());

  別名資源是一種指向其他資源的特殊資源,定義在refs.xml文件中。


十八、應(yīng)用本地化


十九、Android輔助功能
  TalkBack


二十、數(shù)據(jù)綁定與MVVM
  為什么需要用MVVM架構(gòu):當(dāng)應(yīng)用越來越復(fù)雜,fragment和activity開始膨脹,逐漸變得難以理解和擴(kuò)展。這個(gè)時(shí)候,控制器層就需要做功能拆分了。
  怎么拆?先搞清楚控制器類到底做了哪些工作,再把這些工作拆分到獨(dú)立的小類里。讓一個(gè)個(gè)拆開的小類協(xié)同工作。
  如何確定控制器類的不同使用呢?你的架構(gòu)可以給你答案,使用MVC/MVP時(shí)它們就是這個(gè)答案。
  每個(gè)視圖模型應(yīng)控制成多在規(guī)模,這要具體情況具體分析。如果 視圖模型過大,你還可以繼續(xù)拆分??傊?,你的架構(gòu)你把控。

  開啟數(shù)據(jù)綁定:
    dataBinding {
        enabled = true
    }
  然后把一般布局改造為數(shù)據(jù)綁定布局:把布局定義放入<layout>標(biāo)簽。
  實(shí)例化綁定類:
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        FragmentBeatBoxBinding binding = DataBindingUtil
                .inflate(inflater,R.layout.fragment_beat_box,container,false);
        binding.recyclerView.setLayoutManager(new GridLayoutManager(getActivity(),3));
        return binding.getRoot();
    }
  創(chuàng)建SoundHolder:
private class SoundHolder extends RecyclerView.ViewHolder {
        private ListItemSoundBinding mBinding;
        
        private SoundHolder(ListItemSoundBinding binding) {
            super(binding.getRoot());
            mBinding = binding;
        }
    }
  創(chuàng)建SoundAdapter:
private class SoundAdapter extends RecyclerView.Adapter<SoundHolder> {

        @Override
        public SoundHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(getActivity());
            ListItemSoundBinding binding = DataBindingUtil
                    .inflate(inflater,R.layout.list_item_sound,parent,false);
            return new SoundHolder(binding);
        }

        @Override
        public void onBindViewHolder(SoundHolder holder, int position) {

        }

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

  使用assets有兩面性:一方面,無需配置管理,可以隨意命名,并按自己的文件結(jié)構(gòu)組織它們;另一方面,沒有配置管理,無法自動(dòng)響應(yīng)屏幕顯示密度、語言這樣的設(shè)置配置變更,自然也就無法在布局或其他資源里自動(dòng)使用它們了。
  為何使用assets:如果使用resources系統(tǒng)要一個(gè)個(gè)去處理,效率非常低,因?yàn)檫@些文件不能全放在一個(gè)目錄下管理,所以使用assets。assets可以看作是一個(gè)微型文件系統(tǒng),支持任意層次的文件目錄結(jié)構(gòu),常用來加載大師圖片和違章資源。


二一、音頻播放與單元測(cè)試
  MMVM架構(gòu)極大方便了一項(xiàng)關(guān)鍵編程工作:?jiǎn)卧獪y(cè)試。

  利用SoundPool實(shí)現(xiàn)音頻播放:
  先添加兩個(gè)測(cè)試依賴:Mockito,Hamcrest。把compile改為testCompile。
  在類名上創(chuàng)建一個(gè)新測(cè)試類(光標(biāo)移到類名上,按Command+Shift+T,選擇Create New Test...),測(cè)試庫選JUnit4,勾選setUp/@before,其他保持默認(rèn)設(shè)置。選擇test目錄進(jìn)行單元測(cè)試
  實(shí)現(xiàn)測(cè)試類,使用虛擬依賴項(xiàng):
public class SoundViewModelTest {
    private BeatBox mBeatBox;
    private Sound mSound;
    private SoundViewModel mSubject;

    @Before
    public void setUp() throws Exception {
        mBeatBox = mock(BeatBox.class);
        mSound = new Sound("assetPath");
        mSubject = new SoundViewModel(mBeatBox);
        mSubject.setSound(mSound);
    }
}
  編寫測(cè)試方法:
@Test
    public void exposesSoundNameAsTitle() {
        assertThat(mSubject.getTitle(), is(mSound.getName()));
    }

  設(shè)備旋轉(zhuǎn)和對(duì)象保存:實(shí)現(xiàn)Serializable或者Parcelable接口。
  保留fragment:setRetainInstance(true);。原理:當(dāng)設(shè)備配置改變時(shí),fragment視圖被銷毀,但fragment本身不會(huì)被銷毀。然而,這個(gè)功能不推薦使用。第一是用起來更復(fù)雜,第二是因系統(tǒng)回收內(nèi)存而被銷毀時(shí),就會(huì)數(shù)據(jù)丟失。

  Esspresso與整合測(cè)試,Espresso是Google開發(fā)的一個(gè)UI測(cè)試框架。


二十二、樣式與主題
  在resources是定義顏色資源,一處定義,整個(gè)應(yīng)用中引用。colors.xml

  樣式是能夠應(yīng)用于視圖組件的一套屬性。styles.xml
<style name="BeatBoxButton">
        <item name="android:background">@color/dark_blue</item>
    </style>
style="@style/BeatBoxButton"   //xml中Button屬性中
  樣式支持繼承:
<style name="BeatBoxButton.Strong">
        <item name="android:textStyle">bold</item>
    </style>
  也可以采用指定父樣式的方式。

  主題可以看作是樣式的進(jìn)貨加強(qiáng)版,會(huì)自動(dòng)應(yīng)用于整個(gè)應(yīng)用,主題能引用外部和資源和其他樣式:
<style name="AppTheme" parent="Theme.AppCompat.Light">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/red</item>
        <item name="colorPrimaryDark">@color/dark_red</item>
        <item name="colorAccent">@color/gray</item>
    </style>
  覆蓋主題屬性:從現(xiàn)有主題往上找父主題,直到找到相關(guān)屬性為止
  修改按鈕屬性:逐級(jí)向上查找父主題,找到buttonSytle樣式,可以在Widget.AppCompat.Button中看到button的各個(gè)屬性。修改BeatBosButton的父樣式為Widget.AppCompat.Button。
<style name="BeatBoxButton" parent="Widget.AppCompat.Button">
  在主題中覆蓋buttonStyle屬性:
<item name="buttonStyle">@style/BeatBoxButton</item>

  如果是繼承自己內(nèi)部的主題,使用主題名指定父主題即可;如果繼承android操作系統(tǒng)中的樣式或主題,記得使用parent屬性
  在主題中引用資源時(shí),使用?符號(hào)。


二十三、XML drawable
  在android中,凡是要在屏幕上繪制的東西都可以叫drawable,比如抽象圖形、Drawable類的子類代碼、位圖圖像等。

  shape drawable可以把按鈕變成各種形狀:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/dark_blue" />
</shape>
  在styles中把上面的drawable作為按鈕背景:
        <item name="android:background">@drawable/button_beat_box_normal</item>

  state list drawable:狀態(tài)改變時(shí)變化。
  先定義一個(gè)用于按鈕按下狀態(tài)的shape drawable:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="@color/red" />
</shape>
  然后定義一個(gè)state list drawable:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/button_beat_box_pressed"
          android:state_pressed="true"/>
    <item android:drawable="@drawable/button_beat_box_normal" />
</selector>
  最后將上面的state list drawable作為按鈕背景:
 <item name="android:background">@drawable/button_beat_box</item>
  除了按下狀態(tài),state list drawable還支持禁用、聚集以及激活等狀態(tài)。

  layer list drawable能讓兩個(gè)drawable合二為一。比如為按下狀態(tài)的按鈕添加一個(gè)深色的圓環(huán):
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape xmlns:android="http://schemas.android.com/apk/res/android"
               android:shape="oval">
            <solid android:color="@color/red" />
        </shape>
    </item>
    <item>
        <shape android:shape="oval">
            <stroke
                android:width="4dp"
                android:color="@color/dark_red"/>
        </shape>
    </item>
</layer-list>

  drawable用起來靈活方便,不僅用法多樣,還易于更新維護(hù),能做出復(fù)雜的背景圖,連圖像編輯器都省了,更改應(yīng)用的配色也很簡(jiǎn)單。
  另外,drawable獨(dú)立于屏幕像素密度,不需要準(zhǔn)備多個(gè)版本來適合不同屏幕。

  把應(yīng)用啟動(dòng)器圖標(biāo)放在mipmap目錄中,其他圖片都放在drawable目錄中。


二十四、深入學(xué)習(xí)intent和任務(wù)
  Intent另一個(gè)構(gòu)造方法:setClassName(String packageName,String className)

  任務(wù)是一個(gè)activity棧,默認(rèn)情況下,新activity都在當(dāng)前任務(wù)中啟動(dòng)。
  啟動(dòng)新activity時(shí)啟動(dòng)新任務(wù):addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

  進(jìn)程是操作系統(tǒng)創(chuàng)建的、供應(yīng)用對(duì)象生存以及應(yīng)用運(yùn)行的地方。通常會(huì)占用操作系統(tǒng)管理著的系統(tǒng)資源,在Android中,每個(gè)進(jìn)程都需要一個(gè)虛擬機(jī)來運(yùn)行。
  每一個(gè)activity實(shí)例都存在于一個(gè)進(jìn)程之中,同一個(gè)任務(wù)關(guān)聯(lián)。這也是進(jìn)程與任務(wù)的唯一相似之處。任務(wù)只包含activity,這些activity通常來自于不同的應(yīng)用進(jìn)程;而進(jìn)程則包含了應(yīng)用的全部運(yùn)行代碼和對(duì)象
  并發(fā)文檔(concurrent document),可以為運(yùn)行的應(yīng)用動(dòng)態(tài)創(chuàng)建任意數(shù)目的任務(wù):android:documentLaunchMode="intoExisting" 


二十五、HTTP與后臺(tái)任務(wù)
  線程是個(gè)單一招行序列。單個(gè)線程中的代碼會(huì)逐步招行。所有的Android應(yīng)用的運(yùn)行都是從主線程開始的。然后,主線程不是線程那樣的預(yù)訂招行序列,相反,它處于一個(gè)無限循環(huán)的運(yùn)行狀態(tài),等著用戶或系統(tǒng)觸發(fā)事件。一旦有事件觸發(fā),主線程便招行代碼做出響應(yīng)。

  清理AsyncTask:AsyncTask.cancel(true)。如果fragment/activity已銷毀了,不撤銷AsyncTask,可能會(huì)引發(fā)內(nèi)存泄漏。
  第一個(gè)參數(shù)是輸入?yún)?shù)類型,第二個(gè)參數(shù)是指定發(fā)送進(jìn)度更新需要的類型,第三個(gè)參數(shù)是輸出類型。

  相比AsyncTask,推薦使用loader。遇到配置變化 時(shí)會(huì)自動(dòng)管理loader和加載的數(shù)據(jù) ,而且LoaderManager負(fù)責(zé)啟動(dòng)和停止,以及管理生命周期。

  分頁:RecyclerView.OnScrollListener

  動(dòng)態(tài)調(diào)整網(wǎng)格:ViewTreeObserver.OnGlobalLayoutListener


二十六、Looper、Handler和HandlerThread
  RecyclerView設(shè)置GridLayoutManager時(shí),意味著寬度會(huì)變,而高度固定(寬高分別為match_parent和120dp),為最大化利用ImageView的空間,設(shè)置scaleType的屬性值為centerCrop。

  在應(yīng)用圖片展示頁面,很多應(yīng)用通常會(huì)選擇僅在需要顯示 圖片時(shí)都去下載。顯然,RecyclerView及其adapter應(yīng)負(fù)責(zé)實(shí)現(xiàn)按需下載。adapter觸發(fā)圖片下載就放在onBindViewHolder方法中實(shí)現(xiàn)。Async是執(zhí)行后臺(tái)線程的最簡(jiǎn)單方式,但它不適用于那些重復(fù)且長(zhǎng)時(shí)間運(yùn)行的任務(wù)。放棄AsyncTask,創(chuàng)建一個(gè)專用的后臺(tái)線程。這是實(shí)現(xiàn)按需下載的最常用方式。
  創(chuàng)建并啟動(dòng)后臺(tái)線程:
public class ThumbnailDownloader<T> extends HandlerThread {
    private static final String TAG = "ThumbnailDownloader";
    
    
    private Boolean mHasQuit = false;
    
    public ThumbnailDownloader() {
        super(TAG);
    }

    @Override
    public boolean quit() {
        mHasQuit = true;
        return super.quit();
    }
    
    public void queueThunbnail(T target,String url) {
        Log.i(TAG, "queueThunbnail: URL " + url);
    }
}

  圖片下載可用Picasso、Glide、Fresco,Picasso小而類,Glide小巧,F(xiàn)resco性能好。

  StrictMode.enableDefaults方法能發(fā)現(xiàn)以下問題:在主線程上發(fā)起網(wǎng)絡(luò)請(qǐng)求、在主線程上做了磁盤讀寫、Activit未及時(shí)銷毀(泄露)、SQLite數(shù)據(jù)庫游標(biāo)未關(guān)閉、網(wǎng)絡(luò)通信使用明文(未使用SSL/TLS加密)。

  預(yù)加載以及緩存:LruCache。


二十七、搜索
  SearchView是個(gè)可以嵌入工具欄的操作視圖類(action view),用戶可以輸入查詢關(guān)鍵字,提交查詢請(qǐng)求搜索,返回結(jié)果將顯示在RecyclerView中。
  創(chuàng)建和響應(yīng)Menu菜單:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_item_search"
          android:title="@string/search"
          app:actionViewClass="android.support.v7.widget.SearchView"
          app:showAsAction="ifRoom" />

    <item android:id="@+id/menu_item_clear"
          android:title="@string/clear_search"
          app:showAsAction="never" />
</menu>
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.fragment_photo_gallery,menu);

        MenuItem searchItem = menu.findItem(R.id.menu_item_search);
        final SearchView searchView = (SearchView) searchItem.getActionView();
        
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
                Log.d(TAG, "onQueryTextSubmit: " + query);
                updateItems();
                return true;
            }

            @Override
            public boolean onQueryTextChange(String newText) {
                Log.d(TAG, "onQueryTextChange: " + newText);
                return false;
            }
        });
    }
  
  使用SharedPreferences存儲(chǔ)查詢記錄

  默認(rèn)顯示已保存查詢信息:
searchView.setOnSearchClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String query = QueryPreferences.getStoredQuery(getActivity());
                searchView.setQuery(query,false);
            }
        });


二十八、后臺(tái)服務(wù)
  創(chuàng)建IntentService

  后臺(tái)網(wǎng)絡(luò)連接安全:
private boolean isNetworkAvailableAndConnected() {
        ConnectivityManager cm = 
                (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
        
        boolean isNetworkAvailable = cm.getActiveNetwork() != null;
        boolean isNetworkConnected = isNetworkAvailable &&
                cm.getActiveNetworkInfo().isConnected();
        
        return isNetworkConnected;
    }

  使用AlarmManager延遲運(yùn)行服務(wù):
public static void setServiceAlarm(Context context,boolean isOn) {
        Intent i = PollService.newIntent(context);
        PendingIntent pi = PendingIntent.getService(context,0,i,0);

        AlarmManager alarmManager = (AlarmManager)
                context.getSystemService(Context.ALARM_SERVICE);

        if (isOn) {
            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME,
                    SystemClock.elapsedRealtime(),POLL_INTERVAL_MS,pi);
        } else {
            alarmManager.cancel(pi);
            pi.cancel();
        }
    }
  setRepeting方法是非精準(zhǔn)重復(fù)計(jì)時(shí)器。
  PendingIntent是一種token對(duì)象,將它交給其他應(yīng)用使用時(shí),它是代表當(dāng)前應(yīng)用發(fā)送token對(duì)象的。另外,它本身存在于操作系統(tǒng)而不是token里,因此實(shí)際上是你在控制著它。
  可以使用PendingIntent管理定時(shí)器,查看定時(shí)器激活與否:
public static boolean isServiceAlarmOn(Context context) {
        Intent i = PollService.newIntent(context);
        PendingIntent pi = PendingIntent.getService(context,0,i,PendingIntent.FLAG_NO_CREATE);
        return pi != null;
    }

  控制定時(shí)器:
boolean shouldStartAlarm = !PollService.isServiceAlarmOn(getActivity());
                PollService.setServiceAlarm(getActivity(),shouldStartAlarm);
 
  通知信息:Notification notification = new NotificationCompat.Builder(this)
                    .setTicker(resources.getString(R.string.new_pictures_title))
                    .setSmallIcon(android.R.drawable.ic_menu_report_image)
                    .setContentTitle(resources.getString(R.string.new_pictures_title))
                    .setContentText(resources.getString(R.string.new_pictures_text))
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .build();

            NotificationManagerCompat notificationManager =
                    NotificationManagerCompat.from(this);
            notificationManager.notify(0,notification);

  對(duì)于大多數(shù)服務(wù)任務(wù),推薦使用IntentService。原生服務(wù)不能在后臺(tái)線程上運(yùn)行。這也是推薦使用IntentService的最主要原因。大多數(shù)重要的服務(wù)都需要在后臺(tái)線程上運(yùn)行,而IntentService已提供了一套標(biāo)準(zhǔn)支持方案。
  服務(wù)生命周期:onCreate,onStartCommand,onDestroy。
  IntentService是一種non-sticky服務(wù)。
  sticky服務(wù)適用于長(zhǎng)時(shí)間運(yùn)行的服務(wù),如音樂播放器,即使這樣,也就考慮一種使用non-sticky服務(wù)的替代架構(gòu)方案。sticky服務(wù)的管理很不方便,因?yàn)楹茈y判斷服務(wù)是否已啟動(dòng)。

  在API 21  中,為更好地實(shí)現(xiàn)后臺(tái)服務(wù),引入了JobScheduler的全新API,除了能用來處理種類任務(wù)外,還能:發(fā)現(xiàn)沒有網(wǎng)絡(luò)時(shí),禁止服務(wù)啟動(dòng);如果請(qǐng)求失敗或網(wǎng)絡(luò)連接受限,提供稍后重試機(jī)制;控制只在設(shè)備充電的時(shí)候,才允許檢查是否有新圖片。還支持按場(chǎng)景、按條件運(yùn)行后臺(tái)服務(wù)。

  還可以使用sync adapter創(chuàng)建常規(guī)的polling網(wǎng)絡(luò)服務(wù)。


二十九、broadcast intent
  使用私有權(quán)限:在Manifest文件中定義,安全級(jí)別有四種

  如何用broadcast觸發(fā)長(zhǎng)時(shí)運(yùn)行任務(wù):將任務(wù)交給服務(wù)去處理,然后通過broadcast瞬時(shí) 啟動(dòng)服務(wù)。第二種是用goAsync,但不夠靈活,不推薦使用。

  事件總線:EventBus、RxJava

  本地廣播不支持有序,也不支持在獨(dú)立線程上發(fā)送和接收廣播。


三十、網(wǎng)頁瀏覽
  WebChromeClient是一個(gè)事件接口,用來響應(yīng)那些改變?yōu)g覽器中裝飾元素的事件。

  自己處理配置更改,資源修改符無法自動(dòng)工作。

  非HTTP鏈接支持:加載URI前,先檢查它的scheme,如果不是HTTP或HTTPS,就發(fā)送一個(gè)針對(duì)目標(biāo)URI的Intent.ACTION_VIEW。


三十一、定制視圖與觸摸事件
  創(chuàng)建定制視圖三大步驟:選擇超類;覆蓋超類的構(gòu)造方法;覆蓋其他關(guān)鍵方法。

public class BoxDrawingView extends View {
public BoxDrawingView(Context context) {
this(context,null);
}

public BoxDrawingView(Context context, AttributeSet attrs) {
    super(context,attrs);
}

}

  監(jiān)聽觸摸事件的一種方式是使用setOnTouchListener方法,不過,由于定制視圖是View的子類,可走捷徑覆蓋以下方法:onTouchEvent:
@Override
    public boolean onTouchEvent(MotionEvent event) {
        PointF current = new PointF(event.getX(),event.getY());
        String actioin = "";
        
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                actioin = "ACTION_DOWN";
                break;
            case MotionEvent.ACTION_MOVE:
                actioin = "ACTION_MOVE";
                break;
            case MotionEvent.ACTION_UP:
                actioin = "ACTION_UP";
                break;
            case MotionEvent.ACTION_CANCEL:
                actioin = "ACTION_CANCEL";
                break;
        }
        Log.i(TAG, "onTouchEvent: x " + current.x + "y " + current.y);
        return true;
    }
  跟蹤運(yùn)行事件:
public boolean onTouchEvent(MotionEvent event) {
        PointF current = new PointF(event.getX(),event.getY());
        String actioin = "";

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                actioin = "ACTION_DOWN";
                mCurrentBox = new Box(current);
                mBoxen.add(mCurrentBox);
                break;
            case MotionEvent.ACTION_MOVE:
                actioin = "ACTION_MOVE";
                if (mCurrentBox != null) {
                    mCurrentBox.setCurrent(current);
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                actioin = "ACTION_UP";
                mCurrentBox = null;
                break;
            case MotionEvent.ACTION_CANCEL:
                actioin = "ACTION_CANCEL";
                mCurrentBox = null;
                break;
        }
        Log.i(TAG, "onTouchEvent: x " + current.x + "y " + current.y);
        return true;
    }
 onDraw方法內(nèi)的圖形繪制
private Paint mBoxPaint;
    private Paint mBackgroundPaint;

    public BoxDrawingView(Context context) {
        this(context,null);
    }

    public BoxDrawingView(Context context, AttributeSet attrs) {
        super(context,attrs);

        mBoxPaint = new Paint();
        mBoxPaint.setColor(0x22ff0000);

        mBackgroundPaint = new Paint();
        mBackgroundPaint.setColor(0xfff8efe0);
    }
protected void onDraw(Canvas canvas) {
        canvas.drawPaint(mBackgroundPaint);

        for (Box box:mBoxen) {
            float left = Math.min(box.getOrigin().x,box.getCurrent().x);
            float right = Math.max(box.getOrigin().x,box.getCurrent().x);
            float top = Math.min(box.getOrigin().y,box.getCurrent().y);
            float botton = Math.max(box.getOrigin().y,box.getCurrent().y);

            canvas.drawRect(left,top,right,botton,mBoxPaint);
        }
    }

  設(shè)備旋轉(zhuǎn)問題:protected Parcelable onsavedInstanceState(),protected void onRestoreInstanceState(Pracelable state)


三十二、屬性動(dòng)畫
  ObjectAnimator heightAnimator = ObjectAnimator.ofFloat(mSunView,"y",sunYStart,sunYEnd).setDuration(3200);
heightAnimator.setInterpolator(new AccelerateInterpolator());
heightAnimator.start();


三十三、地理位置和Play服務(wù)


三十四、使用地圖


三十五、material design
  material surface:elevation和Z值、state list animator
 
   動(dòng)畫工具:circular reveal、shared element transition

  新的視圖組件:card、floating action button、snackbar


三十六、編后語
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 說到下拉刷新控件,網(wǎng)上版本有很多,很多軟件也都有下拉刷新功能。有一個(gè)叫XListView的,我看別人用過,沒看過是...
    AiPuff閱讀 3,515評(píng)論 3 43
  • 開發(fā)自定義控件的步驟: 1、了解View的工作原理 2、 編寫繼承自View的子類 3、 為自定義View類增加屬...
    Ten_Minutes閱讀 1,529評(píng)論 1 9
  • 轉(zhuǎn)載請(qǐng)務(wù)必在文章開頭注明出處!http://www.itdecent.cn/p/a3014f8442b0 一、簡(jiǎn)介...
    XuYanjun閱讀 16,273評(píng)論 6 69
  • 孩子的成長(zhǎng)有其內(nèi)在軌跡,家長(zhǎng)因?yàn)椴欢?往往容易從成人社會(huì)的規(guī)定出發(fā),導(dǎo)致破壞。 對(duì)于不懂的家長(zhǎng),如果做不到接納,...
    育麟閱讀 131評(píng)論 0 1
  • 媽媽一個(gè)人帶著你來到重慶,因?yàn)楦职仲€氣半夜定了機(jī)票走的倉促,下飛機(jī)時(shí)都毫無頭緒,還好你有個(gè)冰雪聰明媽媽,...
    幻慕慕閱讀 297評(píng)論 0 2

友情鏈接更多精彩內(nèi)容