android开发教程 常用控件讲解

 更新时间:2016年9月20日 19:56  点击:1569
本教程我们来讲讲在android开发经常用到的简单控件,android自定义控件简单示例,ListView控件的简单使用。

基本控件


1.TextView:

功能与传统的桌面应用开发中的Label控件相似,用于显示文本信息

如:

<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#0000ff"
        android:textSize="40sp"
        android:text="@string/hello_world" />


显示效果:


上面的xml代码中,设置了几个常用的属性:

android:layout_width和android:layout_height分别设置控件的宽高

textColor设置显示的文本的颜色

textSize设置显示的文本的字体大小

text设置显示的文本内容。

2.Button:

前面用到的比较多,经常被用到的就是通过id获取按钮,然后绑定单击监听事件,这里仅列举个例子:

activity_main.xml:

<TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#0000ff"
        android:textSize="40sp"
        android:text="@string/hello_world" />
    <Button
        android:id="@+id/btn"
        android:layout_below="@id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnText"/>


MainActivity.java:

public class MainActivity extends ActionBarActivity {
 
    private TextView tv;
      private Button btn;
 
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        btn = (Button) findViewById(R.id.btn);
        tv = (TextView) findViewById(R.id.tv);
        btn.setOnClickListener(new OnClickListener() {
                
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      tv.setText("It's changed!");
                 }
           });
    }
}


3.EditText:

即文本输入框,如下修改程序,在按钮之上添加一个EditText,点击按钮,会获取EditText的值并把它设置为TextView的Text属性:

activity_main.xml:

<TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#0000ff"
        android:textSize="40sp"
        android:text="@string/hello_world" />
    <EditText
        android:id="@+id/et"
        android:layout_below="@id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:hint="@string/hintText"
        />
      <Button
        android:id="@+id/btn"
        android:layout_below="@id/et"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnText"/>


MainActivity.java:

public class MainActivity extends ActionBarActivity {
    private TextView tv;
      private Button btn;
      private EditText et;
 
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        btn = (Button) findViewById(R.id.btn);
        tv = (TextView) findViewById(R.id.tv);
        et = (EditText)findViewById(R.id.et);
        btn.setOnClickListener(new OnClickListener() {
                
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      Editable text = et.getText();
                      tv.setText(text.toString());
                 }
           });
    }
}


运行效果:

 
输入值,然后点击按钮:

 


注意到由于EditText的layout_height属性是wrap_content,所以会随着输入内容的增多不断变大,影响整体布局。若想固定其高度,可以设置maxLines属性,设置最多只显示的行数,其他内容向上滚动

如:android:maxLines = “1”

EditText的高度就不会变化了。

4.ImageView:

使用来显示图片的一个控件,之前的程序中曾经用到过,当然,它最主要的属性肯定是要显示图片的来源了,即android:src属性,将要显示的图片存放在res/drawable中,如图片名为hero.png。要显示该图片,设置android:src=”@drawable/hero”即可。

<ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/hero"/>


显示结果:



5.ProgressBar:

即进度条,使用style属性,可以设置不同的显示风格:

1)不设置style属性或者设置为style="?android:attr/progressBarStyle" ,环形显示

 
2)style="?android:attr/progressBarStyleHorizontal",水平横条显示

 


3)style="?android:attr/progressBarStyleLarge",大号的环形显示

 


4)style="?android:attr/progressBarStyleSmall",小号的

 


进度条当然是用来显示进度的,通过findViewById()获取ProgressBar,然后使用setProgress()就可以设置当前进度,使用getProgress()可以获取当前进度。

如:

布局代码:

<ProgressBar
          android:id="@+id/pb"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          style="?android:attr/progressBarStyleHorizontal"
          android:max="100"
          />
      <Button
          android:id="@+id/btn"
          android:layout_below="@id/pb"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@string/add_progress"/>


Activity代码:

public class MainActivity extends ActionBarActivity {
    private ProgressBar pb;
      private Button btn;
      @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pb = (ProgressBar) findViewById(R.id.pb);
        btn = (Button) findViewById(R.id.btn);
        Log.i("PB",pb.getProgress()+"");
        btn.setOnClickListener(new OnClickListener() {
                
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      Log.i("PB",pb.getProgress()+"");
                      pb.setProgress(pb.getProgress()+10);
                 }
           });
    }
}


运行结果:

 


初始时,默认进度为0



多次点击按钮之后:

 


达到android:max所设置的最大值后,再加也不会有变化了。

6.AlertDialog:

这个控件就是弹出一个对话框,类似于桌面开发中的模态对话框,必须关闭该对话框,才能进行后续交互操作,可用于显示比较重要的内容。

AlertDialog的构造方法都是protected,没法直接通过构造来创建AlertDialog,但是可以通过其内部类Builder来创建。

具体使用可以参考帮助手册中关于这个内部类的帮助信息,下面举个简单例子:

AlertDialog.Builder dialog = new AlertDialog.Builder(this);
        dialog.setTitle("Warning");
        dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                      // TODO Auto-generated method stub
                     
                 }
           });
        dialog.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
                      // TODO Auto-generated method stub
                     
                 }
           });
        dialog.setMessage("warning, hahaha");
        dialog.show();


运行结果:

 


7.ProgressDialog:

类似于AlertDialog,也是对话框,不过它显示的内容是一个进度条,好像是对话框和进度条两个控件的结合。

ProgressDialog pd = new ProgressDialog(this);

pd.setTitle("Data Loading...");

pd.show();

运行结果:




自定义控件简单示例

有时候,可能觉得系统提供的控件太丑,就会需要自定义控件来实现自己想要的效果。

以下主要参考《第一行代码》

1.自定义一个标题栏:

系统自带的标题栏很丑,且没什么大的作用,所以我们之前会在onCreate()中调用requestWindowFeature(Window.FEATURE_NO_TITLE);设置不显示标题栏。

下面自定义一个标题栏,中间显示标题,左右各有一个按钮:

title.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:background="#bbbbbb" >
    <Button
        android:id="@+id/btn_back"
        android:text="@string/back"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:layout_gravity="left|center_vertical"
        android:textColor="#0099cc"
        android:layout_weight="1"/>
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#0099cc"
        android:text="@string/this_is_title"
        android:layout_gravity="center"
        android:gravity="center"
        android:layout_weight="2"/>
    <Button
        android:id="@+id/btn_edit"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/edit"
        android:layout_margin="5dp"
        android:layout_gravity="right|center_vertical"
        android:textColor="#0099cc"
        android:layout_weight="1"/>
</LinearLayout>


Activity代码:

protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);     
           requestWindowFeature(Window.FEATURE_NO_TITLE);
           setContentView(R.layout.title);
}

运行结果:

 


2.复用布局代码:

想让这个标题栏应用在以后的每个布局文件,要怎么做呢?

总不能每次都把这些xml代码重写一遍吧。

android布局中提供了类似于c预处理指令#include的<include>标签,可以实现布局代码的复用。

下面新建一个first_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <include layout="@layout/title"/>
    <Button android:id="@+id/btn"
        android:text="@string/i_m_a_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>


修改setContentView(R.layout.first_layout);

显示结果:

 


现在Back和Edit按钮都没有任何事件处理的,怎样实现点击Back按钮就结束当前Activity呢?方法跟之前的做法完全一样,使用findViewById()根据id找到Back按钮,然后设置click事件监听即可。

代码如下:

public class FirstActivity extends Activity {
      @Override
      protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);     
           requestWindowFeature(Window.FEATURE_NO_TITLE);
           setContentView(R.layout.first_layout);
           Button btn = (Button) findViewById(R.id.btn_back);
           btn.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      FirstActivity.this.finish();
                 }
           });
      }
}


布局文件的复用已然通过<include>实现了,但是每次都要重新写事件监听,还是觉得麻烦……到这里一般就会想到抽象出一个自定义类,每次需要的时候,直接使用该自定义类不就行了,其实就是自定义控件的做法了。

3.自定义控件,复用功能代码

TitleLinearLayout.java代码:

public class TitleLinearLayout extends LinearLayout {
      public TitleLinearLayout(Context context, AttributeSet attrs) {
           super(context, attrs);
           LayoutInflater.from(context).inflate(R.layout.title, this);
           Button btn_back = (Button) findViewById(R.id.btn_back);
           btn_back.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
                      // TODO Auto-generated method stub
                      Log.i("clicked","back");
                      ((Activity)getContext()).finish();
                 }
           });
      }
}


继承自LinearLayout,实现带两个参数的构造方法。在构造方法中,加载布局文件,并对其中的Back按钮进行事件监听设置。

LayoutInflater.from(context).inflate(R.layout.title, this);用于动态加载布局文件。

注意到,Activity中有一个获取LayoutInflater的方法,所以,也可以使用下面一行代码加载布局文件:

((Activity)context).getLayoutInflater().inflate(R.layout.title, this);这种方法,在Activity代码中比较常用,而这里需要进行类型强转,反倒麻烦点,而且不如第一个方法安全。

如何使用自定义的控件呢?

first_layout代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <cn.csc.custom_ui.TitleLinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </cn.csc.custom_ui.TitleLinearLayout>
    <Button android:id="@+id/btn"
        android:text="@string/i_m_a_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"/>
</LinearLayout>


说明:

