前言
今天闲来无事就将上个月写的一款小的app介绍一下吧,这款app是用来下载小说,小说来源是无限小说网,原理自然是直接扒掉了人家 的html页面,然后从html源码里面寻找出来小说的下载地址,里面用到了三个开源库,一个是设置状态栏变色的(有bug,不太通用),另一个是下拉刷新的开源库,第三个解析xml的库jsoup(源代码在文末)
app效果图
源码介绍
下面简单介绍一下源码:
- 自定义进度条:
自定义进度条可以参考我前面的代码
我在这个基础上增加了自定义属性,用来设置旋转的进度条图片和文字,使用过程中变成了如下形式:
<cn.karent.downloadtxt.UI.ProgressViewInResult
android:id="@+id/result_progress"
android:layout_centerInParent="true"
android:layout_width="70dp"
android:layout_height="70dp"
wan:bitmap="@drawable/pic_dlg_loading"
wan:text="载"/>
自定义属性需要在values文件夹中创建一个叫做attrs.xml的文件:
<resources>
<declare-styleable name="ProgressBitmap">
<!--显示的是哪个背景图的id-->
<attr name="bitmap" format="reference"/>
<!--要绘制在中心的字符串-->
<attr name="text" format="string"/>
</declare-styleable>
</resources>
这样就可以在上面的使用了,下面搬上最终的进度条代码:
public class ProgressViewInResult extends View {
private Bitmap mBitmap;
private Matrix mMatrix;
private int mCurrent = 0;
private Progress mProgress;
private boolean mContinue = true;
private Paint mTextPaint;
private String mText;
public ProgressViewInResult(Context context) {
super(context);
init();
}
public ProgressViewInResult(Context context, AttributeSet attrs) {
super(context, attrs);
mTextPaint = new Paint();
mTextPaint.setTextSize(ScreenUtil.dp2px(18));
mTextPaint.setColor(Color.rgb(0xa6, 0xa6, 0xa6));
//获取自定义属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ProgressBitmap);
if( typedArray != null) {
int res = typedArray.getResourceId(R.styleable.ProgressBitmap_bitmap, R.drawable.abs__spinner_48_inner_holo);
mBitmap = BitmapFactory.decodeResource(context.getResources(), res);
mText = typedArray.getString(R.styleable.ProgressBitmap_text);
} else {
mBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.abs__spinner_48_inner_holo);
}
init();
}
private void init() {
mMatrix = new Matrix();
mProgress = new Progress();
//执行操作
mProgress.execute();
}
public Bitmap getmBitmap() {
return mBitmap;
}
public void setmBitmap(Bitmap mBitmap) {
this.mBitmap = mBitmap;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mMatrix.setRotate( (mCurrent ++) * 6, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
//旋转之后将图片缩小
mMatrix.postScale(0.7f, 0.7f);
canvas.drawBitmap(mBitmap, mMatrix, null);
//绘制文字
if( mText == null)
return;
Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
float strWidth = mTextPaint.measureText(mText);
float y = mBitmap.getHeight() * 0.7f / 2;
y += (Math.abs(fontMetrics.ascent) - fontMetrics.descent) / 2;
float x = ( mBitmap.getWidth() * 0.7f - strWidth ) / 2;
canvas.drawText(mText, x, y, mTextPaint);
}
public void setContinue(boolean c) {
mContinue = c;
}
class Progress extends AsyncTask<Void, Void, Void> {
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
invalidate();
}
@Override
protected Void doInBackground(Void... params) {
while( mContinue ) {
try {
Thread.sleep(50);
} catch (Exception e) {
e.printStackTrace();
}
publishProgress();
}
return null;
}
}
}
- 下载功能:创建了一个service(service的执行也是在UI线程里面)在后台下载。
DownloadService.java
public class DownloadService extends Service {
private NotificationManager mNotifyManager;
private DownloadBinder mBinder = new DownloadBinder();
private NotificationCompat.Builder mBuilder;
private Integer mLength = 0;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if( msg.what == 0x123) {
int length = (Integer)msg.obj;
mBuilder.setProgress(100, length, false);
mBuilder.setContentText(length + "%");
if( length != 100) {
mBuilder.setContentInfo("正在下载...");
} else {
mBuilder.setContentInfo("下载完成!");
}
mNotifyManager.notify(0, mBuilder.build());
}
}
};
@Override
public void onCreate() {
Log.d("service", "onCreate....");
mNotifyManager = (NotificationManager) getSystemService(Activity.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(this);
mBuilder.setProgress(100,0,false).setSmallIcon(R.drawable.paper_flight).setContentInfo("下载中...").setContentTitle("正在下载");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("service", "onStartCommand.....");
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class DownloadBinder extends Binder {
public void startDownload(final String url,final String name) {
if( url == null) {
Toast.makeText(DownloadService.this, "sorry,下载地址没有找到!", Toast.LENGTH_SHORT).show();
return;
}
new Thread(new Runnable() {
@Override
public void run() {
//开启事件循环,使线程能够显示UI提示
Looper.prepare();
//下载小说
Http.downloadTxt(url, name, new RefreshListenerImpl());
Looper.loop();
}
}).start();
}
}
public class RefreshListenerImpl implements RefreshListener {
@Override
public void refresh(int length) {
Message msg = new Message();
msg.what = 0x123;
msg.obj = length;
mHandler.sendMessage(msg);
}
@Override
public void finish() {
}
}
}
Http.downloadTxt():
public static void downloadTxt(String url, String name, DownloadService.RefreshListenerImpl refresh) {
String path = "/mnt/sdcard/小说下载/";
String filePath = path + name + ".zip";
try {
File directory = new File(path);
if( !directory.exists() )
directory.mkdirs();
File file = new File(filePath);
if( !file.exists())
file.createNewFile();
FileOutputStream fos = new FileOutputStream(filePath);
URL u = new URL(url);
HttpURLConnection conn = (HttpURLConnection) u.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Charset", "UTF-8");
conn.setDoInput(true);
//禁止转发
conn.setInstanceFollowRedirects(false);
conn.connect();
//获取转发后的真正地址
String location = conn.getHeaderField("Location");
//说明下载地址不存在
if( location == null) {
Toast.makeText(MyApplication.getContext(), "小说不存在!", Toast.LENGTH_SHORT).show();
return;
}
//小米手机获取的中文地址是以ISO-8859-1为编码的,需要将其转换为utf8,否则找不到真正的下载地址
// location = new String(location.getBytes("ISO-8859-1"), "utf8");
System.out.println("location:" + location);
String url1 = StringUtil.encodeUrl(location);
url1 += ".zip";
//System.out.println("url1:" + url1);
URL u2 = new URL(url1);
HttpURLConnection conn1 =(HttpURLConnection) u2.openConnection();
InputStream is = conn1.getInputStream();
//获取文件长度
int fileLength = conn1.getContentLength();
byte[] bytes = new byte[1024];
int total = 0;
int length = -1;
int currentProgress = 0;
int downloadPrecent = 0;
while( (length = is.read(bytes)) != -1) {
//System.out.println("bytes:" + bytes);
total += length;
//更新进度
//卡顿解决方案,当进度积累到一定量才更新,不要频繁调用更新
currentProgress = total * 100 / fileLength;
if( currentProgress - downloadPrecent >= 2) {
refresh.refresh(currentProgress);
downloadPrecent = currentProgress;
}
fos.write(bytes, 0, length);
}
is.close();
fos.close();
System.out.println("下载完成...");
} catch (Exception e) {
e.printStackTrace();
}
}
在真正下载小说的时候我获取到了下载地址,点击之后会发现无限小说网的后台重定向了:
如上图,在获取到了访问地址之后返回的状态码是302,而真正的地址就藏在返回头的Location字段里面,所以下载的代码才需要获取Location字段进而进行真正的下载
String location = conn.getHeaderField("Location");
//说明下载地址不存在
if( location == null) {
Toast.makeText(MyApplication.getContext(), "小说不存在!", Toast.LENGTH_SHORT).show();
return;
}
//小米手机获取的中文地址是以ISO-8859-1为编码的,需要将其转换为utf8,否则找不到真正的下载地址
// location = new String(location.getBytes("ISO-8859-1"), "utf8");
System.out.println("location:" + location);
String url1 = StringUtil.encodeUrl(location);
url1 += ".zip";
//System.out.println("url1:" + url1);
URL u2 = new URL(url1);
HttpURLConnection conn1 =(HttpURLConnection) u2.openConnection();
InputStream is = conn1.getInputStream();
获取Location字段还有一个问题,那就是Location字段里面确实是小说的地址,但是却含有中文,小米、vivo手机必须要将其再用ISO-8859-1才能使用,否则就会找不到地址,其他的华为、模拟器等就直接是utf8就行
最后
上面代码可用,但是不要用于商业用途,出了问题概不负责
源代码:点我下载