温斯顿吴的个人博客 woojean.com

《Android编程权威指南》读书笔记

2017-04-05

Android编译过程

image

Android的MVC设计模式

Model层:存储应用的数据和业务逻辑,通常被设计来映射与应用相关的一些事物,存在的唯一目的是存储和管理应用数据。 View层:知道如何在屏幕上绘制自己以及如何响应用户的输入,如触摸等。通常由布局文件中定义的各类组件构成。 Controller层:用来响应由视图对象触发的各类事件,以及管理模型对象与视图层间的数据流动。在Android中,控制器通常是Activity、Fragment或Service的一个子类。

Activity生命周期

设备旋转时,当前看到的Activity实例会被系统销毁,然后重新创建。因为设备旋转会改变设备配置,所谓设备配置即用来描述设备当前状态的一系列特征,包括:屏幕的方向、屏幕的密度、屏幕的尺寸、键盘类型、底座模式、语言等等。通常为匹配不同的设备配置,应用会提供不同的备选资源,在运行时,当设备配置发生变更时,会销毁当前Activity并重建。 为了保存设备旋转以前的数据,需要覆盖onSaveInstanceState(Bundle outState)方法,将一些数据保存在Bundle中,然后在onCreate()方法中取回这些数据。注意:在Bundle中存储和恢复的数据类型只能是基本数据类型以及可以实现Serializable接口的对象。 onSaveInstanceState方法通常在onPause()、onStop()、onDestory()方法之前由系统调用,并不仅仅用来处理设备配置变更的问题,当用户离开当前activity管理的用户界面,或Activity需要回收内存时,activity也会被销毁。在描述Activity的生命周期时,需要将其考虑进来:

image

Activity记录:当调用onSaveInstanceState方法时,用户数据会被保存在Bundle对象中,然后操作系统将Bundle对象放入Activity记录中,在需要恢复Activity时,操作系统可以使用暂存的Activity记录重新激活Activity。即使用户离开当前应用(此时彻底停止了当前应用的进程),暂存的Activity记录依然被系统保留。除非用户通过按后退键退出应用,或者系统重启,或者长时间不适用Activity,此时系统彻底销毁当前Activity,暂存的Activity记录通常也会被清除。

Activity通信

activity调用startActivity(…)方法时,调用请求实际发给了ActivityManager,ActivityManager负责创建Activity实例,并调用其onCreate()方法。

Intent对象是Android Component(Activity、Service、Broadcast Receiver、Content Provider)之间用来通信的一种媒介工具。如果通过指定Context与Class对象的方式来创建Intent,则创建的是显式Intent,否则就是隐式Intent(指定动作和标志)。显式Intent通常用于同一个应用内的通信,隐式Intent通常用于不同应用间的通信。

启动Activity-不带参数:

Intent i = new Intent(XxxActivity.this,YyyActivity.class);
startActivity(i);

启动Activity-带参数:

Intent i = new Intent(XxxActivity.this,YyyActivity.class);
boolean param = true;
i.putExtra(PARAM_NAME,param);
startActivity(i);

被启动Activity获取参数:

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mParam = getIntent().getBooleanExtra(PARAM_NAME);

启动Activity后,获取被启动Activity的返回值: 启动:

Intent i = new Intent(XxxActivity.this,YyyActivity.class);
boolean param = true;
i.putExtra(PARAM_NAME,param);
startActivityForResult(i,REQUEST_CODE);

被启动Activity返回:

Intent i= new Intent();
i.putExtra(RETURN_VALUE,mReturn);
setResult(RESULT_OK,i);  // 另一个可取标记为Activity.RESULT_CANCELED

接收返回值:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
  if(data == null){
    return;
  }
  mParam = data.getBooleanExtra(PARAM_NAME,false);
}

注意这里onActivityResult(…)方法的签名中既需要启动时的requestCode,也需要返回时的resultCode。

ActivityManager维护着一个非特定应用独享的回退栈,所有应用的activity都共享该回退栈。即ActivityManager被设计成操作系统级的activity管理器来负责启动activity,不局限于单个应用,回退栈作为一个整体共享给操作系统及设备使用。当用户点击一个应用时,实际是启动了该应用的launcher activity,并将它加入到activity栈中。

SDK版本