1)在布局文件中,引用自定义的控件,需要使用完整的类限定名,即包名.类名的方式;

2)在定义控件中,设置属性时,使用alt+/进行代码提示补全功能将经常不可用,标签名可以先设置为内置控件,然后进行属性的设置,之后再把标签名改回到自定义的控件的完整限定名即可。



控件ListView的简单使用

MVC模式:

MVC的基本原理就是通过Controller连接View和Model。当View中所显示的数据发生变化时,会通知Controller,然后由Controller调用Model中的相关方法执行相应的数据修改操作。反之,当Model中的数据发生变化时,也会通知Controller,由Controller通知View更新显示内容。如此一来,就使得数据部分与视图部分相分离,任何一方发生改变都不会影响到另一方。

而在android中,MVC的一个常见应用就是ListView显示数据。V代表的就是显示控件;M代表的是各种数据源,可以是自己定义的List或者数组,也可以是数据库,文件等;C代表的是Adapter类,android中比较常见的adapter有:BaseAdapter,ArrayAdapter,SimpleAdapter等。

ListView通过setAdapter方法实现了其和一个Adapter对象的绑定,Adapter一般通过getView()方法返回当前列表项所要显示的View对象,完成了对Model中数据的读取。

当Model发生变化时,会调用BaseAdapter.notifyDataSetChanged()方法通知组件数据已然变化,此时Adapter就会调用getView()方法重新显示组件内容。

ListView:间接继承自抽象类AdapterView。

常见方法:

void  setOnItemClickListener(AdapterView.OnItemClickListener listener)

void  setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)

void  setAdapter(ListAdapter adapter)

Adapter:是一个接口

主要的方法:

abstract int  getCount()  返回要显示的item总数

abstract Object  getItem(int position)  根据item索引返回该item

abstract long  getItemId(int position)  返回item的id。

abstract View  getView(int position, View convertView, ViewGroup parent)返回一个用来展示数据源中索引为position的View对象。

BaseAdapter是一个间接实现了Adapter接口的抽象类

自定义Adapter继承BaseAdapter时,需要提供上面四个方法的实现。主要要实现的是getCount()和geView()方法。

ArrayAdapter<T>, CursorAdapter, SimpleAdapter则直接继承自BaseAdapter,实现了其抽象方法。

ListView示例1:展示一个字符串数组中的各个字符串。

main_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <ListView
        android:id="@+id/listview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>


MainActivity.java:

public class MainActivity extends Activity {
     
      private String[] strs;
      @Override
      protected void onCreate(Bundle savedInstanceState) {
           // TODO Auto-generated method stub
           super.onCreate(savedInstanceState);
           setContentView(R.layout.main_layout);
           strs = new String[]{"aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii"};
           ListView lv = (ListView) findViewById(R.id.listview);
           lv.setAdapter(new MyBaseAdapter());
          
      }
      class MyBaseAdapter extends BaseAdapter{
           @Override
           public int getCount() {
                 return strs.length;
           }
           @Override
           public Object getItem(int position) {
                 // TODO Auto-generated method stub
                 return null;
           }
           @Override
           public long getItemId(int position) {
                 return 0;
           }
           @Override
           public View getView(int position, View convertView, ViewGroup parent) {
                 TextView tv = null;
                 tv = new TextView(MainActivity.this);
                 Log.i("listview", position+"get view");
                 tv.setText(strs[position]);
                 tv.setTextSize(40);
                 tv.setTextColor(Color.RED);
                 return tv;
           }
      }
}

运行结果:

 


注意到,每次滚动屏幕显示新的item,或将滚出屏幕上方的item重新显示出来都会调用getItem()方法。而上面的代码中每次都会新建一个TextView,这样做是存在问题的。

getView(int position, View convertView, ViewGroup parent),其中参数convertView在允许的情况下,会保存旧的View实例,以便拿来复用。所以,可以利用该参数来优化上面的getView()实现。

代码修改如下:

public View getView(int position, View convertView, ViewGroup parent) {
 
                 TextView tv = null;
                 Log.i("listview", position+"get view");
                 if(convertView == null){
                      tv = new TextView(MainActivity.this);
                 }
                 else{
                      tv = (TextView) convertView;
                 }
                 tv.setText(strs[position]);
                 tv.setTextSize(40);
                 tv.setTextColor(Color.RED);
                 return tv;
}


其实,若只是显示数组中的内容,直接使用ArrayAdapter会比较方便:

MainActivity.java修改如下:

private String[] strs;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
       // TODO Auto-generated method stub
       super.onCreate(savedInstanceState);
       setContentView(R.layout.main_layout);
       strs = new String[]{"aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii"};
       ListView lv = (ListView) findViewById(R.id.listview);
       ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, strs);
       lv.setAdapter(adapter);
      
  }


运行结果:

 


其中android.R.layout.simple_list_item_1是系统自带的一个布局id。

一般来说,在ListView中显示的东西可能更加复杂点,只靠一个TextView肯定解决不了,这时,就可以为ListView中的item根据需要编写一个布局文件。

如:item_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/hero"
        />
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="35sp"
        />
</LinearLayout>


用于在线性布局中水平显示一个图片和一个字符串。

修改MyAdapter内部类的getView():

public View getView(int position, View convertView, ViewGroup parent) {
 
         View view = null;
         Log.i("listview", position+"get view");
         if(convertView == null){
              view = LayoutInflater.from(MainActivity.this).inflate(R.layout.item_layout, null);
         }
         else{
              view =  convertView;
         }
         TextView tv = (TextView) view.findViewById(R.id.tv_name);
         tv.setText(strs[position]);
         return view;
   }


运行结果:

 

另一种可能常用的Adapter是SimpleAdapter。

构造函数:SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)

context指定上下文

data指定存放数据的list对象,list中存放的是Map对象。

resource指定item的布局文件id

from和to是有序的,两者中的元素必须一一对应,from中存放的是list中每个map对象中的key值,to存放的是显示对应key获取的值的控件的id。

如:item_layout.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <ImageView
        android:id="@+id/iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="35sp"
        />
</LinearLayout>

MainActivity.java中修改如下:

protected void onCreate(Bundle savedInstanceState) {
           // TODO Auto-generated method stub
           super.onCreate(savedInstanceState);
           setContentView(R.layout.main_layout);
           ListView lv = (ListView) findViewById(R.id.listview);
           List<Map<String,Object>> data = new ArrayList<Map<String,Object>>();
           Map<String,Object> map = new HashMap<String, Object>();
           map.put("img", R.drawable.hero);
           map.put("str", "aaa");
           data.add(map);
           map = new HashMap<String, Object>();
           map.put("img", R.drawable.hero);
           map.put("str", "bbb");
           data.add(map);
           map = new HashMap<String, Object>();
           map.put("img", R.drawable.hero);
           map.put("str", "ccc");
           data.add(map);
           map = new HashMap<String, Object>();
           map.put("img", R.drawable.hero);
           map.put("str", "ddd");
           data.add(map);
           map = new HashMap<String, Object>();
           map.put("img", R.drawable.hero);
           map.put("str", "eee");
           data.add(map);
          
           SimpleAdapter sa = new SimpleAdapter(this, data, R.layout.item_layout, new String[]{"img","str"}, new int[]{R.id.iv,R.id.tv_name});
           lv.setAdapter(sa);
}

显示结果:

 


关于ListView的事件监听:

AdapterView中定义了几个设置事件监听的方法,如:

void  setOnItemClickListener(AdapterView.OnItemClickListener listener)

void  setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener)

ListView间接继承了AdapterView,所以,要处理事件监听时,可以调用ListView中相关的方法。

注:如果调用了ListView的setOnClickListener()方法,会报如下错误:

java.lang.RuntimeException: Don't call setOnClickListener for an AdapterView. You probably want setOnItemClickListener instead

setOnItemClickListener()方法示例:

ListView lv = (ListView) findViewById(R.id.listview);
   lv.setOnItemClickListener(new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) {
              Log.i("listview","onClick");
              Log.i("listview",view.toString());
              Log.i("listview",position+"");
              Log.i("listview",id+"");
              TextView tv = (TextView) view.findViewById(R.id.tv_name);
              Toast.makeText(MainActivity.this, tv.getText(), Toast.LENGTH_LONG).show();
         }
   });


运行结果:

 


本文我们总结了三种Android应用向用户发送提示信息的方法,分别是1)发送Toast信息;2)弹出对话框;3)发送通知。

Android应用向用户发送提示信息的三种方法是:

1)发送Toast信息

2)弹出对话框

3)发送通知


方式1:发送Toast信息:

这种方式最简单,在之前的学习中多次使用过。Toast是在一个浮动于应用之上的View中显示信息,显示一定的时间间隔后自动消失,不可获得焦点。

最简单的用法就是之前的学习中一直使用的:通过一个静态的makeText()方法返回一个Toast对象,然后调用show()方法。

如:

布局文件添加一个Button:

<Button

        android:id="@+id/button1"

        android:onClick="showToast"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:layout_margin="20dp"

        android:text="@string/show_toast" />

MainActivity.java添加一个showToast()方法:

public void showToast(View view){

          Toast.makeText(this, "客观,您要的吐司来啦", Toast.LENGTH_LONG).show();

}

显示结果:

 


