1.Android多线程断点下载示例
使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务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,每条线程开始下载的位置如图所示。
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
4>保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
代码示例:
package com.itheima.mutildownload;
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 android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;
@SuppressLint("HandlerLeak")
public class MainActivity extends Activity {
protected static final int DOWN_LOAD_ERROR = 1;
protected static final int SERVER_ERROR = 2;
public static final int DOWN_LOAD_FINSIH = 3;
private EditText et_path;
private ProgressBar pb;
public static int threadCount = 3;
public static int runningThreadCount;
public int currentProcess = 0;
private Handler handler = new Handler() {
@SuppressWarnings("unused")
public void handlerMessage(Message msg) {
switch (msg.what) {
case DOWN_LOAD_ERROR:
Toast.makeText(getApplicationContext(), "下载失败", Toast.LENGTH_SHORT).show();
break;
case SERVER_ERROR:
Toast.makeText(getApplicationContext(), "服务器错误,下载失败", Toast.LENGTH_SHORT).show();
break;
case DOWN_LOAD_FINSIH:
Toast.makeText(getApplicationContext(), "下载完毕", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_path = (EditText) findViewById(R.id.et_path);
pb = (ProgressBar) findViewById(R.id.pb);
}
public void download(View view) {
final String path = et_path.getText().toString().trim();
if (TextUtils.isEmpty(path)) {
Toast.makeText(this, "下载路径错误", Toast.LENGTH_SHORT).show();
return;
}
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();
pb.setMax(length);//设置进度条的最大值
System.out.println("服务器文件总长度:" + length);
// 在客户端本地创建出来一个大小跟服务器端文件的一样大小的临时文件
RandomAccessFile raf = new RandomAccessFile(
"/sdcard/setup.exe", "rwd");
raf.setLength(length);
raf.close();
// 假设是3个线程去下载资源
// 平均每一个线程下载的文件的大小
int blockSize = length / threadCount;
for (int threadId = 1; threadId <= threadCount; threadId++) {
int startIndex = (threadId - 1) * blockSize;
int endIndex = threadId * blockSize - 1;
if (threadId == threadCount) {// 最后一个线程下载的长度要稍微长一点
// 最后一个线程
endIndex = length;
}
System.out.println("线程:" + threadId + "下载:---"
+ startIndex + "--->" + endIndex);
new DownloadThread(threadId, startIndex, endIndex,
path).start();
}
} else {
System.out.println("服务器下载错误");
Message msg = new Message();
msg.what = SERVER_ERROR;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
Message msg = new Message();
msg.what = DOWN_LOAD_ERROR;
handler.sendMessage(msg);
}
}
}.start();
}
class DownloadThread extends Thread {
private int threadId;
private int startIndex;
private int endIndex;
private String path;
public DownloadThread(int threadId, int startPos, int endPos,
String path) {
this.threadId = threadId;
this.startIndex = startPos;
this.endIndex = endPos;
this.path = path;
}
@Override
public void run() {
try {
URL url = new URL(path);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5000);
// 检查是否有下载中断的记录..。
File file = new File("/sdcard/" + threadId + ".txt");
if (file.exists() && file.length() > 0) {
FileInputStream fis = new FileInputStream(file);
byte[] temp = new byte[1024];
int leng = fis.read(temp);
String downloadLen = new String(temp, 0, leng);
int downloadlenInt = Integer.parseInt(downloadLen);
int alreadDownloadInt = downloadlenInt - startIndex;
currentProcess+=alreadDownloadInt;//计算上次断点已经下载文件的长度
startIndex = downloadlenInt;
fis.close();
}
conn.setRequestProperty("Range", "bytes=" + startIndex + "-"
+ endIndex);
System.out.println("线程id:" + threadId + "开始下载:" + startIndex
+ "~" + endIndex);
int code = conn.getResponseCode();
if (code == 206) {
InputStream is = conn.getInputStream(); // 不是200 206请求部分数据成功
RandomAccessFile raf = new RandomAccessFile(
"/sdcard/setup.exe", "rwd");
// 重新指定某个线程保存文件的开始位置 与 服务器下载的位置相同
raf.seek(startIndex);
int len = 0;
byte[] buffer = new byte[1024];
int total = 0;// 当前这次下载的数据大小
while ((len = is.read(buffer)) != -1) {
RandomAccessFile raffile = new RandomAccessFile(
"/sdcard/" + threadId + ".txt", "rwd");
raf.write(buffer, 0, len);
total += len;// 数据记录下来 //记录下一次下载数据的开始位置.
String newStartPos = String.valueOf(startIndex + total);
raffile.write(newStartPos.getBytes());
raffile.close();
synchronized (MainActivity.this) {
currentProcess+=len;//获取当前的总进度
pb.setProgress(currentProcess);//更改界面上progressbar进度条的进度
//特殊情况,progressbar progressdialog 进度框对话框可以直接在子线程里面更新ui 内部代码进行了特殊处理
}
}
is.close();
raf.close();
System.out.println("线程:" + threadId + "下载完毕");
} else {
System.out.println("线程:" + threadId + "下载失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadFinish();
}
}
private synchronized void threadFinish() {
runningThreadCount--;
if (runningThreadCount == 0) {
for (int i = 1; i <= threadCount; i++) {
File file = new File("/sdcard/" + i + ".txt");
System.out.println(file.delete());
}
System.out.println("文件下载完毕,删除所有下载记录");
Message msg = new Message();
msg.what = DOWN_LOAD_FINSIH;
handler.sendMessage(msg);
}
}
}
}
xml布局文件:<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" >
<EditText
android:id="@+id/et_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:hint="请输入下载路径" >
</EditText>
<ProgressBar
android:id="@+id/pb"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="download"
android:text="下载" />
</LinearLayout>
2.显示意图与隐式意图激活Activity
显示意图要求必须知道被激活组件的包和class
隐式意图只需要知道跳转activity的动作和数据,就可以激活对应的组件
第一步:新建一个继承Activity的类,如:NewActivity
public class NewActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//这里可以使用setContentView(R.layout.xxx)显示某个视图....
}
}
第二步:需要在功能清单AndroidManifest.xml文件中添加进上面Activity配置代码(红色部分):
<activity
android:name="com.itheima.twoactivity.OtherScreenActivity"
android:icon="@drawable/ic_scanner_malware"
android:label="@string/activity02" >
<intent-filter>
<!-- 隐式意图中指定intent的名字 自己定义 可匹配多项 -->
<action android:name="com.itheima.xxx" />
<!-- 指定传想该activity数值的类型 和主机,如果指定该项,就必须在跳转activity的时候传入还数据和主机名 -->
<data
android:mimeType="vnd.android.cursor.item/haha"
android:scheme="itheima" >
</data>
<!-- 隐式intent需要指定的activity的类型,可自己定义该值,需要在调用的时候相对应不写该项默认为 android.intent.category.DEFAULT,可匹配多项 -->
<category android:name="android.intent.category.DEFAULT" >
</category>
</intent-filter>
</activity>
android:name属性值的前面加了一个点表示NewActivity是当前包cn.itcast.action下的类,如果类在应用的当前包下,可以省略点符号,如果类在应用的子包下必须加点,如:NewActivity类在cn.itcast.action.user包下可以这样写:<activity android:name=".user.NewActivity" />
显式意图激活Activity
public void click(View view){
// Intent 意图.
// 显示意图: 必须要指定开启组件的具体信息( 包名, 组件名, 组件的class)
//第一种方法:将要跳转的Activity的字节码传入
Intent intent = new Intent(this, OtherScreenActivity.class);
//第二种方法:将要跳转的Activity的类的路径传入
//intent.setClassName(this, "com.itheima.twoactivity.OtherScreenActivity");//指定包名
startActivity(intent);
}
隐式意图激活Activity
public void click3(View view){
Intent intent = new Intent();
intent.setAction("com.itheima.xxx");
intent.addCategory(Intent.CATEGORY_DEFAULT);
//指定数据的类型.
//intent.setType("vnd.android.cursor.item/haha");
//intent.setData(Uri.parse("itheima:gagaga"));
//如果Type与Data两个都指定必须要用下面的方法,因为单个方法都会clear另一个方法
intent.setDataAndType(Uri.parse("itheima:gagaga"), "vnd.android.cursor.item/haha");
startActivity(intent);
//动作 数据
//打 人 打酱油
//泡 茶 泡 妞
//泡绿茶 泡红茶 泡乌龙茶
//addCategory 附加的信息. 提供一些执行的环境 参数
}
同一个应用程序里面,自己激活自己的东西,推荐使用显示意图,效率高。不同的应用程序里面,激活别人的应用,或者是让自己的某一个界面希望被别人激活,推荐使用隐匿意图。
在不同Activity之间数据传递
通过Intent中的putExtra(String name,xxx value)的方法将数据传出并在另一个Activity中对Intent中的数据进行取出
//在当前Activity中调用putExtra方法
Intent intent = new Intent(this,ResultActivity.class);
intent.putExtra("name", name);
startActivity(intent);
//在另外一个Activity中调用getXxxxExtra("xxx")方法获取数据
Intent intent = getIntent();
String name = intent.getStringExtra("name");
3.Activity生命周期
主要有如下7种状态:
onCreate:Activity在被创建的时候调用
onStart:当Activity界面变成用户可见的时候调用onRestart:Activity没有被销毁并且没有焦点用户不可见的情况下,在跳转到此Activity的时候会调用onRestart方法
onResume:界面获取到焦点的时候被调用
onPause:当界面失去焦点(控件不能得到点击事件)的时候调用
onStop:当Activity界面变成用户不可见的时候调用
onDestroy:Activity销毁的时候调用
详解:
1. 当一个activity启动时,系统会自动依次调用三个方法onCreate->onStart->onResume. 其中当第一次启动activity的时候会调用oncreate方法,执行初始化,设置布局等,onStart在activity能被我们看到的时候调用,onResume在能够获得用户焦点的时候调用
2. 当我们启动第二个activity时,首先第一个activity会调用onPause方法,第二个activity会依次调用三个方法 onCreate->onStart->onResume,最后第一个activity会调用onStop方法。在当前activity不可见的时候,会调用onStop方法,若如对话框风格未完全遮挡当前activity,则只会调用onPause方法,而不会调用onStop方法。
3. 若点击back按钮,则先回调用第二个activity的onPause方法,第一个activity则会依次调用onRestart->->onStart->onResume,注意这里第一个activity不会调用oncreate方法,而是调用onRestart,因为第一个activity并没有被销毁.最后会调用第二个activity的onDestory方法。
4. 如果再次进入第二个activity,则会视作重新启动一个activity。操作如2.
注意: activity什么时候会调用ondestory方法呢?一种情况是在程序里调用了finish方法,另外一个就是当内存不足的时候,系统会自动释放当前不可见的activity。
利用Activity中的onStart方法进行启动联网判断:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
ConnectivityManager cm = (ConnectivityManager) MainActivity.this
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
Toast.makeText(MainActivity.this, "网络可用", 0).show();
} else {
Toast.makeText(MainActivity.this, "网络不可用", 0).show();
// 定向用户到系统网络设置的界面
// com.android.settings/.WirelessSettings
//1.创建对话框的构造器
AlertDialog.Builder builder = new Builder(this);
builder.setTitle("提醒");
builder.setMessage("当前网络不可用,点击确定请设置网络");
builder.setPositiveButton("确定", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 检查用户的网络情况.
Intent intent = null;
if (android.os.Build.VERSION.SDK_INT > 10) {//判断用户SDK版本
intent = new Intent(
android.provider.Settings.ACTION_APN_SETTINGS);
} else {
intent = new Intent();
ComponentName component = new ComponentName(
"com.android.settings",
"com.android.settings.WirelessSettings");
intent.setComponent(component);
intent.setAction("android.intent.action.VIEW");
}
startActivity(intent);//跳转页面
}
});
builder.create().show();
}
super.onStart();
}
}
横竖屏生命周期
当Activity横竖屏切换的时候,当前Activity会被销毁然后重新创建,这样在特点的应用场景使用的时候会很不方便,可以通过配AndroidManifest.xml中对应Activity节点中的configChange的属性:
android:configChange="orientation|keyboardHidden"
orientation代表不关心横竖屏切换
keyboardHidden代表软键盘只点屏幕一半,这样就不会影响Activity的生命周期(经测试在4.0系统下无效)
配置屏幕的朝向:
竖:android:screenOrientation="portrait"
横:android:screenOrientation="landscape"
注:可以在AndroidManifest中指定相关Activity的主题样式:
如android:theme="@android:style/Theme.Dialog"(会显示成对话框的风格)