1、多线程断点续传下载

使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内CPU分配给每条线程的平均执行时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒内只有10ms的执行时间。就如同一个水龙头,每秒出水量相等的情况下,放990毫秒的水肯定比放10毫秒的水要多。

多线程下载的实现过程:

1>首先得到下载文件的长度,然后设置本地文件

的长度。

HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度
2>根据文件长度和线程数计算每条线程下载的数据长度和下载位置。如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如上图所示。
3>使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止,代码如下:
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
4>保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
public class FileDownLoader {
@Test
public void download() throws Exception {
String path = "http://browse.babasport.com/QQWubiSetup.exe";
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5*1000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn.setRequestProperty("Connection", "Keep-Alive");
System.out.println(conn.getResponseCode());
int filesize = conn.getContentLength();//得到文件大小
conn.disconnect();
int threasize = 3;//线程数
int perthreadsize = filesize / 3 + 1;
RandomAccessFile file = new RandomAccessFile("102.wma","rw");
file.setLength(filesize);//设置本地文件的大小
file.close();
for(int i=0; i<threasize ; i++){
int startpos = i * perthreadsize;//计算每条线程的下载位置
RandomAccessFile perthreadfile = new RandomAccessFile("102.wma","rw");//
perthreadfile.seek(startpos);//从文件的什么位置开始写入数据
new DownladerThread(i, path, startpos, perthreadsize, perthreadfile).start();
}
//以下代码要求用户输入q才会退出测试方法,如果没有下面代码,会因为进程结束而导致进程内的下载线程被销毁
int quit = System.in.read();
while('q'!=quit){
Thread.sleep(2 * 1000);
}
}
private class DownladerThread extends Thread{
private int startpos;//从文件的什么位置开始下载
private int perthreadsize;//每条线程需要下载的文件大小
private String path;
private RandomAccessFile file;
private int threadid;
public DownladerThread(int threadid, String path, int startpos, int perthreadsize, RandomAccessFile perthreadfile) {
this.path = path;
this.startpos = startpos;
this.perthreadsize = perthreadsize;
this.file = perthreadfile;
this.threadid = threadid;
}
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn.setRequestProperty("Accept-Language", "zh-CN");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Range", "bytes=" + this.startpos + "-");
InputStream inStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
int length = 0;
while(length<perthreadsize && (len = inStream.read(buffer))!=-1){
file.write(buffer, 0, len);
length += len;//累计该线程下载的总大小
}
file.close();
inStream.close();
System.out.println(threadid+ "线程完成下载");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
多线程断点下载示例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<EditText
android:id="@+id/et_count"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="请输入下载线程的个数"
android:inputType="number" />
<EditText
android:id="@+id/et_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="请输入下载的路径" />
<Button
android:id="@+id/bt_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="download"
android:text="下载" />
    
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="stop"
android:text="停止/暂停" />
<LinearLayout
android:id="@+id/ll_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
package com.itheima.mutildownloader;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
public class MainActivity extends Activity {
private EditText et_path;
private EditText et_count;
private Button bt_download;
private boolean downloadflag;
/**
下载的线程的数量
 */
private static int threadCount = 3;
/**
每一个线程下载的数据的大小
 */
private static int blockSize;
/**
已经下载完毕的线程树立那个
 */
private static int finishedThreadCount;
private static List<ProgressBar> pbs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_path = (EditText) findViewById(R.id.et_path);
et_count = (EditText) findViewById(R.id.et_count);
bt_download = (Button) findViewById(R.id.bt_download);
}
public void download(View view){
String countstr = et_count.getText().toString().trim();
if(TextUtils.isEmpty(countstr)){
Toast.makeText(getApplicationContext(), "请输入线程的个数", 0).show();
return;
}
bt_download.setEnabled(false);
downloadflag = true;
threadCount = Integer.parseInt(countstr);
pbs = new ArrayList<ProgressBar>();
LinearLayout ll = (LinearLayout) findViewById(R.id.ll_container);
ll.removeAllViews();
for(int i =1;i<=threadCount;i++){
ProgressBar pb = (ProgressBar) View.inflate(this, R.layout.pb_item, null);
pbs.add(pb);
ll.addView(pb);
}
final String path  =et_path.getText().toString().trim();
new Thread(){
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
int code = conn.getResponseCode();
if (code == 200) {
int length = conn.getContentLength();
System.out.println("文件的总长度:" + length);
RandomAccessFile raf = new RandomAccessFile("/sdcard/"+getFileName(path), "rw");
// 创建一个空文件
raf.setLength(length);
// 计算每个线程 下载文件区块的大小
blockSize = length / threadCount;
// 开启若干个子线程进行下载
finishedThreadCount = 0;
for (int i = 1; i <= threadCount; i++) {
// 计算当前线程下载的开始和结束的位置
int startIndex = (i - 1) * blockSize;
int endIndex = i * blockSize - 1;
if (i == threadCount) {
endIndex = length;
}
System.out.println("线程:" + i + "下载的文件的位置:" + startIndex + "~"
+ endIndex + "当前线程下载的大小:" + (endIndex - startIndex));
//设置进度条的最大值
pbs.get(i-1).setMax(endIndex - startIndex);
new Thread(new DownLoadTask(i, startIndex, endIndex, path)).start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
private class DownLoadTask implements Runnable{
private int threadId;
private int startIndex;
private int endIndex;
private String path;
public DownLoadTask(int threadId, int startIndex, int endIndex,
String path) {
this.threadId = threadId;
this.startIndex = startIndex;
this.endIndex = endIndex;
this.path = path;
}
@Override
public void run() {
try {
int downloadSize = 0;
//定义一个文件对象,保存当前下载的位置.
File file = new File("/sdcard/"+getFileName(path)+threadId+".txt");
//检查是否已经有过下载的记录,如果有,应该从上一次终止的位置继续下载
if(file.exists()&&file.length()>0){
FileInputStream fis = new FileInputStream(file);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//重新设置当前现在下载的开始位置.
int newStartIndex  = Integer.parseInt(br.readLine());
//原来已经下载的大小
downloadSize = newStartIndex - startIndex;
//重新设置当前现在下载的开始位置.
startIndex = newStartIndex;
fis.close();
}
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
conn.setRequestProperty("Range", "bytes="+startIndex+"-"+endIndex);
System.out.println("线程:"+threadId+"下载的真实位置:"+"bytes="+startIndex+"-"+endIndex);
int code  = conn.getResponseCode();//下载指定位置的文件  206
if(code == 206){
InputStream is = conn.getInputStream();
RandomAccessFile raf = new RandomAccessFile("/sdcard/"+getFileName(path), "rw");
//指定每个线程 保存数据的开始位置
raf.seek(startIndex);
byte[] buffer = new byte[2048];
int len = 0;
//当前线程下载的大小
int total = 0;
while((len = is.read(buffer))!=-1&&downloadflag){
raf.write(buffer, 0, len);
total +=len;
pbs.get(threadId-1).setProgress(total+downloadSize);
//记录当前线程 下载的位置
RandomAccessFile positionRaf = new RandomAccessFile(file, "rwd");
positionRaf.write(String.valueOf(total+startIndex).getBytes());
positionRaf.close();
}
is.close();
raf.close();
System.out.println("线程:"+threadId+"下载完毕...");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
synchronized (MainActivity.class) {
finishedThreadCount++;
if(finishedThreadCount==threadCount){
for(int i=1;i<=threadCount;i++){
if(!downloadflag){
return ;
}
File file = new File("/sdcard/"+getFileName(path)+i+".txt");
System.out.println(file.delete());
}
System.out.println("全部下载完毕.");
runOnUiThread(new Runnable() {
@Override
public void run() {
bt_download.setEnabled(true);
}
});
}
}
}
}
}
/**
返回文件名
 * 
 * @param path
 * @return
 */
public static String getFileName(String path) {
int start = path.lastIndexOf("/") + 1;
return path.substring(start, path.length());
}
public void stop(View view){
downloadflag = false;
bt_download.setEnabled(true);
}
}
2、为应用添加新的Activity
第一步:新建一个继承Activity的类,如:NewActivity
public class NewActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
                   super.onCreate(savedInstanceState);
 这里可以使用setContentView(R.layout.xxx)显示某个视图....
     }
}
第二步:需要在功能清单AndroidManifest.xml文件中添加进上面Activity配置代码(红色部分):
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="cn.itcast.action"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        .....
<activity android:name=".NewActivity" android:label="新activity的页面标题"/>
    </application>
    ...
</manifest>

android:name属性值的前面加了一个点表示NewActivity是当前包cn.itcast.action下的类,如果类在应用的当前包下,可以省略点符号,如果类在应用的子包下必须加点,如:NewActivity类在cn.itcast.action.user包下可以这样写:<activity android:name=“.user.NewActivity“ />

3、Activity生命周期

Activity有三个状态:

 当它在屏幕前台时(位于当前任务堆栈的顶部),它是激活或运行状态。它就是响应用户操作的Activity。 

Activity,使它失去了焦点但仍然对用户可见时(如右图),它处于暂停状态。在它之上的Activity没有完全覆盖屏幕,或者是透明的,被暂停的Activity仍然对用户可见,并且是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接)。如果系统处于内存不足时会杀死这个Activity。 

当它完全被另一个Activity覆盖时则处于停止状态。它仍然保留所有的状态和成员信息。然而对用户是不可见的,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个Activity。 

当Activity从一种状态转变到另一种状态时,会调用以下保护方法来通知这种变化:

(1)void onCreate(Bundle savedInstanceState)

(2)void onStart()

(3)void onRestart()

(4)void onResume()

(5)void onPause()

(6)void onStop()

(7)void onDestroy()

这七个方法定义了Activity的完整生命周期。实现这些方法可以帮助我们监视其中的三个嵌套生命周期循环:

的完整生命周期自第一次调用onCreate()开始,直至调用onDestroy()为止。Activity在onCreate()中设置所有“全局”状态以完成初始化,而在onDestroy()中释放所有系统资源。例如,如果Activity有一个线程在后台运行从网络下载数据,它会在onCreate()创建线程,而在 onDestroy()销毁线程。 

的可视生命周期自onStart()调用开始直到相应的onStop()调用结束。在此期间,用户可以在屏幕上看到Activity,尽管它也许并不是位于前台或者也不与用户进行交互。在这两个方法之间,我们可以保留用来向用户显示这个Activity所需的资源。例如,当用户不再看见我们显示的内容时,我们可以在onStart()中注册一个BroadcastReceiver来监控会影响UI的变化,而在onStop()中来注消。onStart() 和 onStop() 方法可以随着应用程序是否为用户可见而被多次调用。 

的前台生命周期自onResume()调用起,至相应的onPause()调用为止。在此期间,Activity位于前台最上面并与用户进行交互。Activity会经常在暂停和恢复之间进行状态转换——例如当设备转入休眠状态或者有新的Activity启动时,将调用onPause() 方法。当Activity获得结果或者接收到新的Intent时会调用onResume() 方法。关于前台生命周期循环的例子请见PPT下方备注栏。

Activity的前台生命周期循环例子:

1》创建一个Activity,添加七个生命周期方法,方法内输出各个方法名称。再添加一个按钮用于打开下面新添加的Activity。

startActivity(new Intent(LifeActivity.this, CustomDialogActivity.class));

2》添加一个新Activity,代码如下:

public class CustomDialogActivity extends Activity {
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
必须在调用setContentView()之前调用requestWindowFeature()
要标题栏显示图标
        setContentView(R.layout.dialog_activity);       
设置图标
    }
}
3》在A

ndroidManifest.xml文件配置Activity,并且通过主题指定该Activity以对话框样式显示。

<application android:icon="@drawable/icon" android:label="@string/app_name" >
        .....
<activity android:name=".CustomDialogActivity" android:label="对话框activity" 
android:theme="@android:style/Theme.Dialog"/>
 </application>
Activity的onSaveInstanceState()和 onRestor

eInstanceState()方法

Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它们不同于 onCreate()、onPause()等生命周期方法,它们并不一定会被触发。当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState() 会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。因为在这种情况下,用户的行为决定了不需要保存Activity的状态。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。

另外,当屏幕的方向发生了改变, Activity会被摧毁并且被重新创建,如果你想在Activity被摧毁前缓存一些数据,并且在Activity被重新创建后恢复缓存的数据。可以重写Activity的 onSaveInstanceState() 和 onRestoreInstanceState()方法,如下:

public class PreferencesActivity extends Activity {
    private String name;
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
name = savedInstanceState.getString("name"); //被重新创建后恢复缓存的数据
super.onRestoreInstanceState(savedInstanceState);
    }
    protected void onSaveInstanceState(Bundle outState) {
outState.putString("name", "liming");//被摧毁前缓存一些数据
super.onSaveInstanceState(outState);
    }
}
Activity生命周期
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:onClick="click"
android:text="点击打开02界面" />
</RelativeLayout>
<?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" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Large Text"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="@+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
package com.itheima.lifecycle;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
//当activity被创建 要开始的时候执行的方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("activity  oncreate");
}
//当activity被销毁之前调用的方法
@Override
protected void onDestroy() {
System.out.println("activity  ondestroy");
super.onDestroy();
}
//当界面变成用户可见的时候调用的方法
@Override
protected void onStart() {
System.out.println("onstart");
super.onStart();
}
//当前界面用户 不再可见的时候调用的方法
@Override
protected void onStop() {
System.out.println("onstop");
super.onStop();
}
//界面获取到焦点的时候 调用的方法.  用户就可以与界面进行交互
@Override
protected void onResume() {
System.out.println("onresume");
super.onResume();
}
//当界面失去焦点的时候 调用的方法
@Override
protected void onPause() {
System.out.println("pause");
super.onPause();
}
public void click(View view){
Intent intent = new Intent(this, SencodeAcitvity.class);
startActivity(intent);
}
}
package com.itheima.lifecycle;
import android.app.Activity;
import android.os.Bundle;
public class SencodeAcitvity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}