通常情况下,Toast.makeText()这个方法已然够用了,但是,如果有些别的需求,就需要看一下Toast的帮助信息了:

 


在Toast这个类中定义了两个常量:LENGTH_LONG和LENGTH_SHORT,用来表示Toast显示的时长。想让Toast停留的时间长一点,可以设置为LENGTH_LONG,否则设置为LENGTH_SHORT。

LENGTH_LONG的实际值为1,LENGTH_SHORT的实际值为0。在用到的地方也可以直接传入0或者1,但是阅读起来就显得不太直观了。

 


Toast中只定义了一个构造函数,传入一个上下文对象,返回一个空的Toast对象。

更常用的是,通过下面两个静态方法获取Toast对象:

 


这两个方法的区别在于第二个参数,该参数表示要显示的文本内容,第一个方法传入strings.xml中的一个字符串id,而第二个方法直接传入字符串对象,之前用到的都是第二个方法。下面测试下传入id的方法:

strings.xml中加入一个<string>节点:

<string name="tv_text">客官,这个是放在strings.xml的Toast,请慢用</string>

修改showToast()方法:

public void showToast(View view){

          Toast.makeText(this, R.string.tv_text, Toast.LENGTH_LONG).show();

}

运行结果:

 


如果直接使用Toast的构造方法获取一个Toast对象的话,该对象是一个空的Toast,这时,是不能直接show()的,毕竟没有任何要显示的内容。

如果:new Toast(this).show();

会报错:

 


提示,必须先调用setView():

 


当然,肯定有对应的getView():

 


这两个方法,分别用来给Toast设置要显示的View和取得当前Toast对象的View。

测试下setView()方法:

新建一个tv.xml布局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:gravity="center_horizontal"

    android:orientation="vertical" >

    <TextView

        android:id="@+id/tv"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/tv_text" />

 
    <Button

        android:id="@+id/btn1"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="Button" />

</LinearLayout>

修改showToast()方法:

public void showToast(View view){

          Toast toast = new Toast(this);

          View v = getLayoutInflater().from(this).inflate(R.layout.tv, null);

          toast.setView(v);

          toast.show();

}

显示效果:


 

直接把tv.xml中的内容都显示出来了,真丑……虽然显示有按钮,即便设置了事件监听,但是由于Toast是不能获取焦点的,所以点击是没有任何反应的。

在使用makeText()方法时,传入了三个参数,而构造方法只传入了与之对应的第一个表示上下文的参数,因而肯定有对应的setter方法:

 


setText()用于设置Toast中显示的信息,但是,注意到帮助文档中的信息:是用于修改之前使用makeText()创建的Toast对象显示的信息,如Toast对象是通过构造方法创建的是不能调用setText()方法的。

 


setDuration()用于设置显示的时长。

感觉还是挺鸡肋的,加起来都不如一个makeText()。

下面这个方法,可能看起来稍微有用些:

 



用来设置Toast的显示位置,如:

修改tv.xml设置个绿色背景,不要按钮了,太丑也没用:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:background="#00ff00"

    android:orientation="vertical" >

    <TextView

        android:id="@+id/tv"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="客官,您要的Toast来啦,请慢用" />

</LinearLayout>

修改showToast()方法:

public void showToast(View view){

          Toast toast = new Toast(this);

          toast.setView(getLayoutInflater().inflate(R.layout.tv, null));

          toast.setDuration(1);

          toast.setGravity(Gravity.CENTER, 0, 0);

          toast.show();

}

Gravity.CENTER表示的水平垂直均居中显示,后面的参数分别表示在第一个参数的基础上x轴方向上的偏移及y轴方向上的偏移,正数表示x轴上向右移动,y轴上向下移动;负数则表示x轴上向左移动,y轴上向上移动。这里指定为0,表示不偏移。

显示效果:

 

确实,居中显示了,而且是绿色背景,还是好丑,实际中应该不会这样用吧,费力不讨好。

总结:上面一大堆无用的东西,实际使用中直接使用makeText()方法获取Toast对象,需要修改显示位置,则调用setGravity()方法即可。实在需要自定义布局时再使用构造及setView()方法。

方式2:弹出对话框:

通过弹出对话框的形式向用户发送提示信息的方式,就需要用到AlertDialog这个类,准确来说,用的更多的是它的内部类AlertDialog.Builder类。

其实,弹出对话框的流程很简单:

1)获取一个AlertDialog.Builder对象:

AlertDialog.Builder builder = new Builder(this);

 

2)调用Builder的各种setXX方法,设置对话框的样式:

如:

builder.setTitle("俺是对话框");

builder.setMessage("没什么要说的");

3)显示对话框:

builder.show();

 

1.弹出一个简单对话框的示例:

修改布局文件,添加一个按钮,用于弹出对话框:

<Button

        android:id="@+id/button2"

        android:onClick="showDialog"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:layout_margin="20dp"

        android:text="弹出对话框" />

在MainActivity中添加一个button2回调的方法:

public void showDialog(View view){

          AlertDialog.Builder builder = new Builder(this);

          builder.setTitle("俺是对话框");

          builder.setMessage("没什么要说的");

          builder.setPositiveButton("确定", new OnClickListener() {

                

                 @Override

                 public void onClick(DialogInterface dialog, int which) {

                      Toast.makeText(MainActivity.this, "你点了确定,啥也没有", 1).show();

                 }

           });

          builder.setNegativeButton("取消", new OnClickListener() {

                

                 @Override

                 public void onClick(DialogInterface dialog, int which) {

                      Toast.makeText(MainActivity.this, "取消啦", 1).show();

                 }

           });

          builder.show();

}

运行结果:

 


但是,此时如果按下Back键,该对话框就自动关闭了,一般我们需要的是用户或者按下确定或者按下取消,这时就可以设置按下Back不进行任何操作:

builder.setCancelable(false);

 

2.弹出一个单选框,修改showDialog()方法:

builder.setTitle("明天做什么");

final String[] items = new String[]{"看书","看电影","散步"};

builder.setSingleChoiceItems(items, -1, new OnClickListener() {

            

             @Override

             public void onClick(DialogInterface dialog, int which) {

                   // TODO Auto-generated method stub

                   Toast.makeText(MainActivity.this, "你选了"+items[which], 1).show();

                   dialog.dismiss();

             }

});

第一个参数是一个数组类型的单选项

第二个参数表示默认选定项目的下标,-1表示默认一个都不选

第三个参数指定选定项目的事件监听器。

dialog.dismiss();表示关闭对话框

显示效果:

 


3.弹出一个复选框,修改showDialog()方法:

builder.setTitle("哪个电影好看点");

      final String[] items = new String[]{"道士下山","大圣归来","战狼2","捉妖记","栀子花开"};

      builder.setMultiChoiceItems(items, new boolean[]{false,false,false,false,false}, new OnMultiChoiceClickListener() {

            

             @Override

             public void onClick(DialogInterface dialog, int which, boolean isChecked) {

                   // TODO Auto-generated method stub

                   Toast.makeText(MainActivity.this, "你觉得"+items[which]+(isChecked?"好看":"不好看"), 0).show();

             }

});

运行结果:

 


4.显示一个进度对话框:

ProgressDialog pd = new ProgressDialog(this);

pd.setTitle("正在缓冲……");

pd.setMessage("这网好烂啊……");

pd.show();

运行结果:

 


调用: pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); 可以将进度对话框改为水平进度条的样式:


final ProgressDialog pd = new ProgressDialog(this);

      pd.setTitle("正在缓冲……");

      pd.setMessage("这网好烂啊……");

      pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

      pd.setMax(100);

      pd.show();

      new Thread(){

            public void run(){

                 try {

                        for (int i = 0; i < 100; i++) {

                              Thread.sleep(1000);

                              pd.setProgress(i);

                        }

                        pd.dismiss();

                   } catch (Exception e) {

                        e.printStackTrace();

                   }

            }

      }.start();

运行结果:

 


注意:此时点击屏幕对话框之外的部分,发现对话框消失了,解决方法:调用pd.setCancelable(false)。

方式3:发送通知:

当应用程序不在前台时,或者只有后台服务在运行,也许通过通知的方式向用户发送提示信息更方便一些:

要使用通知,涉及到的类有:NotificationManager和Notification这两个类。

见名知意,前一个用于管理通知,后一个就是被管理的通知实体类。

首先看管理器类:

NotificationManager要管理通知,所以有几个比较常用的方法:

首先要获取NotificationManager对象:

但是,查看帮助手册却没有发现它的构造方法……

文档中这样描述的:

 


所以,应当通过getSystemService(String)方法获取NotificationManager对象。

这个方法时Context中的方法,查看帮助手册发现:

 


Context中定义了好几个String类型的常量,其中就有NOTIFICATION_SERVICE,后面的描述信息更是直接说明通过将这个字符串常量传给getSystemService()可以获取NotificationManager实例。

 


但是getSystemService()方法返回的是Object对象,故而,需要强制类型转换:

NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

 


notify()方法用于发出一个通知,cancel()方法由于取消通知。

有了通知管理器,要让它发送通知,还是需要有通知实体的:

 


构造方法参数说明:

第一个参数用来指定通知在状态栏的图标

第二个参数用来指定通知在状态栏的描述

第三个参数用来指定什么时候显示

如:

Notification notification = new Notification(R.drawable.ic_launcher, "这是一个通知", System.currentTimeMillis());

 


Notification中的方法就这几个,最有用的只有setLatestEventInfo()方法

 


用于给通知在任务栏中的显示设置一个标准的布局

参数说明:

第一个参数指定上下文

第二个参数指定在任务栏中显示的标题

第三个参数指定在任务栏中显示的内容

第四个参数是PendingIntent类型的,是一种特殊的意图,用于指定通知被点击之后的动作,若设置为空则表示不进行任何处理。

一个简单的通知示例:

在布局文件中添加第三个按钮,用于发送通知:

<Button

        android:id="@+id/button3"

        android:onClick="showNotification"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"

        android:layout_margin="20dp"

        android:text="弹出通知" />

在MainActivity中添加一个showNotification()方法,用于按钮的回调:

public void showNotification(View view){

          NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

          Notification notification = new Notification(R.drawable.ic_launcher, "这是一个通知", System.currentTimeMillis());

          notification.setLatestEventInfo(this, "这是个通知", "没有通知内容", null);

          manager.notify(123, notification);

}

运行效果:

 


查看任务栏:

 


但是,点击这个通知是没有任何反应的。

因为notification.setLatestEventInfo(this, "这是个通知", "没有通知内容", null);第四个参数设置为null了。

下面稍微了解下关于第四个参数的内容:

PendingIntent:延迟意图,是一种特殊的意图,这种意图并不是在发出之后立即得到执行,而是交由其他应用在恰当的时候进行执行处理。

PendingIntent实例的获得可以通过以下几个静态方法:

getActivity(Context, int, Intent, int)返回一个PendingIntent实例用于启动一个新的Activity。

getBroadcast(Context, int, Intent, int) 返回一个PendingIntent实例用于发出广播

getService(Context, int, Intent, int) 返回一个PendingIntent实例用于启动一个新的Service。

它们均需要传入一个上下文对象,及一个普通的Intent对象。

下面获取一个PendingIntent对象,用来打开一个新的Activity,然后将其传入notification.setLatestEventInfo()方法:

新建一个Actvity:

public class NotificationActivity extends Activity {

 

      @Override

      protected void onCreate(Bundle savedInstanceState) {

           super.onCreate(savedInstanceState);

           setContentView(R.layout.tv);

      }

}

布局文件:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:background="#00ff00"

    android:orientation="vertical" >

    <TextView

        android:id="@+id/tv"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="客官,您要的通知来啦,请细阅" />

</LinearLayout>


修改showNotification()方法:

public void showNotification(View view){

          NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

          Notification notification = new Notification(R.drawable.ic_launcher, "这是一个通知", System.currentTimeMillis());

          Intent intent = new Intent(this, NotificationActivity.class);

          PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

          notification.setLatestEventInfo(this, "这是个通知", "没有通知内容", pi);

          manager.notify(123, notification);

}

运行结果:

点击通知:

 


Notification中更多的是设置通知的各个属性,来更改通知的类型:

 


这些字段都是共有的,可以直接赋值:

如:notificaion.icon设置通知的图标

notification.sound设置通知的铃音

notification.vibrate设置通知的震动模式,需要赋值为一个long的数组

如notification.vibrate = new long[]{0,1000,500,1000,500};下标为偶数的表示静止时间,下标为奇数的表示震动时间,单位为毫秒

ledARGB、ledOffMS、ledOnMS可以用来通知到来时led灯的显示方式

这些都可以自己测试使用下,这里不一一列举了。

此外,新版本中可以采用与对话框类似的Builder内部类方式,创建Notification对象那个,并设置其属性,但出于兼容性的考虑,这里没有列举:

简单示例如下:

Notification notification = new Notification.Builder(this)

          .setSmallIcon(R.drawable.ic_launcher).setContentTitle("这是个通知")

          .setContentText("没有通知内容").getNotification();

          manager.notify(123, notification);

以上,就是三种向用户发送提示信息的三种方式。

Intent用于启动Activity, Service, 以及BroadcastReceiver三种组件, 同时还是组件之间通信的重要媒介;IntentFilter类表示Intent过滤器, 大部分情况下, 每一个component都会定义一个或多个IntentFilter, 用于表明其可处理的Intent。

使用Intent启动组件的优势

1, Intent为组件的启动提供了一致的编程模型. 无论想要启动的组件是Activity, Service, 还是BroadcastReceiver, 都可以使用Intent封装启动的意图.
2, 在某些时候, 应用程序只是想启动具有某种特征的组件, 并不想和某个特定的组件耦合. 使用Intent可以方便的达到这种高层次解耦的目的.


IntentFilter类

IntentFilter类表示Intent过滤器, 大部分情况下, 每一个component都会定义一个或多个IntentFilter, 用于表明其可处理的Intent.
一般来说, component的IntentFilter应该在AndroidManifest.xml文件中定义.

关于Bundle:

注意到Activity的onCreate()方法的签名是protected void onCreate(Bundle savedInstanceState),其参数是一个Bundle实例。

Bundle以键值对的形式来存储数据,类似于Map,以便在Activity之间传递数据、状态信息。Bundle的键均为String类型,值可以是各种基本类型及可序列化的对象类型。

Bundle的常用方法:

构造方法:

Bundle()、Bundle(Bundle b)等

get系列根据键获取对应值的方法:

Object  get(String key)

boolean  getBoolean(String key)

boolean  getBoolean(String key, boolean defaultValue)

Bundle  getBundle(String key)

char  getChar(String key)

char  getChar(String key, char defaultValue)

int  getInt(String key)

int  getInt(String key, int defaultValue)

Serializable  getSerializable(String key)

等等

注:根据键到Bundle实例中查找对应值,若存在,则返回对应的值。不存在时,带有默认值的方法,返回传入的默认值;不带默认值的方法返回响应的零值,如false、null、0.0f、0等。

put系列存放键值对的方法:

void  putAll(Bundle map)

void  putBoolean(String key, boolean value)

void  putBundle(String key, Bundle value)

void  putChar(String key, char value)

void  putDouble(String key, double value)

void  putSerializable(String key, Serializable value)

等等

其他方法:

boolean  isEmpty()  判断Bundle实例是否为空。

Set<String>  keySet()  返回当前Bundle实例中key的集合。

int  size()  返回Bundle实例键值对的数目。

 

关于Intent:

注意到,在前面的程序中,从一个Activity启动另一个Activity,总是这样做:

ntent intent = new Intent(FirstActivity.this,  SecondActivity.class);
startActivity(intent);

可知,Intent可以用来表示一种启动Activity的意图,用来启动另一个Activity。

其实,Intent是一种消息传递机制,可以在应用程序中使用,也可以在应用程序之间使用。不仅可以用来启动Activity还可以用来启动Service和BroadcastReceiver。而且,还可以利用Intent与被启动的组件进行数据传输,信息交换。

Intent的构造方法:

Intent()

Intent(Intent o)

Intent(String action)

Intent(String action, Uri uri)

Intent(Context packageContext, Class<?> cls)

Intent(String action, Uri uri, Context packageContext, Class<?> cls)

Intent中的主要属性:

  private String mAction;

  private HashSet<String> mCategories;

  private Uri mData;

  private String mType;

  private ComponentName mComponent;

  private Bundle mExtras;

注意到,源码中属性的命名方式都是以m打头,表示这是一个member。

1)Component属性:指明了该Intent要启动的组件。该属性是一个ComponentName类型的对象,其构造方法有:

public ComponentName(String pkg, String cls):

pkg应为Manifest.xml中声明的Manifest节点的package属性声明的包名

cls应为要启动组件的完整类名:包名.类名的形式。

public ComponentName(Context pkg, String cls)

pkg为要启动的上下文环境,可以传入当前Activity实例的引用,如this。若是在内部类中,则应为MainActivity.this(注:MainActivity为当前Activity类名)

cls为要启动组件的完整类名:包名.类名的形式。

public ComponentName(Context pkg, Class<?> cls)

pkg为要启动的上下文环境,可以传入当前Activity实例的引用,如this。若是在内部类中,则应为MainActivity.this(注:MainActivity为当前Activity类名)

cls为要启动组件,如SecondActivity.class(注:SecondActivity为要启动的组件名)

总结:这三个构造方法都是要指定启动的上下文环境和一个要启动组件的完整类限定名,即传入包名.类名。由于在Android应用中包名是作为应用的唯一标识,所以包名与代表应用上下文环境的Context是一一对应的。

如:

第一个构造代码:

Intent intent1 = new Intent();

intent1.setComponent(new ComponentName("cn.csc.lifecycle", "cn.csc.lifecycle.NormalActivity"));

startActivity(intent1);

 

第二个构造代码:

 Intent intent1 = new Intent();

intent1.setComponent(new ComponentName(this, "cn.csc.lifecycle.NormalActivity"));

startActivity(intent1);

 

第三个构造代码:

Intent intent1 = new Intent();

intent1.setComponent(new ComponentName(this, NormalActivity.class));

startActivity(intent1);

 

Intent提供了一种简化的设置Component属性的方法,即:

Intent(Context packageContext, Class<?> cls)

这也是我们之前一直用的一种方式。

startActivity(new Intent(this, NormalActivity.class));

 

