说说封装sdk的那些坑(上)

本文分享了作者在接手一个半成品SDK后进行的一系列优化工作,包括重构显示新版本信息的DialogFragment、启动Service、使用DownloadTask进行多线程下载等。详细介绍了通过Messenger进行进程间通信的方式替代广播来提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这半个月一直在做一件事,升级sdk的修改、打包、集成,移交给我的sdk源码是个半成品,效率低,逻辑混乱,好多代码是直接复制粘贴,连空格都没对齐。强迫症犯了,拿过来直接重新改造。

对SDK的改造

首先是一个显示新版本信息的DialogFragment,点击升级会启动一个service,service会启动一个downloadtask类,里面有一个成员变量List《DownloadThread》(英文尖括号里内容无法显示),DownloadThread继承了Thread,根据传过来的下载文件信息、线程数量来创建每个下载线程的信息,如第一个线程从0字节到总大小/线程数量,并且要把每个线程信息保存到数据库;初始化完成后,downloadtask开始下载,将保存的线程依次取出并运行,每下载一定长度,downloadtask有个回调把下载的状态和文件大小传回给service,然后service再返回给DialogFragment,DialogFragment更新下载进度条。

这里写图片描述
类图不完整,只包含了主要的函数和成员。下面是这三个类的关系图:
这里写图片描述
DialogFragment和service通过messager传递消息,点击下载后,dialogfragment调用startDownLoadByBindService(),以bindservice方式启动downloadservice,返回IBindder对象用于构造mSender:

        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected...");
            ........

            mSender = new Messenger(service);
            Message message = Message.obtain();
            message.what = Constant.DOWNLOAD_INIT;
            message.replyTo = mReceiver;
            long fileSizelong = Long.parseLong(fileSize);
            fileInfo = new FileInfo(0, apkDownloadURL,
                    apkName, fileSizelong, 0);
            Bundle bundle = new Bundle();
            bundle.putParcelable(Constant.FILE_INFO, fileInfo);
            message.setData(bundle);
            try {
                mSender.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

mReceiver是DialogFragment成员变量,接收来自service的消息:

private Messenger mReceiver = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {

            Bundle bundle = msg.getData();
            switch (msg.what){
                case Constant.UPDATE_PROGRESSBAR:
                    ......
                    break;

                case Constant.DOWNLOAD_FINISHED:
                    ......
                    break;

                case Constant.DOWNLOAD_EXCEPTION:
                    ......
                    break;

            }
            super.handleMessage(msg);

        }
    });

mSender把要下载的文件信息传给service(FileInfo不是一般数据类型,用messager传递需要实现Parcelable接口,就是要实现writeToParcel()、describeContents()并包含一个Parcelable.Creator的方法)。
再看看DownloadService中对消息的处理:

    private Messenger mMessenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Log.i(TAG, "get message,msg is " + msg.what);
            mSender = msg.replyTo;
            Bundle bundle = msg.getData();
            mFileInfo = bundle.getParcelable(Constant.FILE_INFO);
            switch (msg.what){
                case Constant.DOWNLOAD_INIT:
                    startDownLoad();
                    break;

                case Constant.DOWNLOAD_STOP:
                    //按下暂停
                    if (mFileInfo != null){
                        DownLoadTask task = hashMap.get(mFileInfo.getFileUrl());
                        task.isPause = true;
                    }
                    break;

                case Constant.DOWNLOAD_START:
                    startDownLoad();
                    break;

                case Constant.NETWORK_EXCEPTION:
                    if (mFileInfo != null){
                        DownLoadTask task = hashMap.get(mFileInfo.getFileUrl());
                        task.isPause = true;
                    }
                    break;
            }
        }
    });