SDK最低版本:操作系统会拒绝将应用安装在系统版本低于该标准的设备上。 SDK目标版本:告知系统该应用是给哪个API级别去运行的,高于目标版本的系统功能将被忽略。 SDK编译版本:该设置不会出现在Manifest文件中(不同于最低版本与目标版本),不会通知给操作系统,而是用于编译时指定具体要使用的系统版本。

检查设备的Android系统的编译版本: if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ){ … }

禁止Lint提示兼容性问题:

@TargetApi(11)
@Override
protected void onCreate(Bundle savedInstanceState){
  // ...
  if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ){
    // ...
  }
}

Fragment的使用

Fragment在API 11以后才被引入到标准库。之前的版本要想使用Fragment,必须使用Android支持库中的android.support.v4.app.Fragment并配合android.support.v4.app.FragmentActivity类来使用。

Fragment不能单独呈现UI,他必须托管于Activity才能使用,有两种托管Fragment的方式: 1.添加fragment到activity布局中;(布局文件方式,在Activity的生命周期中无法切换fragment视图) 2.在activity代码中添加fragment;(代码方式,可以在运行时控制fragment)

添加Fragment到Activity的步骤: 1.定义Fragment的视图文件 2.新建继承于Fragment的自定义类,重写其onCreateView方法,渲染布局文件,设置布局中各个控件的回调函数,最后返回该视图:

public class CrimeFragment extends Fragment{
  @Override
  public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle savedInstanceState){
    View v = inflater.inflate(R.layout.fragment_crime,parent,false);
    mTitleField = (EditText)v.findViewById(R.id.xxx);
    mTitleField.addTextChangedListener(
      new TextWatcher(){
        public void onTextChanged(CharSequence c, int start, int before, int count){
          // ...
        };
      }
    );
    return v;
  }
}

3.在Activity中通过FragmentManager将Fragment添加到Activity的布局中:

public class CrimeActivity extends FragmentActivity{
  @Override
  public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_xxx);

    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
    if(fragment == null){ // 判断是否已存在该Fragment
      fragment = new CrimentFragment();
      fm.beginTransaction().add(R.id.fragmentContainer,fragment).commit();
    }
  }
}

Fragment的生命周期

image

托管Activity的onCreate()方法执行之后,Fragment的onActivityCreated(…)方法也会被调用。在Activity处于停止、暂停或运行状态下时,FragmentManager立即驱使fragment跟上activity的步伐(维护两者的状态一致),直到与activity的最新状态保持同步。比如向处于运行状态的activity中添加fragment时,以下fragment生命周期的方法会被依次调用:onAttach()、onCreate()、onCreateView()、onActivityCreated()、onStart()、onResume()。

android:layout_weight属性

layout_weight属性用于LinearLayout进行子组件的布局安排。在决定子组件视图的宽度(横向布局时)时,LinearLayout使用的是layout_width与layout_weight参数的混合值,具体分为以下两步: 1.LinearLayout查看layout_weight属性值(竖直方向则是layout_height属性),比如两个空间的layout_width都设置为wrap_content,则依他们的实际内容的宽度绘制控件。 2.LinearLayout依据layout_weight属性值进行额外的空间分配,若两者layout_weight属性相同,则均分剩余空间,如一个空间layout_weight为2,另一个为1,则为2的控件获得2/3的剩余空间,为1的控件获得1/3的剩余空间。 如果想让控件占据相同的宽度,可以将layout_weight设为0dp,将layout_weight设为一样。

使用ListFragment显示列表 自定义一个继承于android.v4.app.ListFragment的类,该类默认生成一个ListView布局,因此无需覆盖onCreateView()方法,或为其生成布局。

创建一个托管该ListFragment的Activity类,方法与添加一般的Fragment一样。

在自定义ListFragment的onCreate()方法中实现一个ArrayAdapter,并使用setListAdapter()方法进行设置。实现ArrayAdapter有两种方式,一是通过指定Item的视图文件来定义每一项的视图,二是自定一个继承自ArrayAdapter的类,重写其getView()方法,该方法基于实际的数据集来映射出具体的视图。

重写ListFragment的onListItemClick()方法,设置点击列表项时的响应事件。该方法会传入一个position参数用以判断所点击的具体项。