注意:设置了Component属性的Intent因为已然明确指定要启动的组件,故而被称之为显式Intent。当然,有显式Intent,肯定就有隐式Intent。

隐式Intent不指定Component属性,而是通过设置其他属性,来制定所要启动的组件应该具备的条件,然后,符合Intent其他属性所指定的条件的组件就会被启动。这时,运行时会使用一个称为“Intent解析”的过程来动态选择符合条件的组件。在Intent中设置要启动组件所需要具备的条件,就要用到Intent的mAction和mCategories等属性。

2)Action和Category属性:

源代码中属性的声明如下:

private String mAction;

private HashSet<String> mCategories;

可知,Action属性是一个普通的字符串,只能有一个,Category属性也是字符串,可以有多个,共同存放在一个名为mCategories的HashSet中。

其中,Action表示该Intent所要启动的组件应该能完成的抽象动作,比如Intent.ACTION_VIEW,可以完成查看动作,Intent.ACTION_DIAL可以完成拨号的动作等等。Category属性则用于为Action增加额外的附加类别信息。

如何让自己的组件具备Intent所要求的条件呢?

这里就需要在Manifest.xml中,注册应用所需组件,如Activity时,给Activity节点添加上<action>和<category>子节点,设置这两个节点的name属性,使其值与Intent中Action属性和Category属性一一对应即可。

如:

Intent intent1 = new Intent();

intent1.setAction("myaction");

startActivity(intent1);

 

要使此时自己定义的NormalActivity能被启动,则应在Manifest.xml中这样配置:

<activity android:name=".NormalActivity">
            <intent-filter >
                <action android:name="myaction"/>
            </intent-filter>
</activity>


运行程序,发现如下错误:

 


查看LogCat错误信息:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act = myaction}

找不到能够接收处理action为”myaction”的Intent意图。

但是,我们明明在<intent-filter>中声明了的。

其实,这里只要修改一下<intent-filter>即可:

<intent-filter >
                <action android:name="myaction"/>
                <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>


这样,就能正常启动NormalActivity。

原因在于:当我们不明确设定Intent的Category属性时,其值默认为Intent.CATEGORY_DEFAULT常量,该常量值为"android.intent.category.DEFAULT"。

注意:Intent最多只能设置一个Action以及0~n个Category(注:0个其实,也是一个Intent.CATEGORY_DEFAULT常量);但是在Manifest.xml中为组件注册时,在<intent-filter>中可以指定多个<action>,多个<category>。只要该组件所注册的<action>和<category>能涵盖在Intent中设置的所有Action和Category即可被启动。

如上例中,修改<intetn-filter>如下:

<intent-filter >
                <action android:name="myaction"/>
                <action android:name="myaction1"/>
                <category android:name="mycategory"/>
                <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>


仍能被上面的Intent启动。

如,修改Intent如下:

Intent intent1 = new Intent();

intent1.setAction("myaction");

intent1.addCategory("mycategory");

startActivity(intent1);

仍然符合条件。

注意,设置Action的方式是setAction(),而由于Category可以有多个,设置的方式不是setCategory()而是addCategory()。

Intent也提供了对应的构造方法来简化Action的设置:

Intent(String action)

Intent(String action, Uri uri)

其中第二个构造函数的第二个参数为Uri对象,这就涉及到Intent的Data属性。

3)Data属性:

Data属性用于向Action属性所表示的动作提供所要操作的数据,如Action为Intent.ACTION_CALL表示要拨号,它对应的Data就应当为要拨打的号码。

Data属性接受一个Uri对象,通常以字符串”scheme://host:port/path”的形式表示:

如:”http://www.baidu.com” ,”tel:110”等。

如,修改Intent代码:

Intent intent1 = new Intent();

intent1.setAction(Intent.ACTION_CALL);

intent1.setData(Uri.parse("tel:110"));

startActivity(intent1);

将启动模拟器的拨号器,并拨打110

当然,在运行程序之间需要在Manifest.xml中Manifest节点下,添加<uses-permisson>子节点,配置该程序所需要的拨打电话的权限:

<uses-permission  android:name="android.permission.CALL_PHONE"/>



此时,如果修该NormalActivity的<intent-filter>

添加如下Action: <action android:name="android.intent.action.CALL"/>,想要让它也能被Intent启动,发现还是只启动拨号器。

这是因为,Intent设置了Data属性,要想让NormalActivity能被该Intent启动,也要在<intent-filter>中设置<data>子节点:<data android:scheme="tel"/>

此时,由于有多个组件具备Intent启动所要求的条件,故而出现选择列表,让用户来选择要启动的组件。

运行效果:

 


注意到,上面<data>子节点只设置了scheme属性,则所有scheme属性为tel的Data属性都能符合条件。

若修改Intent:

Intent intent1 = new Intent();

intent1.setAction(Intent.ACTION_VIEW);

intent1.setData(Uri.parse("http://www.baidu.com"));

startActivity(intent1);

此时,会启动浏览器,浏览http://www.baidu.com

 


修改NormalActivity的<intent-filter>添加

<action android:name="android.intent.action.VIEW"/>

 

修改<data>子节点: <data android:scheme="http"/>

重新运行:

 


若在<data>节点中设置android:host=“www.baidu.com”此时,也是可以启动NormalActivity的。但是,若Intent的Data属性换成”http://www.taobao.com”此时就不能启动NormalActivity了,因为它的<data>不符合条件。

只有<data>的属性与Data一致时,才能被Intent所启动。一般只指定android:scheme属性即可,不会给出太具体的属性。

注意:<intent-filter>节点中可以有0~N个<action>、0~N个<category>、0或1个<data>。

4)Type属性:用于指定该Data所指定的Uri对应的MIME类型,可以是任何自定义的MIME类型,只要符合abc/xyz格式的字符串就行。如:text/html等。

Data属性与Type属性会相互覆盖:谁最后被设置,谁就起作用,之前被设置的就被覆盖。若Data被覆盖,则getData()返回null,若Type被覆盖,getType()返回null。

Intent intent1 = new Intent();

intent1.setAction(Intent.ACTION_VIEW);

intent1.setData(Uri.parse("http://www.baidu.com"));

intent1.setType("text/html");

此时,调用intent1.getData()返回值为null。data节点若设置如下:

 <data android:mimeType="text/html"/> 则能被intent1启动。但是若:

 <data android:scheme="http" android:mimeType="text/html"/> ,这时就不能启动了,因为比Type值多出来个scheme属性。

若是希望同时设置Data和Type属性,则必须要使用setDataAndType()方法,如:

intent1.setDataAndType(Uri.parse("http://www.baidu.com"), "text/html");

 

此时,浏览器一定会被启动,若要NormalActivity也能被启动,其<data>节点,一定要同时设置android:scheme和android:mimeType属性:

<data  android:scheme="http" android:mimeType="text/html"/>

 5)Extras属性:

注意到private Bundle mExtras; mExtras的类型是Bundle。所以Intent可以借助其Bundle类型的mExtras属性在Activity之间进行数据传递。

Bundle  getExtras()  获取mExtras属性。

Intent  putExtras(Bundle extras)  设置mExtras属性。

可以调用Bundle的相关方法设置好要保存的键值对,然后再把Bundle实例传给putExtras()方法。Intent提供了更简单的键值对操作方式:

putExtra(String name, XXX  value) :向Intent中存入name-value的键值对,实际上是存入mExtras中。XXX表示不同的数据类型,如同Bundle中的putInt(),putDouble()等putXXX()。

getXxxExtra(String name):从Intent中按照key获取对应的值。实际上是到mExtras这个Bundle实例中去获取值。如同Bundle中的getInt(),getDouble()等getXxx()。

利用Intent在Activity之间传递数据:

前面曾经提到过Activity中有个名为getIntent()的方法,可以用来获取启动该Activity的Intent实例。

如,在MainActivity中获取启动它的Intent实例,并调用其toString()方法,将其设置为第一个按钮的text属性:

protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main_layout);

        Log.i("LIFECYCLE","onCreate");

        Button btnNormal = (Button) findViewById(R.id.normal);

        Button btnDialog = (Button) findViewById(R.id.dialog);

        btnNormal.setOnClickListener(this);

        btnDialog.setOnClickListener(this);

        btnNormal.setText(getIntent().toString());

}


从MainActivity向NormalActivity传递一个名为”data”,值为” data given by MainActivity”的数据:

Intent intent1 = new Intent(this, NormalActivity.class);
intent1.putExtra("data", "data given by MainActivity");
startActivity(intent1);

 
在NormalActivity中获取这个值,并将其设置为TextView的text属性:

protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.first_layout);
           TextView tv = (TextView) findViewById(R.id.tv);
           tv.setText(getIntent().getStringExtra("data"));
}



如果想要让被启动的Activity返回数据给启动它的那个Activity要怎么做呢?

startActivity()启动别的Activity之后就完全不再理它了,此时,就需要另外一个方法:

startActivityForResult(Intent intent, int requestCode):该方法用于根据intent参数启动一个Activity,并期望在被启动的Activity结束时,获得一个返回结果。

调用startActivityForResult()之后,被启动的Activity结束后返回前一个Activity时,会回调它的onActivityResult(int requestCode, int resultCode, Intent intent),其中,requestCode代表请求码,resultCode代表被启动Activity设置的返回结果码,intent里面包含了返回的数据。

