[android开发]小说下载软件

前言

今天闲来无事就将上个月写的一款小的app介绍一下吧,这款app是用来下载小说,小说来源是无限小说网,原理自然是直接扒掉了人家 的html页面,然后从html源码里面寻找出来小说的下载地址,里面用到了三个开源库,一个是设置状态栏变色的(有bug,不太通用),另一个是下拉刷新的开源库,第三个解析xml的库jsoup(源代码在文末

app效果图

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

源码介绍

下面简单介绍一下源码:

  • 自定义进度条:
    自定义进度条可以参考我前面的代码

http://blog.youkuaiyun.com/supervictim/article/details/53907393

我在这个基础上增加了自定义属性,用来设置旋转的进度条图片和文字,使用过程中变成了如下形式:

 <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就行

最后

上面代码可用,但是不要用于商业用途,出了问题概不负责
源代码:点我下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值