之后,数据有更新时,则修改底层getview()方法所使用的数据集,然后调用ListAdapter的notifyDataSetChanged()方法,通常在Fragment的onResume()方法中调用。

Fragment Argument

每个Fragment实例都可附带一个Bundle对象,该bundle对象包含key-value对,一个key-value对即是一个argument。通过调用Fragment.setArguments(Bundle)方法进行设置,该任务必须在fragment创建后、添加给activity之前完成。通常的做法是实现一个newInstance()方法来封装以上行为:

public class CrimeFragment extends Fragment{

  @Override
  public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    // 获取argument
    UUID crimeId = (UUID)getArguments.getSerializable(EXTRA_CRIME_ID); 
  }

  public static CrimeFragment newInstance(UUID crimeId){
    Bundle args = new Bundle();
    args.putSerializable(EXTRA_CRIME_ID,crimeId);
    CrimeFragment fragment = new CrimeFragment();
    fragment.setArgument(args);
    return fragment;
  }
  // ...
}

在Activity中调用newInstance()方法

public class CrimeActivity extends SingleFragmentActivity{
  @Override
  protected Fragment createFragment(){
    UUID crimeId = (UUID)getIntent().getSerializableExtra(CrimeFragment.EXTRA_CRIME_ID);
    return CrimeFragment.newInstance(crimeId);
  }
  // ...
}

Fragment也有startActivityForResult(…)和onActivityResult(…)方法,但没有setResult(…)方法。

使用ViewPager展示Fragment

使用ViewPager配合PagerAdapter(实际是其子类FragmentPagerAdapter或FragmentStatePagerAdapter)来实现Fragment的切换,而不是使用AdapterView(AdapterView有一个和ViewPager类似的子类Gallery)与Adapter来实现,是因为:AdapterView无法使用现有的Fragment,需要编写代码及时地提供View,然而决定fragment视图何时创建的是FragmentManager,所以当Gallery要求Adapter提供fragment视图时,我们无法立即创建fragment并提供视图。PagerAdapter比Adapter复杂很多,因为其要处理更多的视图管理相关工作,其中代替使用getView()方法,PagerAdapter使用下列方法:

public Object instantiateItem(ViewGroup container,int position);
public void destroyItem(ViewGroup container,int position,Object object);
public abstract boolean isViewFromObject(View view,Object object);

使用ViewPager时,只需为其设置PagerAdapter,并重写getCount()和getItem()这两个方法即可:

public class CrimePagerActivity extends FragmentActivity{
  private ViewPager mViewPager;
  Private ArrayList<Crime> mCrimes;

  @Override
  public void onCreate(Bundle savedInstanceState){
    super.onCreate();
    mViewPager = new ViewPager(this);
    mViewPager.setId(R.id.viewPager);  // 

FragmentManager要求任何作为fragment的容器的视图都需要具有一个资源ID,可在res/values/下新建一个ids.xml,写入以下内容:

<resources xmlns:android=’...’>
<item type=”id” name=”viewPager”/>
</resources>
setContentView(mViewPager);  //  以代码方式定义视图,而不是传入一个布局的ID

mCrimes = CrimeLab.get(this).getCrimes();

FragmentManager fm = getSupportFragmentManager();  

//这里设置了和fm的关系,相当于委托它来负责具体的Fragment视图添加工作
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm){ 
  @Override
  public int getCount(){ 
    return mCrimes.size();
  }

  @Override
  public Fragment getItem(int pos){ // 返回一个Fragment
    Crime crime mCrime.get(pos);
    return CrimeFragment.newInstance(crime.getId());  
  }
});
}
}

除了FragmentStatePagerAdapter外,还有一个可用的PagerAdapter的子类FragmentPagerAdapter。两者的区别在于在卸载不再需要的fragment时,所采用的处理方法不同。FragmentStatePagerAdapter不会销毁掉不需要的fragment,在销毁fragment时,会将其onSaveInstanceState(Bundle)方法中的Bundle信息保存下来,用户切换回原来的页面后,保存的实例的状态可用于恢复生成新的fragment。而FragmentPagerAdapter对于不再需要的fragment则选择调用事务的detach(Fragment)方法,而非remove(Fragment)方法来处理,即只是销毁了fragment的视图,但仍将fragment实例保留在FragmentManager中,因此用FragmentPagerAdapter创建的fragment永远不会被销毁。通常来说,FragmentStatePagerAdapter更加节省内存,对于包含图片等内容比较大的fragment,最好选用FragmentStatePagerAdapter。 ViewPager默认加载当前屏幕上的列表项以及左右相邻页的数据,从而实现页面滑动的快速切换,可以通过调用setOffscreenPageLimit(int)方法来指定预加载相邻页面的数目。 ViewPager默认显示PageAdapter中的第一个列表项,可通过调用setCurrentItem()方法来设置具体项。