被启动的Activity要给启动它的Activity返回数据时,需要调用setResult()方法设置要返回的数据。

如:MainActivity中使用startActivityForResult()启动NormalActivity,并重写onActivityResult()方法,接收NormalActivity传回的数据。

Intent intent1 = new Intent(this, NormalActivity.class);

intent1.putExtra("data", "data given by MainActivity");

startActivityForResult(intent1,0);

protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
           // TODO Auto-generated method stub
           super.onActivityResult(requestCode, resultCode, intent);
           if(intent == null){
                 Log.i("tag","null");
           }else{
                 Log.i("tag",intent.getStringExtra("res"));
           }
}

NormalActivity重写返回按钮按下的回调函数onBackPressed()向MainActivity返回数据:

public void onBackPressed() {
           Log.i("tag","back pressed");
           setResult(Activity.RESULT_OK,getIntent().putExtra("res", "resultxxx"));
           super.onBackPressed();
}


此时,要注意,如果super.onBackPressed()放在该方法的第一句,无论后面设置什么值都不会被成功传递回去。

查看Activity的onBackPressed()方法的源代码,即可发现原因:

public void onBackPressed() {
        if (!mFragments.popBackStackImmediate()) {
            finish();
        }
}


这个方法会直接调用finish()直接结束该Activity实例。

public void finish() {
        if (mParent == null) {
            int resultCode;
            Intent resultData;
            synchronized (this) {
                resultCode = mResultCode;
                resultData = mResultData;
            }
            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
            try {
                if (resultData != null) {
                    resultData.prepareToLeaveProcess();
                }
                if (ActivityManagerNative.getDefault()
                    .finishActivity(mToken, resultCode, resultData)) {
                    mFinished = true;
                }
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishFromChild(this);
        }
}


阅读finish()的源码可以发现:若发现当前Activity需要返回数据,则直接传递null回去,然后结束掉当前Activity实例。

所以,无论我们在super.onBackPressed()后设置任何返回数据都不会被真正传回。

当然,也可以不调用super.onBackPressed(),在设置完成要返回的数据之后,自己调用一个finish()方法即可。



关于Intent的两点补充:

1.隐式Intent启动组件,会有一个Intent解析的过程,若找不到能够处理该Intent的组件,程序就会异常终止。一个合理的做法是,在使用Intent实例启动组件如:startActivity(intent)之前,最好能判断一下该调用能否解析为一个Activity。为了实现该功能,Intent提供了一个方法:

ComponentName  resolveActivity(PackageManager pm)  :该方法接收一个包管理器对象作为参数,通过查找该包管理器,返回能够处理该Intent的Activity的Component对象,没有找到能处理该Inent的组件时则返回null。

Intent intent1 = new Intent();
intent1.setAction(Intent.ACTION_VIEW);
intent1.setType("text/html");
Log.i("tag",intent1.toString());
ComponentName resolveActivity = intent1.resolveActivity(getPackageManager());
Log.i("tag",resolveActivity.getClassName());
Log.i("tag",resolveActivity.getPackageName());
Log.i("tag",resolveActivity.toString());
startActivity(intent1);



NormalActivity中配置<intent-filter>

<activity android:name=".NormalActivity">
            <intent-filter >
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:mimeType="text/html"/>
            </intent-filter>
</activity>


运行信息:

 


可见解析到cn.csc.lifecycle.NormalActivity能够处理该Intent。

若找不到能够处理该Intent的组件时,会返回null,所以,安全的启动Activity的做法是:

if(intent1.resolveActivity(getPackageManager())!=null){
                      startActivity(intent1);
}


当然,可以加上找不到能处理intent1的组件的提示信息等。

注意,若用多个组件都能处理该Intent实例时,如:

Intent intent1 = new Intent();
intent1.setAction(Intent.ACTION_VIEW);
intent1.setData(Uri.parse("http://www.baidu.com"));
Log.i("tag",intent1.toString());
ComponentName resolveActivity = intent1.resolveActivity(getPackageManager());
Log.i("tag",resolveActivity.toString());
startActivity(intent1);
<activity android:name=".NormalActivity">
            <intent-filter >
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="http"/>
            </intent-filter>
</activity>


此时,浏览器和NormalActivity都能处理该Intent实例。

运行效果:

 


返回的Component信息为:

 


这个ResolverActivity其实就是一个选择打开程序的对话框,当有多个组件能处理Intent实例,但是没有设置默认的处理程序时,就会交由ResolverActivty来处理,而ResolverActivity的做法就是把所有能处理的都显示在一个列表中,由用户决定采用哪个来处理。当我们勾选了下面的use by default for this action时,之后选择的组件将会作为该Intent的默认处理程序,该设置会保存在手机或者模拟器中,下次在出现该Intent时,直接使用设置的默认组件去处理,而不再转到ResolverActivity。

如,这里我选择了Browser作为默认处理程序,下次再启动改程序,输出的运行信息:
 
 

2.关于使用Intent在Activity之间传递数据,在之前都是传递些内置类型的数据,若想传递自定义类型的数据,应该怎么办呢?

注意到在Intent中有下面两对方法:

Serializable  getSerializableExtra(String name)

Intent  putExtra(String name, Serializable value)

放入/获取Serializable类型的值。

<T extends Parcelable> T  getParcelableExtra(String name)

Intent  putExtra(String name, Parcelable value)

放入/获取Parcelable类型的值。

Serializable和Parcelable都是接口,即要想使用Intent传递自定义类型的数据,则自定义类型的数据应当事先Serializable接口或者Parcelable接口。

Serializable接口:

java.io.Serializable是一个空的起到标记作用的接口。标记实现该接口,只需要在类定义时声明实现该接口即可。实现该接口的类表明能够被序列化和反序列化,对大多数的类来说已然足够了,当然,若想更细粒度的操纵序列化过程,可以实现下面这两个方法:

private void writeObject(java.io.ObjectOutputStream out) throws IOException

private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

一般情况下,都用不到的。

这里简单声明实现该接口,如:

import java.io.Serializable;
 
public class MyData implements Serializable {
      private String stringData;
      private int intData;
public MyData(String stringData, int intData) {
           super();
           this.stringData = stringData;
           this.intData = intData;
      }
      public String getStringData() {
           return stringData;
      }
      public void setStringData(String stringData) {
           this.stringData = stringData;
      }
      public int getIntData() {
           return intData;
      }
      public void setIntData(int intData) {
           this.intData = intData;
      }
}


之后,便可以传递MyData类型数据了:

MyData  data1 = new MyData(“aaa”,111);

Intent intent1 = new Intent();

intent1.putExtra(“mydata”, data1);

 

取得该数据:

MyData  data1 = (MyData)getIntent.getSerializableExtra(“data1”);

 

Parcelable接口:相比Serializable要复杂一点,需要实现里面的抽象方法。

android.os.Parcelable接口,帮助文档中,有一个典型的实现例子如下,可以用作参考,自己实现该接口时,模仿即可:

public class MyParcelable implements Parcelable {    
      private int mData;    
      public int describeContents() {        
           return 0;    
           }    
      public void writeToParcel(Parcel out, int flags) {
           out.writeInt(mData);    
           }    
      public static final Parcelable.Creator<MyParcelable> CREATOR            
      = new Parcelable.Creator<MyParcelable>() {        
           public MyParcelable createFromParcel(Parcel in) {
                 return new MyParcelable(in);         
                 }        
           public MyParcelable[] newArray(int size) {            
                 return new MyParcelable[size];        
                 }    
           };         
      private MyParcelable(Parcel in) {      
           mData = in.readInt();    
      }
}


定义各种内置类型的数据,然后在writeToParcel分别根据不同的类型调用对应类型的

out.writeXXX(mData);在构造方法中,分别调用in.readXX()即可。

然后就可以调用Intent  putExtra(String name, Parcelable value)去放入该自定义类型数据,调用<T extends Parcelable> T  getParcelableExtra(String name)获取该自定义类型数据。

在Android应用程序开发中,不可避免的会出现异常,我们应该如何快速的在开发阶段捕获异常进行处理呢?在Java线程类中,可以在线程中捕捉未处理异常,本文我们详细介绍。

APP开发出现异常在所难名,甚至会导致应用程序崩溃。如果在debug模式下开发的时候,是可以通过查看logcat日志来查看异常消息,从而进行处理。但是,如果我们在发布版本之后,用户在使用的时候crash掉了,就无法查看异常信息,也就很难找出bug来解决问题。

还好在java线程类中,有一个针对上述问题的解决办法:在线程中捕捉未处理的异常。因为crash时,抛出的异常就是因为没有在app中catch处理,就会抛给系统,如果我们在这个时候对这个能够对这个异常进行处理,就最好不过了,这样就能打印异常信息,就能发布给服务器,供开发人员查看。

一,写一个CrashHanler类

package com.raise.wind.utils;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
 * Created by yu on 2015/7/15.
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    public static CrashHandler instance;
    private CrashHandler() {
    }
    public static CrashHandler get_instance() {
        if (instance == null)
            new CrashHandler();
        return instance;
    }
    public void init() {
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        saveFile(ex.getMessage(), "crash.txt");
        //退出程序
        //这里由于是我们自己处理的异常,必须手动退出程序,不然系统出一只处于crash等待状态
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(1);
    }
    public static void saveFile(String data, String file_name) {
        File sdPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                + File.separator + "aacrash" + File.separator + "cache");
        if (!sdPath.exists()) {
            sdPath.mkdirs();
        }
        File file = new File(sdPath, file_name);
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            fos.write(data.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fos != null)
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}