4、横竖屏幕切换

默认情况下,当“屏幕方向”或“键盘显示隐藏” 变化时都会销毁当前Activity,创建新的Activity。如果不希望重新创建Activity实例,可以按如下配置Activity:

<activity android:name=".MainActivity" android:configChanges="keyboardHidden|orientation">

上面的android:configChanges属性指定了要捕获“屏幕方向”和“键盘显示隐藏”变化,当捕获到这些变化后会调用Activity的onConfigurationChanged()方法。

默认情况下(没有配置android:configChanges属性):

竖屏切横屏,销毁当前Activity之后,创建一个新Activity实例。

横屏切竖屏,销毁当前Activity之后,创建一个新Activity实例,新的Activity实例很快就被销毁,接着又会创建一个新Activity实例。如果只希望创建一个实例,可以配置android:configChanges="orientation"

5、打开新的Activity ,不传递参数

在一个Activity中可以使用系统提供的startActivity(Intent intent)方法打开新的Activity,在打开新的Activity前,你可以决定是否为新的Activity传递参数:

第一种:打开新的Activity,不传递参数

public class MainActivity extends Activity {
  @Override protected void onCreate(Bundle savedInstanceState) {
.......
Button button =(Button) this.findViewById(R.id.button);
点击该按钮会打开一个新的Activity
public void onClick(View v) {
新建一个显式意图,第一个参数为当前Activity类对象,第二个参数为你要打开的Activity类
    startActivity(new Intent(MainActivity.this, NewActivity.class));
}});
         }
}