基于Fragment的对话框及同一个Activity的两个Fragment之间的数据传递

Android推荐的做法是将对话框,比如一个AlertDialog,封装在DialogFragment实例中,以通过FragmentManager来管理对话框,而不是直接显示。使用FragmentManager管理对话框可使用更多配置选项来显示对话框,另外如果设备发生旋转,独立配置使用的AlertDialog会在旋转后消失,而配置封装在fragment中的AlertDialog则不会有此问题。

要将DialogFragment添加给FragmentManager管理,并放到屏幕上,可调用以下两种方法: public void show(FragmentManager manager, String tag) // 事务可自动创建并提交 public void show(FragmentTransaction transaction, String tag)

从一个Fragment中打开另一个Fragment,并传值,使用Fragment Arguments。 从被打开的Fragment中返回数据,需要调用setTargetFragment()设置目标Fragment,并主动去调用目标Fragment的onActivityResult()方法,其效果类似Activity的sendResult()方法,但是Fragment没有该方法,因此可以自定义一个来实现同样的功能。

以在一个Fragment上点击弹出日期选择的对话框为例:

// 定义一个DialogFragment的子类,重写其onCreateDialog()方法,返回一个对话框
public class DatePickerFragment extends DialogFragment{
  @Override
  public Dialog onCreateDialog(Bundle savedInstanceState){
    View v = getActivity().getLayoutInflater().inflater(R.layout.dialog_date,null);
    return new AlertDialog.Builder(getActivity()).setView(v)  // 设置对话框的显示内容,如一个根节点为DatePicker的布局文件
    .setTitle(...)
	.setPositiveButton(
      android.R.string.ok,
      new DialogInterface.OnClickListener(){
        public void onClick(DialogInterface dialog,int which){
          sendResult(Activity.RESULT_OK);
        }
      }
    )
    .create();
  }

  // 模拟Activity的sendResult()方法
  private void sendResult(int resultCode){  
    if(getTargetFragment() == null)
    return;

    Intent i = new Intent();
    i.putExtra(EXTRA_DATE,mDate);  // 设置返回值,mDate的值通过重写DatePicker的onDateChanged()方法进行设置
    getTargetFragment().onActivityResult(getTargetRequestCode(),resultCode,i);
  }
}

// 触发显示对话框
public class CrimeFragment extends Fragment{
  //...
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup parent,Bundle savedInstanceState){
  mDateButton = (Button)findViewById(R.id.crime_date);
  mDateButton.setOnClickListener(new View.OnClickListener(){
  public void onClick(View v){
    FragmentManager fm = getActivity().getSupportFragmentManager();
    DatePickerFragment dialog = new DatePickerFragment();
    dialog.setTargetFragment(CrimeFragment.this,REQUEST_DATE);
    dialog.show(fm,DIALOG_DATE);
  }
}); 
...
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
// 接受、处理从Fragment中返回的数据
}
}

音频播放

public class AudioPlayer{
private MediaPlayer mPlayer;

public void stop(){
if(mPlayer != null){
mPlayer.release(); // 除非调用release()方法,否则MediaPlayer将一直占着音频解码硬件及其他系统资源。另有一个stop()方法,会使MediaPlayer实例进入停止状态,需要时再重新启动。对于简单的音频播放应用,应该直接release()
mPlayer = null;
}
}

public void play(Context c){
stop(); // 避免用户多次单击Play按钮创建多个MediaPlayer实例
mPlayer = MediaPlayer.create(c, R.raw.one_small_step);  // R.raw.one_small_step是raw文件夹下的一个音频资源
// 监听音频播放结束事件,结束后主动调用stop()方法,释放占用资源
mPlayer.setCompletionListener(new MediaPlayer.OnCompletionListener(){
public void onCompletion(MediaPlayer mp){
stop();
}
});
mPlayer.start();
}
}