二,在Application中声明

记得在Androidanifest.xml中配置

package com.raise.wind.app;
import android.app.Application;
import com.raise.wind.utils.CrashHandler;
/**
 * Created by yu on 2015/7/15.
 */
public class APP extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler.get_instance().init();
    }
}


三,测试

这样就声明了我们线程中出现的为捕捉异常交给CrashHandler类来处理。
现在我们写一个空指针异常来测试:

package com.raise.wind.crashproject;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
    TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView.setText("text");
    }
}


打开app,发现程序立即crash,打开文件管理能找到在程序中保存的文件,里面有异常消息

Unable to start activity ComponentInfo{com.raise.wind.crashproject/com.raise.wind.crashproject.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

当然,这只是异常消息的开头的提示,如果要想全部打印出来,使用下面代码:

Could not execute method of the activity
android.view.View$1.onClick(View.java:4010)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
Caused by null
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
android.view.View$1.onClick(View.java:4005)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)
Caused by Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
com.raise.wind.crashproject.MainActivity.click_1(MainActivity.java:21)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
android.view.View$1.onClick(View.java:4005)
android.view.View.performClick(View.java:4759)
android.view.View$PerformClick.run(View.java:19770)
android.os.Handler.handleCallback(Handler.java:739)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:135)
android.app.ActivityThread.main(ActivityThread.java:5235)
java.lang.reflect.Method.invoke(Native Method)
java.lang.reflect.Method.invoke(Method.java:372)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:906)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:701)


这个类一般结合log4j来记录日志,并且在发生crash时,将log文件发送到服务器。
这样程序就可以查看用户手机端的crash消息了,方便我们处理在debug模式开发时未发现的异常。


Android APP级异常捕获实现方式

描述:App级异常捕获,并记录下CrashLog到文件。

以下,代码。

在Application的,onCreate中,初始化自定义的CrashHandler

import android.app.Application;
import com.tjd.appexceptioncatch.exception.CrashHandler;
public class MyApplication extends Application {
    private static MyApplication instance;
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler.getInstance().init(getApplicationContext());
    }
    public static MyApplication getInstance() {
        if (instance == null) {
            instance = new MyApplication();
        }
        return instance;
    }
}


自定义CrashHandler如下

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import com.tjd.appexceptioncatch.application.MyApplication;
/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 * 需要在Application中注册,为了要在程序启动器就监控整个程序。
 */
public class CrashHandler implements UncaughtExceptionHandler {
    public static final String TAG = "CrashHandler";
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static CrashHandler instance;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<String, String>();
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    /** 保证只有一个CrashHandler实例 */
    private CrashHandler() {
    }
    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHandler getInstance() {
        if (instance == null)
            instance = new CrashHandler();
        return instance;
    }
    /**
     * 初始化
     */
    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器    
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序    
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        //保存日志文件     
        saveCatchInfo2File(ex);
        return true;
    }
    /**
     * 收集设备参数信息
     * @param ctx
     */
    public void collectDeviceInfo(Context ctx) {
        try {
            PackageManager pm = ctx.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null" : pi.versionName;
                String versionCode = pi.versionCode + "";
                infos.put("versionName", versionName);
                infos.put("versionCode", versionCode);
            }
        } catch (NameNotFoundException e) {
            Log.e(TAG, "an error occured when collect package info", e);
        }
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                infos.put(field.getName(), field.get(null).toString());
                Log.d(TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }
    private String getFilePath() {
        String file_dir = "";
        // SD卡是否存在
        boolean isSDCardExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
        // Environment.getExternalStorageDirectory()相当于File file=new File("/sdcard")
        boolean isRootDirExist = Environment.getExternalStorageDirectory().exists();
        if (isSDCardExist && isRootDirExist) {
            file_dir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/crashlog/";
        } else {
            // MyApplication.getInstance().getFilesDir()返回的路劲为/data/data/PACKAGE_NAME/files,其中的包就是我们建立的主Activity所在的包
            file_dir = MyApplication.getInstance().getFilesDir().getAbsolutePath() + "/crashlog/";
        }
        return file_dir;
    }
    /**
     * 保存错误信息到文件中
     * @param ex
     * @return 返回文件名称,便于将文件传送到服务器
     */
    private String saveCatchInfo2File(Throwable ex) {
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, String> entry : infos.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            sb.append(key + "=" + value + "\n");
        }
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        ex.printStackTrace(printWriter);
        Throwable cause = ex.getCause();
        while (cause != null) {
            cause.printStackTrace(printWriter);
            cause = cause.getCause();
        }
        printWriter.close();
        String result = writer.toString();
        sb.append(result);
        try {
            long timestamp = System.currentTimeMillis();
            String time = formatter.format(new Date());
            String fileName = "crash-" + time + "-" + timestamp + ".log";
            String file_dir = getFilePath();
            //            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            File dir = new File(file_dir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            File file = new File(file_dir + fileName);
            if (!file.exists()) {
                file.createNewFile();
            }
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(sb.toString().getBytes());
            //发送给开发人员
            sendCrashLog2PM(file_dir + fileName);
            fos.close();
            //            }
            return fileName;
        } catch (Exception e) {
            Log.e(TAG, "an error occured while writing file...", e);
        }
        return null;
    }
    /**
     * 将捕获的导致崩溃的错误信息发送给开发人员
     * 目前只将log日志保存在sdcard 和输出到LogCat中,并未发送给后台。
     */
    private void sendCrashLog2PM(String fileName) {
        //        if (!new File(fileName).exists()) {
        //            Toast.makeText(mContext, "日志文件不存在!", Toast.LENGTH_SHORT).show();
        //            return;
        //        }
        //        FileInputStream fis = null;
        //        BufferedReader reader = null;
        //        String s = null;
        //        try {
        //            fis = new FileInputStream(fileName);
        //            reader = new BufferedReader(new InputStreamReader(fis, "GBK"));
        //            while (true) {
        //                s = reader.readLine();
        //                if (s == null)
        //                    break;
        //                //由于目前尚未确定以何种方式发送,所以先打出log日志。
        //                Log.i("info", s.toString());
        //            }
        //        } catch (FileNotFoundException e) {
        //            e.printStackTrace();
        //        } catch (IOException e) {
        //            e.printStackTrace();
        //        } finally { // 关闭流
        //            try {
        //                reader.close();
        //                fis.close();
        //            } catch (IOException e) {
        //                e.printStackTrace();
        //            }
        //        }
    }
}


在MainActivity中触发异常

import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
    final String str = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        str.equals("exception");
    }
}


Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。

1、PC端安装Fiddler

下载地址:Fiddler.exe,http://www.telerik.com/download/fiddler


2、 配置PC端Fiddler和手机

(1) 配置Fiddler允许监听https

打开Fiddler菜单项Tools->Fiddler Options,选中decrypt https traffic和ignore server certificate errors两项,如下图:


fiddler https options

第一次会提示是否信任fiddler证书及安全提醒,选择yes,之后也可以在系统的证书管理中进行管理。

(2) 配置Fiddler允许远程连接

如上图的菜单中点击connections,选中allow remote computers to connect,默认监听端口为8888,若被占用也可以设置,配置好后需要重启Fiddler,如下图:


fiddler remote connect

(3) 配置手机端

Pc端命令行ipconfig查看Fiddler所在机器ip,本机ip为10.0.4.37,如下图


ipconfig

打开手机连接到同一局域网的wifi,并修改该wifi网络详情(长按wifi选择->修改网络)->显示高级选项,选择手动代理设置,主机名填写Fiddler所在机器ip,端口填写Fiddler端口,默认8888,如下图:


android network proxy

这时,手机上的网络访问在Fiddler就可以查看了,如下图微博和微信的网络请求:


微信抓数据包

可以双击上图某一行网络请求,右侧会显示具体请求内容(Request Header)和返回内容(Response Header and Content),如下图:


微博网络拦截

可以发现Fiddler可以以各种格式查看网络请求返回的数据,包括Header, TextView(文字), ImageView(图片), HexView(十六进制),WebView(网页形式), Auth(Proxy-Authenticate Header), Caching(Header cache), Cookies, Raw(原数据格式), JSON(json格式), XML(xml格式)很是方便。

停止网络监控的话去掉wifi的代理设置即可,否则Fiddler退出后手机就上不网了哦。

如果需要恢复手机无密码状态,Android端之后可以通过系统设置-安全-受信任的凭据-用户,点击证书进行删除或清除凭据删除所有用户证书,再设置密码为无。

如果只需要监控一个软件,可结合系统流量监控,关闭其他应用网络访问的权限。


利用fiddler抓取Android app数据包

做Android开发的朋友经常需要做网络数据的获取和提交表单数据等操作,然而对于调试程序而言,很难知道我们的数据到底是以怎样的形式发送的,是否发送成功,如果发送失败有是什么原因引起的。fiddler工具为我们提供了很方便的抓包操作,可以轻松抓取浏览器的发出的数据,不管是手机APP,还是web浏览器,都是可以的。