6、打开新的Activity,并传递若干个参数给它

第二种:打开新的Activity,并传递若干个参数给它:

public class MainActivity extends Activity {
  @Override protected void onCreate(Bundle savedInstanceState) {
.......
点击该按钮会打开一个新的Activity
          public void onClick(View v) {
           Intent intent = new Intent(MainActivity.this, NewActivity.class)
Bundle bundle = new Bundle();//该类用作携带数据
bundle.putString("name", "传智播客");
bundle.putInt("age", 4);
intent.putExtras(bundle);//附带上额外的数据
startActivity(intent);
}}); }
}
在新的Activity中接收前面Activity传递过来的参数:
public class NewActivity extends Activity {
            @Override protected void onCreate(Bundle savedInstanceState) {
     ........
     Bundle bundle = this.getIntent().getExtras();
     String name = bundle.getString("name");
                    int age = bundle.getInt("age");
            }
}

7、Bundle类的作用

Bundle类用作携带数据,它类似于Map,用于存放key-value名值对形式的值。相对于Map,它提供了各种常用类型的putXxx()/getXxx()方法,如:putString()/getString()和putInt()/getInt(),putXxx()用于往Bundle对象放入数据,getXxx()方法用于从Bundle对象里获取数据。Bundle的内部实际上是使用了HashMap<String, Object>类型的变量来存放putXxx()方法放入的值:

public final class Bundle implements Parcelable, Cloneable {
            ......
 Map<String, Object> mMap;
 public Bundle() {
       mMap = new HashMap<String, Object>();
        ......
 }
 public void putString(String key, String value) {
      mMap.put(key, value);
 }
public String getString(String key) {
       Object o = mMap.get(key);
        return (String) o;
类型转换失败后会返回null,这里省略了类型转换失败后的处理代码
}
}

在调用Bundle对象的getXxx()方法时,方法内部会从该变量中获取数据,然后对数据进行类型转换,转换成什么类型由方法的Xxx决定,getXxx()方法会把转换后的值返回。

8、为Intent附加数据的两种写法

第一种写法,用于批量添加数据到Intent:

Intent intent = new Intent();
Bundle bundle = new Bundle();//该类用作携带数据
bundle.putString("name", "传智播客");

intent.putExtras(bundle);//为意图追加额外的数据,意图原来已经具有的数据不会丢失,但key同名的数据会被替换

第二种写法:这种写法的作用等价于上面的写法,只不过这种写法是把数据一个个地添加进Intent,这种写法使用起来比较方便,而且只需要编写少量的代码。

Intent intent = new Intent();
intent.putExtra("name", "传智播客");

Intent提供了各种常用类型重载后的putExtra()方法,如: putExtra(String name, String value)、 putExtra(String name, long value),在putExtra()方法内部会判断当前Intent对象内部是否已经存在一个Bundle对象,如果不存在就会新建Bundle对象,以后调用putExtra()方法传入的值都会存放于该Bundle对象,下面是Intent的putExtra(String name, String value)方法代码片断:

public class Intent implements Parcelable {
private Bundle mExtras;
public Intent putExtra(String name, String value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putString(name, value);
        return this;
 }

9、得到新打开Activity 关闭后返回的数据

如果你想在Activity中得到新打开Activity 关闭后返回的数据,你需要使用系统提供的startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新的Activity 关闭后会向前面的Activity 传回数据,为了得到传回的数据,你必须在前面的Activity中重写onActivityResult(int requestCode, int resultCode, Intent data)方法:

public class MainActivity extends Activity {
      @Override protected void onCreate(Bundle savedInstanceState) {
.......
Button button =(Button) this.findViewById(R.id.button);
点击该按钮会打开一个新的Activity
public void onClick(View v) {
//第二个参数为请求码,可以根据业务需求自己编号
startActivityForResult (new Intent(MainActivity.this, NewActivity.class),  1);
}});
         }

第一个参数为请求码,即调用startActivityForResult()传递过去的值

第二个参数为结果码,结果码用于标识返回数据来自哪个新Activity

@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
String result = data.getExtras().getString(“result”));//得到新Activity 关闭后返回的数据
    }

} Activity关闭后,新Activity返回的数据通过Intent进行传递,android平台会调用前面Activity 的onActivityResult()方法,把存放了返回数据的Intent作为第三个输入参数传入,在onActivityResult()方法中使用第三个输入参数可以取出新Activity返回的数据。

使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,新Activity关闭前需要向前面的Activity返回数据需要使用系统提供的setResult(int resultCode, Intent data)方法实现:

public class NewActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) {
......
        button.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
Intent intent = new Intent();//数据是使用Intent返回
intent.putExtra(“result”, “传智播客的学生很可爱”);//把返回数据存入Intent

设置返回数据

关闭Activity
}});
}
}
setResult()方法的第一个参数值可以根据业务需要自己定义,上面代码中使用到的RESULT_OK是系统Activity类定义的一个常量,值为-1,代码片断如下:
public class android.app.Activity extends ......{
  public static final int RESULT_CANCELED = 0;
  public static final int RESULT_OK = -1;
  public static final int RESULT_FIRST_USER = 1;
}