此外,因为MediaPlayer运行在一个不同的线程上,因此一旦启动,即使其所在Fragment被销毁了,MediaPlayer仍可以不停地播放。因此,需要覆盖Fragment的onDestroy()方法:

	// 在Fragment销毁时,释放音频播放资源
@Override
public void onDestroy(){
super.onDestroy();
mPlayer.stop();
}

Fragment的保留

调用setRetainInstance(true)。 保留Fragment利用了这样一个事实:可销毁和重建fragment的视图而无需销毁fragment自身。 fragment的restainInstance属性默认为false,表明其不会被保留。设置为true后可保留fragment,已保留的fragment不会随activity一起被销毁(如旋转设备),其全部实例变量值也将保持不变。当新的activity创建后,新的FragmentManager会找到被保留的Fragment,并重新创建它的视图。 虽然保留的fragment没有被销毁,但它已脱离消亡中的activity并处于保留状态,尽管此时fragment仍然存在,但已经没有任何activity在托管它。fragment必须同时满足两个条件才能进入保留状态: 1.已调用fragment的setRetainInstance(true)方法; 2.因设备配置改变,托管activity正在被销毁;

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

Android并不鼓励保留Fragment,情况未知。

保留Fragment与重写onSaveInstanceState()方法的主要区别在于数据可以存多久。如只是短暂保存数据,则使用保留Fragment比较方便,可以不用操心要保留的对象是否实现了Serializable接口。但是如果需要持久化地存储数据,则需要重写onSaveInstanceState()方法,因为当用户短暂离开应用后,如果因为系统回收内存需要销毁activity,则保留的fragment也会被随之销毁。

配置修饰符

Android提供了用于不同语言的配置修饰符,因此简化了本地化处理的过程:首先创建带有目标语言配置修饰符的资源子目录,然后将可选资源放入其中。Android资源系统会为我们处理其他后续工作。可指定多重配置修饰符,各配置修饰符必须按照优先级顺序排列,例如values-zh-land是一个有效的资源目录名,但是values-land-zh则无效,因为语言配置符的优先级高于布局方向的优先级。 有些配置修饰符的兼容性并不具有非黑即白的排他性,例如API级别就不是一个严格匹配的修饰符,修饰符-v11可兼容运行API11级及更高级别的系统版本设备。 在同一个子目录下,不能以文件的扩展名为依据来区分命名相同的资源文件。 所有资源都必须保存在res/目录的子目录下,尝试在res/目录的根目录下保存资源将会导致编译错误。因为res/子目录的名字直接与Android的编译过程绑定。此外,也无法在res/的子目录下创建多级子目录。

选项菜单

创建一个定义菜单的XML文件,放在res/menu目录下,Android会自动生成该XML文件的对应的资源ID: <menu cmlns:android=’xxx’> <item android:id=”@+id/menu_item_new_crime” android:icon=”@android:drawable/ic_menu_add” android:title=”@string/new_crime” android:showAsAction=”ifRoom|withText” // 其他可选值:always、never /> </menu> showAsAction属性用于指定菜单选项是显示在操作栏上,还是隐藏到溢出菜单中。

在Fragment中实例化选项菜单:

public class CrimeListFragment extends ListFragment{
...
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);  // Fragment的onCreateOptionMenu(...)方法是由FragmentManager负责调用的,因此当activity接收到来自操作系统的onCreateOptionMenu(...)方法调用时,必须明确告诉FragmentManager:其所管理的fragment应接收onCreatOptionMenu(...)方法的调用
...
}

@Override
public void onCreateOptionMenu(Menu menu, MenuInflater inflater){
super.onCreateOptionsMenu(menu,inflater);
inflater.inflate(R.menu.fragment_crime_list,menu); // 加载菜单布局
}

@Override
public boolean onOptionsItemSelected(MenuItem item){ // 响应菜单点击事件
switch(item.getItemId()){
case R.id.menu_item_new_crime:
...
return true;
default:
return super.onOptionsItemSelected(item);
}
}

}

使用上下文操作实现列表视图的多选操作