Constant.DOWNLOAD_INIT表示刚开始下载,Constant.DOWNLOAD_START表示下载暂停后又点击了继续,这两种情况都是调用了startDownLoad();其余两种情况是用户点击暂停下载或网络出现异常都是将暂停的标志位置为true,标志位控制了一个while循环,当它变成true会退出线程。下面看看startDownLoad():

    private void startDownLoad(){
        Log.i(TAG, "startDownLoad...");
        DownLoadTask task = new DownLoadTask(DownLoadService.this,mFileInfo,Constant.DOWNLOAD_THREAD_COUNT);
        hashMap.put(mFileInfo.getFileUrl(),task);
        task.downLoad(new IDownloadCB() {
            @Override
            public void downloadStatus(int event, Object object) {
                Message message = Message.obtain();
                Bundle bundle = new Bundle();
                switch (event){

                    case Constant.UPDATE_PROGRESSBAR:
                        message.what = Constant.UPDATE_PROGRESSBAR;
                        bundle.putInt("progress", Integer.parseInt(object.toString()));
                        message.setData(bundle);
                        break;

                    case Constant.DOWNLOAD_FINISHED:
                        Log.i(TAG, "downloadStatus DOWNLOAD_FINISHED");
                        message.what = Constant.DOWNLOAD_FINISHED;
                        bundle.putParcelable(Constant.FILE_INFO, mFileInfo);
                        message.setData(bundle);
                        break;

                    case Constant.DOWNLOAD_EXCEPTION:
                        Log.i(TAG, "downLoad callback:Constant.DOWNLOAD_EXCEPTION");
                        message.what = Constant.DOWNLOAD_EXCEPTION;
                        bundle.putString("exception", object.toString());
                        message.setData(bundle);
                        break;
                }

                if (mSender != null){
                    try {
                        mSender.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        task.isPause = false;
    }

传入文件信息、线程数量创建一个DownLoadTask然后调用task.downLoad并将标志位isPause置为false,开启下载线程,回调从downloadtask传回来状态,downloadservice做一个封装通过mSender传回给DialogFragment更新UI显示。

最后来看看每个线程在下载时的回调:

if (connection.getResponseCode() == 206)
                {
                    // 读取数据
                    inputStream = connection.getInputStream();
                    byte buf[] = new byte[1024];
                    int len = -1;
                    long time = System.currentTimeMillis();
                    while ((len = inputStream.read(buf)) != -1)
                    {
                        //Log.i(TAG, "threadId = " + mThreadInfo.getThreadId() +";len="+len);
                        // 写入文件
                        raf.write(buf, 0, len);
                        // 累加整个文件完成进度
                       add(len);
                        // 累加每个线程完成的进度
                        Log.d(TAG, "len = " + len +";mFinised="+mFinised );
                        mThreadInfo.setThreadHasFinished(mThreadInfo.getThreadHasFinished() + len);
                        if (System.currentTimeMillis() - time > 300)
                        {
                            time = System.currentTimeMillis();
                            long f = (1L*mFinised * 100) /(1L* mFileInfo.getFileLength());
//                            Log.i(TAG, "threadId = " + mThreadInfo.getThreadId() +";mFinised="+mFinised +"文件下载进度="+f+"%");
                            iDownloadCB.downloadStatus(Constant.UPDATE_PROGRESSBAR, f);

                        }

                        // 在下载暂停时,保存下载进度
                        if (isPause)
                        {
                            Log.i(TAG, "run: ispasuse  true " );
                            mDao.updateThread(mThreadInfo.getThreadUrl(),
                                    mThreadInfo.getThreadId(),
                                    mThreadInfo.getThreadHasFinished());
//                            iDownloadCB.downloadStatus(Constant.DOWNLOAD_STOP, "下载终止");
                            return;
                        }
                    }
                    Log.i(TAG, "threadId = "+mThreadInfo.getThreadId()+" isFinished");
                    // 标识线程执行完毕
                    isFinished = true;
                    checkAllThreadFinished();
                }

if(System.currentTimeMillis() - time > 300)即每隔300毫秒把下载大小传给service,service再传给DialogFragment实现了进度条的更新。

刚接手时,downloadtask把下载进度直接用广播发出来,DialogFragment接收广播更新UI,记得网上有人说过,广播发送消息效率很低,而且这么频繁的发送下载进度广播感觉很奇怪,加上最近研究了下进程间通信,除了aidl,还可以用messager,不如实践下,最后做出了上面的实现方式。

修改好,下面就是打包和集成sdk了,在下一篇文章里说。

使用C++代码封装的win32操作类, 与MFC相似,对于学习SDK与C++是巨好的参考 Tutorials Menu of tutorials Tutorial 1: The Simplest Window Tutorial 2: Using Classes and Inheritance Tutorial 3: Using Messages to Create a Scribble Window Tutorial 4: Repainting the Window Tutorial 5: Wrapping a Frame around our Scribble Window Tutorial 6: Customising Window Creation Tutorial 7: Customising the Toolbar Tutorial 8: Loading and Saving Files Tutorial 9: Printing Tutorial 10: Finishing Touches Tutorial 1: The Simplest Window The following code uses Win32++ to create a window. This is all the code you need (in combination with Win32++) to create and display a simple window. Note that in order to add the Win32++ code to our program, we use an #include statement as shown below. #include "../Win32++/Wincore.h" INT WINAPI WinMain(HINSTANCE, HINSTANCE, LPTSTR, int) { //Start Win32++ CWinApp MyApp; //Create a CWnd object CWnd MyWindow; //Create (and display) the window MyWindow.Create(); //Run the application return MyApp.Run(); } This program has four key steps: Start Win32++. We do this here by creating a CWinApp object called MyApp. Create a CWnd object called MyWindow. Create a default window by calling the Create function. Start the message loop, by calling the Run function. If you compile and run this program, you'll find that the application doesn't end when the window is closed. This is behaviour is normal. An illustration of how to use messages to control the windows behaviour (including closing the application) will be left until tutorial 3.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值