10、请求码的作用

使用startActivityForResult(Intent intent, int requestCode)方法打开新的Activity,我们需要为startActivityForResult()方法传入一个请求码(第二个参数)。请求码的值是根据业务需要由自已设定,用于标识请求来源。例如:一个Activity有两个按钮,点击这两个按钮都会打开同一个Activity,不管是那个按钮打开新Activity,当这个新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestCode, int resultCode, Intent data)方法。在onActivityResult()方法如果需要知道新Activity是由那个按钮打开的,并且要做出相应的业务处理,这时可以这样做:

@Override  public void onCreate(Bundle savedInstanceState) {
        ....
        button1.setOnClickListener(new View.OnClickListener(){
  public void onClick(View v) {
   startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 1);
   }});
        button2.setOnClickListener(new View.OnClickListener(){
  public void onClick(View v) {
   startActivityForResult (new Intent(MainActivity.this, NewActivity.class), 2);
   }}); 
       @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
               switch(requestCode){
                   case 1:
来自按钮1的请求,作相应业务处理
                   case 2:
              来自按钮2的请求,作相应业务处理
                }
          }
}

11、结果码的作用

在一个Activity中,可能会使用startActivityForResult()方法打开多个不同的Activity处理不同的业务,当这些新Activity关闭后,系统都会调用前面Activity的onActivityResult(int requestC

ode, int resultCode, Intent data)方法。为了知道返回的数据来自于哪个新Activity,在onActivityResult()方法中可以这样做
(ResultActivity和NewActivity为要打开的新Activity):
public class ResultActivity extends Activity {
       .....
       ResultActivity.this.setResult(1, intent);
       ResultActivity.this.finish();
}
public class NewActivity extends Activity {
       ......
        NewActivity.this.setResult(2, intent);
        NewActivity.this.finish();
}
public class MainActivity extends Activity { // 在该Activity会打开ResultActivity和NewActivity
       @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
               switch(resultCode){
                   case 1:
的返回数据
                   case 2:
              的返回数据
                }
          }
}

12、Intent(意图)

Android基本的设计理念是鼓励减少组件间的耦合,因此Android提供了Intent (意图) ,Intent提供了一种通用的消息系统,它允许在你的应用程序与其它的应用程序间传递Intent来执行动作和产生事件。使用Intent可以激活Android应用的三个核心组件:活动、服务和广播接收器。

Intent可以划分成显式意图和隐式意图。

显式意图:调用Intent.setComponent()或Intent.setClass()方法明确指定了组件名的Intent为显式意图,显式意图明确指定了Intent应该传递给哪个组件。