上下文操作是一种与某个特定屏幕视图而非整个屏幕相关联的操作,通过长按视图触发,例如删除列表中的某一项。在API11以下的设备上展示为一个浮动菜单,在较新的设备上通过上下文操作栏呈现。 在较新设备上长按视图进入上下文操作模式是提供上下文操作的主流方式,屏幕进入上下文操作模式时,上下文菜单中定义的菜单项出现并覆盖操作栏。相比浮动菜单,上下文操作栏不会遮挡屏幕,因而是更好的菜单展现方式。 列表视图进入上下文操作模式时,可开启它的多选模式。多选模式下,上下文操作栏上的任何操作都将同时应用于所有已选视图。

public class CrimeListFragment extends ListFragment{
@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle savedInstanceState){
View v = super.onCreateView(inflater,parent,savedInstanceState);
ListView listView = (ListView)v.findViewById(android.R.id.list);

// 设置列表视图的选择模式
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);  

// MutiChoiceModeListener实现了另一个接口:ActionMode.Callback,用户屏幕进入上下文操作模式时,会创建一个ActionMode实例,之后在其生命周期内Callback接口的回调方法会在不同时点被调用
listView.setMultiChoiceModelListener(new MultiChoiceModeListener(){ 

// 视图在选中,或撤销选中时触发
public void onItemCheckedStateChanged( 
ActionMode mode, int position, long id, boolean checked){
...
}

// 在ActionMode对象创建后调用,也是实例化上下文菜单资源,并显示在上下文操作栏上的任务完成的地方
public boolean onCreateActionMode(ActionMode mode, Menu menu){
// 注意这里是从ActionMode对象获取MenuInflater对象,而不是通过Activity对象来获得。ActionMode负责对上下文操作栏进行配置,比如设置标题等,Activity的MenuInflater做不到这一点。
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.crime_list_item_context,menu); // 渲染菜单视图
return true;
}

// 在onCreateActionMode()方法之后,以及当前上下文操作栏需要刷新显示新数据时调用
public boolean onPrepareActionMode(ActionMode mode,Menu menu){
return false;
}

// 在用户选中某个菜单项操作时调用,是响应上下文菜单项操作的地方
public boolean onActionItemClicked(ActionMode mode,MenuItem item){
switch(item.getItemId()){
case R.id.menu_item_delete_crime:
CrimeAdapter adapter = (CrimeAdapter)getListAdapter();
CrimeLab crimeLab = CrimeLab.get(getActivity());
for(int i = adapter.getCount()-1; i>=0; i--){
// 多选模式,删除一个或多个Crime对象
if(getListView().isItemChecked(i)){
crimeLab.deleteCrime(adapter.getItem(i));
}
}
mode.finish();
adapter.notifyDataSetChanged();
return true;
default:
return false;
}
}

// 在用户退出上下文操作模式或所选菜单项操作已被响应,从而导致ActionMode对象将要销毁时调用
public void onDestroyActionMode(ActionMode mode){
..
}
});
return v;
}

以上通过使用MultiChioceModeListener接口的方式,会自动创建ActionMode实例。因此以上解决方案可应用于ListView或GridView。对于其他视图,要使用上下文操作栏,需要实现一个View.OnLongClickListener接口的监听器,然后在监听器实现中调用Activity.startActionMode(…)方法创建一个ActionMode实例。startActionMode(…)方法需要一个实现了ActionMode.Callback接口的对象作为参数,而该接口即包含了上述的各个回调方法。

层级式导航

使用后退键的导航称为临时性导航,只能返回到上一次的用户界面。层级式导航可逐级向上在应用内导航。Android可轻松利用操作栏上的应用图标实现层级式导航,即逐级向上直至应用的初始界面。通常,应用图标一旦启用了向上导航按钮的功能,在应用图标的左边就会显示一个向左指向的图标。

在定义应用图标的点击响应事件时,回到某Activity可以使用Intent.FLAG_ACTIVITY_CLEAR_TOP来启动指定的Activity。然而,Android有更好的办法实现层级式导航:配合使用NavUtils以及manifest中的元数据。 首先修改AndroidManifest.xml文件,在CrimePagerActivity声明中添加meta-data属性,指定CrimePagerAactivity的父类为CrimeListActivity: <activity android:name=”.CrimePagerActivity” android:label=”@string/app_name”> <meta-data android:name=”android.support.PARENT_ACTIVITY” android:value=”.CrimeListActivity”> </activity>