fiddler的工作原理

fiddler是基于代理来实现抓取网络数据包的工作的,当我们开启fiddler以后,fiddler会将我们的浏览器的代理默认进行更改为 127.0.0.1 端口是8888,这时fiddler的默认端口,也就是说我们发送的每一个请求和收到的每一个响应都会先经过fiddler,这样就实现了抓取数据包的工作。

路径:选项?>高级设置?>更改代理服务器设置?>局域网设置?>高级


技术分享

9.回话面板说明:


技术分享

session会话的分析

这里我随便选择一个会话来进行简单的分析。


技术分享

替换服务器端返回的数据

利用”autoresponser”可以替换服务器端返回的文件,当调试的时候需要替换服务器端返回的数据的时候,比如一个已经上线的项目,不可能真正的替换器某一个文件,我们可以这样来操作


技术分享

从图片当中,可以很清晰的看出,当我再次加载该会话的时候,会显示之前设置好的404代理。

如果需要设置不同的文件代理,也是可以的。比如对于该会话,原本服务器端返回的内容如下图:


技术分享

由于该session返回的是一个图片类型的,所以我选择ImageView这个选项卡,可以看到此时返回的图片的样子,那么如果需要用本地的文件代理该返回的内容,和之前的操作步骤都是一样的,只是在选择代理的时候选择本地文件即可,如下图:


技术分享

这次,我选择了一个本地的文件作为代理,此时当我再次重新请求该会话的时候,会返回本地的文件:


技术分享

可以看出这个时候该会话返回的内容已经是我本地的代理了。

fiddler网络限速

fiddler还为我们提供了一个很方便的网络限速的功能,通过网络限速的功能,可以来模拟用户的一些真实环境。fiddler提供了网络限速的插件,我们可以在他的官网下载:http://www.telerik.com/fiddler/add-ons


技术分享

点击”download”,下载完成之后,点击安装,需要重新启动fiddler,在重新启动fiddler之后,可以看到fiddler的工具栏选项卡,多出了一个FiddlerScript选项。


技术分享

比如我需要在请求之前延迟一段时间,可以这样做:


技术分享

在onBeforeRequest方法中加入这样一段代码”oSession[“request-trickle-delay”] = “3000”;”,那么如果需要在服务端响应之间做延迟只需要将”oSession[“request-trickle-delay”] = “3000”;”中的request替换成response即可。

利用fiddler抓取Android app数据包

终于到了今天的主题了,如何利用fiddler抓取Android app数据包,其实也是很简单的,只需要稍微配置一下就可以了。由于fiddler默认是抓取http协议的数据包,我们需要其能够抓取https这样的加密数据包,抓取Android app数据包,需要做如下配置:

1.配置fiddler

点击工具栏选项”tools?>FiddlerOptions”

配置https:


技术分享

配置远程连接:


技术分享

这些配置完成之后,一定要重新启动fiddler。

可以看到fiddler的默认端口是8888,我们可以现在浏览器上输入”http://127.0.0.1:8888”


技术分享

到这里为止我们的fiddler就配置完成了,接下来需要配置手机上的无线网络。

2.手机无线网络配置

注意:如果需要fiddler抓取Android app上的数据包,那么两者必须在同一个无线网络中。(同时,必要时请关闭电脑的防火墙)

在手机的无线网络配置之前,必须要首先知道fiddler所在主机的ip地址:


技术分享

可以看到我的fiddler所在主机,也就是我的电脑在无线网中的ip地址是192.168.1.109

打开手机设置中的无线网络界面,进行如下四步操作:

选中连接的网络,点击修改网络


技术分享

点击高级选项


技术分享

代理—>手动


技术分享

输入代理服务器的ip,也就是我们fiddler所在主机的ip地址,和端口,fiddler默认的端口是8888,IP选项设置为”DHCP”


技术分享

点击保存,此时手机端就配置成功了,打开fiddler,使用打开网易新闻客户端。


技术分享

此时可以看到fiddler抓取的网易app发送和接收的相关数据包。


技术分享

ok,左侧是我们的所有会话,我随机的选中一个会话,该会话是image类型的,查看该会话的内容,是我们网易新闻的头条上的图片。

注意:

1.关闭电脑的防火墙

2.如果需要抓取手机app的数据包,需要手机和电脑在都连接同一个无线网络

3.抓完包以后将fiddler关闭(提高访问网络的速度)同时将手机上的代理关闭 (如果不关闭代理,当fiddler关闭,或者是两者连接的不是同一无线网络,手机会不能正常的访问网络)


[!--infotagslink--]

相关文章

  • 安卓手机app添加支付宝支付开发教程

    支付宝支付在国内算是大家了,我们到处都可以使用支付宝了,下文整理介绍的是在安卓app应用中使用支付宝进行支付的开发例子。 之前讲了一篇博客关与支付宝集成获取...2016-09-20
  • Android子控件超出父控件的范围显示出来方法

    下面我们来看一篇关于Android子控件超出父控件的范围显示出来方法,希望这篇文章能够帮助到各位朋友,有碰到此问题的朋友可以进来看看哦。 <RelativeLayout xmlns:an...2016-10-02
  • Android开发中findViewById()函数用法与简化

    findViewById方法在android开发中是获取页面控件的值了,有没有发现我们一个页面控件多了会反复研究写findViewById呢,下面我们一起来看它的简化方法。 Android中Fin...2016-09-20
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • android自定义动态设置Button样式【很常用】

    为了增强android应用的用户体验,我们可以在一些Button按钮上自定义动态的设置一些样式,比如交互时改变字体、颜色、背景图等。 今天来看一个通过重写Button来动态实...2016-09-20
  • Android WebView加载html5页面实例教程

    如果我们要在Android应用APP中加载html5页面,我们可以使用WebView,本文我们分享两个WebView加载html5页面实例应用。 实例一:WebView加载html5实现炫酷引导页面大多...2016-09-20
  • 深入理解Android中View和ViewGroup

    深入理解Android中View和ViewGroup从组成架构上看,似乎ViewGroup在View之上,View需要继承ViewGroup,但实际上不是这样的。View是基类,ViewGroup是它的子类。本教程我们深...2016-09-20
  • Android自定义WebView网络视频播放控件例子

    下面我们来看一篇关于Android自定义WebView网络视频播放控件开发例子,这个文章写得非常的不错下面给各位共享一下吧。 因为业务需要,以下代码均以Youtube网站在线视...2016-10-02
  • Android用MemoryFile文件类读写进行性能优化

    java开发的Android应用,性能一直是一个大问题,,或许是Java语言本身比较消耗内存。本文我们来谈谈Android 性能优化之MemoryFile文件读写。 Android匿名共享内存对外A...2016-09-20
  • Android设置TextView竖着显示实例

    TextView默认是横着显示了,今天我们一起来看看Android设置TextView竖着显示如何来实现吧,今天我们就一起来看看操作细节,具体的如下所示。 在开发Android程序的时候,...2016-10-02
  • vscode搭建STM32开发环境的详细过程

    这篇文章主要介绍了vscode搭建STM32开发环境的详细过程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-05-02
  • android.os.BinderProxy cannot be cast to com解决办法

    本文章来给大家介绍关于android.os.BinderProxy cannot be cast to com解决办法,希望此文章对各位有帮助呀。 Android在绑定服务的时候出现java.lang.ClassCastExc...2016-09-20
  • PHP扩展开发教程(总结)

    PHP是一种解释型的语言,对于用户而言,我们精心的控制内存意味着easier prototyping和更少的崩溃!当我们深入到内核之后,所有的安全防线都已经被越过,最终还是要依赖于真正有责任心的软件工程师来保证系统的稳定运行。1、线...2015-11-08
  • Android 实现钉钉自动打卡功能

    这篇文章主要介绍了Android 实现钉钉自动打卡功能的步骤,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下...2021-03-15
  • Android 开发之布局细节对比:RTL模式

    下面我们来看一篇关于Android 开发之布局细节对比:RTL模式 ,希望这篇文章对各位同学会带来帮助,具体的细节如下介绍。 前言 讲真,好久没写博客了,2016都过了一半了,赶紧...2016-10-02
  • Android中使用SDcard进行文件的读取方法

    首先如果要在程序中使用sdcard进行存储,我们必须要在AndroidManifset.xml文件进行下面的权限设置: 在AndroidManifest.xml中加入访问SDCard的权限如下: <!--...2016-09-20
  • Android开发之PhoneGap打包及错误解决办法

    下面来给各位简单的介绍一下关于Android开发之PhoneGap打包及错误解决办法,希望碰到此类问题的同学可进入参考一下哦。 在我安装、配置好PhoneGap项目的所有依赖...2016-09-20
  • 安卓开发之Intent传递Object与List教程

    下面我们一起来看一篇关于 安卓开发之Intent传递Object与List的例子,希望这个例子能够为各位同学带来帮助。 Intent 不仅可以传单个的值,也可以传对象与数据集合...2016-09-20
  • 用Intel HAXM给Android模拟器Emulator加速

    Android 模拟器 Emulator 速度真心不给力,, 现在我们来介绍使用 Intel HAXM 技术为 Android 模拟器加速,使模拟器运行度与真机比肩。 周末试玩了一下在Eclipse中使...2016-09-20