隐式意图:没有明确指定组件名的Intent为隐式意图。 Android系统会根据隐式意图中设置的动作(action)、类别(category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。

<intent-filter> 
<action android:name="android.intent.action.CALL" /> 
<category android:name="android.intent.category.DEFAULT" /> 
<data android:scheme="tel" /> 
</intent-filter> 
<intent-filter> 
<action android:name="android.intent.action.CALL" /> 
<category android:name="android.intent.category.DEFAULT" /> 
<data android:mimeType="vnd.android.cursor.item/phone" /> 
</intent-filter>

对于隐式意图,Android是怎样寻找到这个最合适的组件呢?记的前面我们在定义活动时,指定了一个intent-filter,Intent Filter(意图过滤器)其实就是用来匹配隐式Intent的,当一个意图对象被一个意图过滤器进行匹配测试时,只有三个方面会被参考到:动作、数据(URI以及数据类型)和类别。

动作测试(Action test)

  一个意图对象只能指定一个动作名称,而一个过滤器可能列举多个动作名称。如果意图对象或过滤器没有指定任何动作,结果将如下:

• 如果过滤器没有指定任何动作,那么将阻塞所有的意图,因此所有的意图都会测试失败。没有意图能够通过这个过滤器。 

• 另一方面,只要过滤器包含至少一个动作,一个没有指定动作的意图对象自动通过这个测试

类别测试(Category test)

对于一个能够通过类别匹配测试的意图,意图对象中的类别必须匹配过滤器中的类别。这个过滤器可以列举另外的类别,但它不能遗漏在这个意图中的任何类别。

原则上一个没有类别的意图对象应该总能够通过匹配测试,而不管过滤器里有什么。大部分情况下这个是对的。但有一个例外,Android把所有传给startActivity()的隐式意图当作他们包含至少一个类别:"android.intent.category.DEFAULT" (CATEGORY_DEFAULT常量)。 因此,想要接收隐式意图的活动必须在它们的意图过滤器中包含"android.intent.category.DEFAULT"。(带"android.intent.action.MAIN"和"android.intent.category.LAUNCHER"设置的过滤器是例外)

数据测试(Data test)

当一个意图对象中的URI被用来和一个过滤器中的URI比较时,比较的是URI的各个组成部分。例如,如果过滤器仅指定了一个scheme,所有该scheme的URIs都能够和这个过滤器相匹配;如果过滤器指定了一个scheme、主机名但没有路经部分,所有具有相同scheme和主机名的URIs都可以和这个过滤器相匹配,而不管它们的路经;如果过滤器指定了一个scheme、主机名和路经,只有具有相同scheme、主机名和路经的URIs才可以和这个过滤器相匹配。当然,一个过滤器中的路径规格可以包含通配符,这样只需要部分匹配即可。

数据测试同时比较意图对象和过滤器中指定的URI和数据类型。规则如下:

a. 一个既不包含URI也不包含数据类型的意图对象仅在过滤器也同样没有指定任何URIs和数据类型的情况下才能通过测试。

b. 一个包含URI但没有数据类型的意图对象仅在它的URI和一个同样没有指定数据类型的过滤器里的URI匹配时才能通过测试。这通常发生在类似于mailto:和tel:这样的URIs上:它们并不引用实际数据。

c. 一个包含数据类型但不包含URI的意图对象仅在这个过滤器列举了同样的数据类型而且也没有指定一个URI的情况下才能通过测试。

d. 一个同时包含URI和数据类型(或者可从URI推断出数据类型)的意图对象可以通过测试,如果它的类型和过滤器中列举的类型相匹配的话。如果它的URI和这个过滤器中的一个URI相匹配或者它有一个内容content:或者文件file: URI而且这个过滤器没有指定一个URI,那么它也能通过测试。换句话说,一个组件被假定为支持content:和file: 数据如果它的过滤器仅列举了一个数据类型。

13、人品计算器

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.itheima.rpcalc.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.itheima.rpcalc.CalcActivity"
android:label="@string/rp_calc" >
<intent-filter>
<action android:name="com.itheima.rpcalc.calc" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.baidu.com"
android:scheme="http" >
</data>
</intent-filter>
</activity>
</application>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:text="欢迎使用人品计算器" />
<EditText
android:text="群群"
android:id="@+id/et_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/tv_title"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:hint="请输入姓名" />
<Button
android:id="@+id/bt_click1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tv_title"
android:layout_centerHorizontal="true"
android:onClick="click"
android:text="显式意图点击进入" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/bt_click1"
android:layout_centerHorizontal="true"
android:onClick="click2"
android:text="隐式意图点击进入" />
</RelativeLayout>
<?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" >
<TextView
android:id="@+id/tv_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
package com.itheima.rpcalc;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity {
private EditText et_name;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
}
//激活组件的两种方式: 显式意图, 隐式意图
public void click(View view) {
String name = et_name.getText().toString().trim();
// 显式意图: 显式的指定要开启的界面的名称 (包名, 类名)
// 创建一个意图对象, 作用就是用来激活组件
Intent intent = new Intent(this, CalcActivity.class);
//把额外的数据 放入到 intent的意图对象里面
intent.putExtra("name", name);
// intent.setClassName(this, "com.itheima.rpcalc.CalcActivity");
// 把意图对应的组件给开启起来
startActivity(intent);
}
// 隐式意图点击进入
// 打人  看网站 看照片 泡茶 泡妞
public void click2(View view){
Intent intent = new Intent();
String name = et_name.getText().toString().trim();
intent.putExtra("name", name);
//设置动作
intent.setAction("com.itheima.rpcalc.calc");
//设置前提条件
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
}
package com.itheima.rpcalc;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
public class CalcActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载一个布局资源
setContentView(R.layout.activity_calc);
//获取到激活当前界面的意图对象
Intent intent = getIntent();
String name = intent.getStringExtra("name");
TextView tv = (TextView) findViewById(R.id.tv_info);
char[] array = name.toCharArray();
int total =0 ;
for(char c : array){
int i = c;
total +=i;
}
tv.setText(name+"的人品值为:"+total%100+"分");
}
}
14、人品计算器V2
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.itheima.rpcalc.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.itheima.rpcalc.CalcActivity"
android:label="@string/rp_calc" >
<intent-filter>
<action android:name="com.itheima.rpcalc.calc" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:host="www.baidu.com"
android:scheme="http" >
</data>
</intent-filter>
</activity>
<activity android:name="com.itheima.rpcalc.NameListActivity" >
</activity>
</application>
package com.itheima.rpcalc;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;
public class CalcActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载一个布局资源
setContentView(R.layout.activity_calc);
//获取到激活当前界面的意图对象
Intent intent = getIntent();
String name = intent.getStringExtra("name");
TextView tv = (TextView) findViewById(R.id.tv_info);
char[] array = name.toCharArray();
int total =0 ;
for(char c : array){
int i = c;
total +=i;
}
tv.setText(name+"的人品值为:"+total%100+"分");
}
}
package com.itheima.rpcalc;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity {
private EditText et_name;
private EditText et_name2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_name = (EditText) findViewById(R.id.et_name);
et_name2 = (EditText) findViewById(R.id.et_name2);
}
// 激活组件的两种方式: 显式意图, 隐式意图
public void click(View view) {
String name = et_name.getText().toString().trim();
// 显式意图: 显式的指定要开启的界面的名称 (包名, 类名)
// 创建一个意图对象, 作用就是用来激活组件
Intent intent = new Intent(this, CalcActivity.class);
// 把额外的数据 放入到 intent的意图对象里面
intent.putExtra("name", name);
// intent.setClassName(this, "com.itheima.rpcalc.CalcActivity");
// 把意图对应的组件给开启起来
startActivity(intent);
}
// 隐式意图点击进入
// 打人 看网站 看照片 泡茶 泡妞
public void click2(View view) {
Intent intent = new Intent();
String name = et_name.getText().toString().trim();
intent.putExtra("name", name);
// 设置动作
intent.setAction("com.itheima.rpcalc.calc");
// 设置前提条件
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
// 选择一个姓名
public void selectName(View view) {
Intent intent = new Intent(this, NameListActivity.class);
// 开启一个新的界面, 当新的界面关闭的时候,会返回一个结果 onActivityResult()
startActivityForResult(intent, 1);
}
// 选择第二个姓名
public void selectName2(View view) {
Intent intent = new Intent(this, NameListActivity.class);
// 开启一个新的界面, 当新的界面关闭的时候,会返回一个结果 onActivityResult()
startActivityForResult(intent, 2);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null) {
String name = data.getStringExtra("clickedName");
if (requestCode == 1) {
et_name.setText(name);
} else if (requestCode == 2) {
et_name2.setText(name);
}
}
}
}
package com.itheima.rpcalc;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
public class NameListActivity extends Activity {
private ListView lv_names;
private static String[] names = new String[]{
"张三","李四","王五","周期","大法","大沙发","反射的"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_name);
lv_names = (ListView) findViewById(R.id.lv_name);
lv_names.setAdapter(new ArrayAdapter<String>(this, R.layout.tv_name,names ));
lv_names.setOnItemClickListener(new OnItemClickListener() {
//position 被点击条目的位置
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
String clickedName = names[position];
Toast.makeText(getApplicationContext(), clickedName, 0).show();
//返回数据给调用的activity
Intent data  = new Intent();
data.putExtra("clickedName", clickedName);
setResult(0, data);
//关闭界面
finish();
}
});
}
}