public class CrimeFragment extends Fragment{
...
@Override
public void onCreate(Bundle savedInstanceState){
...
setHasOptionsMenu(true);  
}

// 响应应用图标菜单项
@Override
public boolean onOptionsItemSelected(MenuItem item){ // 响应菜单点击事件
switch(item.getItemId()){
case android.R.id.home:  // 响应回到父Activity
if(NavUtils.getParentActivityName(getActivity())!=null){
NavUtils.navigateUpFromSameTask(getActivity());
}
return true;
default:
return super.onOptionsItemSelected(item);
}
}


@TargetApi(11)
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle savedInstanceState){
...
if(BUILD.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
getActivity().getActionBar.setDisplayHomeAsUpEnabled(true);  // 启用应用图标的向上导航功能,但这仅仅是让应用图标转变为按钮,显示一个向左的图标而已
}
...
}

操作json

定义一个支持json转换的类:

public class Crime{
private static final String JSON_ID = id;
private static final String JSON_TITLE = title;
private static final String JSON_DATE = date;

private UUID mID;
private String mTitle;
private Date mDate = new Date();

public Crime(){
mId = UUID.randomUUID();
}

// json对象转换为属性
public Crime(JSONObject json) throws JSONException{
mId = UUID.fromString(json.getString(JSON_ID));
if(json.has(JSON_TITLE)){
mTitle = json.getString(JSON_TITLE);
}
mDate = new Date(json.getLong(JSON_DATE));
}

// 属性转换成json对象
public JSONObject toJSON() throws JSONException{
JSONObject json = new JSONObject();
json.put(JSON_ID,mId.toString());
json.put(JSON_TITLE,mTitle);
json.put(JSON_DATE,mDate.getTime());
return json;
}
}

基于支持json转换的类,实现json文件互转的类:

public class CriminalIntentJSONSerializer{
private Context mContext;
private String mFilename;
public CriminalIntentJSONSerializer(Context c, String f){
mContext = c;
mFilename = f;
}

// 将类实例对象的数组转换为JSONObject的数组,再调用JSONObject的toString()方法转换为字符串,然后写入文件中
public void saveCrimes(ArrayList<Crime> crimes) throws JSONException,IOException{
JSONArray array = new JSONArray();
for(Crime c:crimes)
array.put(c.toJSON());
Writer writer = null;
try{
OutputStream out = mContext.openFileOutput(mFilename,Context.MODE_PRIVATE);
writer = new OutputStreamWriter(out);
writer.write(array.toString());
}
finally{
if(writer != null)
writer.close();
}
}

// 读取json文件,拼接为字符串,解析为JSONArray,再转换为所需的类的实例
public ArrayList<Crime> loadCrimes() throws JSONException,IOException{
ArrayList<Crime> crimes = new ArrayList<Crime>();
BufferedReader reader = null;
try{
InputStream in = mContext.openFileInput(mFilename);
reader = new BufferdReader(new InputStreamReader(in));
StringBuilder jsonString = new StringBuilder();
String line = null;
while((line = reader.readline())!=null){
jsonString.append(line);
}
JSONArray array = (JSONArray)new JSONTokener(jsonString.toString()).nextValue(); // JSONTokener是json文本解析类
for(int i=0; i<array.length(); i++){
crimes.add(new Crime(array.getJSONObject(i)));
}
}
catch(FileNotFoundException e){
...
}
finally{
if(reader != null)
reader.close();
}
retuen crimes;
}
}

相机取景

可以通过隐式Intent来与照相机进行交互,相机应用会自动侦听MediaStore.ACTION_IMAGE_CAPTURE创建的Intent。 image Surface对象代表原始像素数据的缓冲区。SurfaceView类实现了SurfaceHolder接口,通过该接口来操作Surface。SurfaceView出现在屏幕上时,会创建Surface对象,当SurfaceView从屏幕上消失时,Surface对象即被销毁。不同于其他对象,SurfaceView不会自我绘制内容,任何想将内容绘制到Surface缓冲区的对象,都称为Surface的客户端,如Camera。SurfaceHolder提供了一个接口SurfaceHolder.Callback,该接口监听Surface生命周期中的事件,以此控制Surface与其客户端协同工作。

创建一个Fragment布局,包含一个SufaceView、一个Button和一个进度条ProgressBar:


文章目录