Android 启动服务配合AsyncTask 使用OKHttp 实现断点下载大文件实例

OkHttpUtils 下载上传文件 android 下载和上传 文件 阅读详情

    前言:最近重读了下“第一行代码”,看到“第一行代码”的一个小项目,特写这篇博客梳理下流程。这个项目实现了使用OKHttp 断点下载大文件,通过服务在下载的过程中暂停和取消并更新通知消息,下面看下效果图:

首先总结一句话,在Android的多线程处理中,尽量做到在子线程中进行耗时操作,在主线程中更新界面UI。好了,下面开始写这个项目。

一。首先创建一个回调接口,用于对下载过程中的各种状态进行监听和回调,代码如下:

public interface DownloadListener {
    // 通知下载进度
    void onProgress(int progress);
    // 通知下载成功
    void onSuccess();
    // 通知下载失败
    void onFailed();
    // 通知下载暂停
    void onPaused();
    //通知下载失败
    void onCanceled();
}

    好吧,这里插一点题外话,回调函数。主要说一下回调函数的机制

    (1)。设置接口,定义回调函数

public interface MyListener {
    void onClick();
}

    (2)。提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。 

public class Realize {
    private MyListener myListener;

    public void setMyListener(MyListener myListener) {
        this.myListener = myListener;
    }

    public void doSth() {
        myListener.onClick();
    }
}

    (3)。当特定的时间或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

public class Test {

    public static void main(String[] args) {
        Realize realize = new Realize();
        realize.setMyListener(new MyListener() {
            @Override
            public void onClick() {
                System.out.println("特定事件或条件发生了");
            }
        });

        realize.doSth();
    }
}

    上面就是一个回调函数的流程,仔细理解下,就能理解回调的实现了,下面回到正题。

二。使用AsyncTask实现下载功能的处理。  

    emmm,这里还要插入一下的AsyncTask的解释.AsyncTask的英文一种轻量级的异步任务类,这个类可以执行后台操作,并在用户界面上发布结果,而不必处理线程和处理程序.OK,先举个例子:

public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {

    /**
     * 刚开始执行的时候调用,可以用于进行一些界面上的初始化操作,比如说显示一个进度条对话框
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();

    }

    /**
     * 这里的代码会在子线程中执行,可以执行耗时操作
     *
     * @param voids
     * @return
     */
    @Override
    protected Boolean doInBackground(Void... voids) {
        //  反馈当前任务的进度,执行完这个方法会调用onProgressUpdate 方法
        publishProgress(50);
        return null;
    }

    /**
     * 根据返回的数据更新UI
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);

    }
    
    /**
     * 后台任务执行完毕通过return语句进行返回时  也可以根据返回的数据更新UI
     *
     * @param aBoolean
     */
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);

    }
}

     简单的来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作。如果想要启动这个任务,只需编写以下代码即可:

                 new DownloadTask()。execute();

    继续回到正题(这样讲着讲着会不会歪楼呀)先在app / build.gradle文件中的依赖中添加OKHttp依赖

        编译'com.squareup.okhttp3:okhttp:3.4.1'

    下面编写下载功能,新建一个DownloadFileTask继承AsyncTask。

//   第一个参数  传给后台参数   第二个 使用整型数据作为进度显示单位   第三个  使用整型数据反馈执行结果
public class DownloadFileTask extends AsyncTask<String, Integer, Integer> {

    // 下载成功
    public static final int TYPE_SUCCESS = 0;
    // 下载失败
    public static final int TYPE_FAILED = 1;
    // 下载暂停
    public static final int TYPE_PAUSED = 2;
    // 下载取消
    public static final int TYPE_CANCELED = 3;
    // 下载状态监听回调
    private DownloadListener listener;
    // 是否取消
    private boolean isCancelled = false;
    // 是否暂停
    private boolean isPaused = false;
    // 当前进度
    private int lastProgress;

    /**
     * 带监听的构造函数
     *
     * @param listener
     */
    public DownloadFileTask(DownloadListener listener) {
        this.listener = listener;
    }

    /**
     * 在后台执行具体的下载逻辑  是在子线程里面 可以执行耗时操作
     */
    @Override
    protected Integer doInBackground(String... strings) {
        // 文件输入流
        InputStream is = null;
        RandomAccessFile accessFile = null;
        File file = null;
        // 记录已下载的文件长度
        long downloadedLength = 0;
        // 获取下载的URL地址
        String downloadUrl = strings[0];
        // 从URL下载地址中截取下载的文件名
        String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
        // 获取SD卡的Download 目录
        String directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
        //  得到要保存的文件
        file = new File(directory + fileName);
        // 如果文件已经存在  获取文件的长度
        if (file.exists()) {
            downloadedLength = file.length();
        }
        try {
            //   获取待下载文件的字节长度
            long contentLength = getContentLength(downloadUrl);
            //  如果待下载文件的字节长度为0 说明待下载文件有问题
            if (contentLength == 0) {
                return TYPE_FAILED;
            } else if (contentLength == downloadedLength) {
                //   已下载字节和文件总字节相等 说明已经下载完了
                return TYPE_SUCCESS;
            }
            //  获取OkHttpClient 对象
            OkHttpClient client = new OkHttpClient();
            //  创建请求
            Request request = new Request.Builder()
                    //  断点下载,指定从哪个字节开始下载
                    .addHeader("RANGE", "bytes=" + downloadedLength + "-")
                    // 设置下载地址
                    .url(downloadUrl)
                    .build();
            //  获取响应
            Response response = client.newCall(request).execute();
            if (response != null) {
                // 读取服务器响应的数据
                is = response.body().byteStream();
                // 获取随机读取文件类  可以随机读取一个文件中指定位置的数据
                accessFile = new RandomAccessFile(file, "rw");
                // 跳过已下载的字节
                accessFile.seek(downloadedLength);
                //指定每次读取文件缓存区的大小为1KB
                byte[] b = new byte[1024];
                int total = 0;
                int len;
                //   每次读取的字节长度
                while ((len = is.read(b)) != -1) {
                    if (isCancelled) {
                        return TYPE_CANCELED;
                    } else if (isPaused) {
                        return TYPE_PAUSED;
                    } else {
                        // 读取的全部字节的长度
                        total += len;
                        // 写入每次读取的字节长度
                        accessFile.write(b, 0, len);
                        // 计算已下载的百分比
                        int progress = (int) ((total + downloadedLength) * 100 / contentLength);
                        // 更新进度条
                        publishProgress(progress);
                    }
                }
                // 关闭连接  返回成功
                response.body().close();
                return TYPE_SUCCESS;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭输入流
                if (is != null) {
                    is.close();
                }
                // 关闭文件
                if (accessFile != null) {
                    accessFile.close();
                }
                Log.d("TAG", "这里永远都会执行 ");
                // 如果是取消的  就删除掉文件
                if (isCancelled && file != null) {
                    file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    /**
     * 获取下载文件的长度
     *
     * @param downloadUrl
     * @return
     * @throws IOException
     */
    private long getContentLength(String downloadUrl) throws IOException {
        // 获取OkHttpClient
        OkHttpClient client = new OkHttpClient();
        // 创建请求
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        //  获取响应
        Response response = client.newCall(request).execute();
        //  如果响应是成功的话
        if (response != null && response.isSuccessful()) {
            // 获取文件的长度  清除响应
            long contentLength = response.body().contentLength();
            response.close();
            return contentLength;
        }
        return 0;
    }

    /**
     * 在界面上更新当前的下载进度
     *
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        int progress = values[0];
        if (progress > lastProgress) {
            listener.onProgress(progress);
            lastProgress = progress;
        }
    }

    /**
     * 用于通知最后的下载结果
     *
     * @param integer
     */
    @Override
    protected void onPostExecute(Integer integer) {
        super.onPostExecute(integer);
        switch (integer) {
            case TYPE_SUCCESS:
                listener.onSuccess();
                break;
            case TYPE_FAILED:
                listener.onFailed();
                break;
            case TYPE_PAUSED:
                listener.onPaused();
                break;
            case TYPE_CANCELED:
                listener.onCanceled();
                break;
        }
    }

    /**
     * 暂停下载
     */
    public void pauseDownload() {
        isPaused = true;
    }

    /**
     * 取消下载
     */
    public void cancelDownload() {
        isCancelled = true;
    }
}

 

三。创建一个下载的服务 

 

    没错,还得简单的介绍下服务(这么墨迹的吗)。一般我们都调用startService()方法来启动服务,并调用stopService()方法来停止这个服务,但这样启动服务后,活动无法干预到服务到底执行了怎样的逻辑。这时候就要用onBind()方法了,对服务进行绑定之后,就可以调用服务里的Binder提供的方法了.OK,下面举个个子子。首先创建一个MyService,代码如下:

public class MyService extends Service {

    private static final String TAG = "MyService";

    private DownloadBinder mBinder = new DownloadBinder();

    // 创建 DownloadBinder 实例  随便定义了两个方法
    class DownloadBinder extends Binder {

        public void startDownload() {
            Log.d(TAG, "startDownload: executed");
        }

        public int getProgress() {
            Log.d(TAG, "getProgress: executed");
            return 0;
        }
    }

    /**
     * 返回这个DownloadBinder 实例
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    
}

    然后在活动中绑定服务,实现活动去指挥服务去干什么。

public class FirstActivity extends AppCompatActivity {

    private Button btnBind;
    private Button btnUnBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);

        btnBind = (Button) findViewById(R.id.btnBind);
        btnUnBind = (Button) findViewById(R.id.btnUnBind);

        btnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 绑定服务
                Intent intent = new Intent(FirstActivity.this, MyService.class);
                bindService(intent, connection, BIND_AUTO_CREATE);
            }
        });

        btnUnBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //  解绑服务
                unbindService(connection);
            }
        });

    }

    private MyService.DownloadBinder downloadBinder;

    ServiceConnection connection = new ServiceConnection() {
        /**
         * 活动与服务绑定成功后
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 向下转型获得DownloadBinder 实例 就能调用DownloadBinder的方法 进而控制服务的逻辑
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }

        /**
         * 活动与服务解绑后
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}

    好的,了解到活动如何去控制服务以后,下面正式写下载的服务,代码如下:    

public class DownloadService extends Service {
    // 下载的异步操作类
    private DownloadFileTask downloadFileTask;
    // 下载地址
    private String downloadUrl;

    private static final String TAG = "DownloadService";
    // 下载状态的回调
    private DownloadListener listener = new DownloadListener() {
        /**
         *  更新下载进度状态
         * @param progress
         */
        @Override
        public void onProgress(int progress) {
            Log.d(TAG, "onProgress: -------" + progress);
            getNotificationManager().notify(1, getNotification("Downloading", progress));
        }

        /**
         * 下载成功
         */
        @Override
        public void onSuccess() {
            Log.d(TAG, "onSuccess: -------------");
            downloadFileTask = null;
            //下载成功时将前台服务通知关闭,并创建一个下载成功的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Success", -1));
            Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载失败
         */
        @Override
        public void onFailed() {
            Log.d(TAG, "onFailed: -------------");
            downloadFileTask = null;
            // 下载失败时将前台服务通知关闭,并创建一个下载失败的通知
            stopForeground(true);
            getNotificationManager().notify(1, getNotification("Download Failed", -1));
            Toast.makeText(DownloadService.this, "Download Failed", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载暂停
         */
        @Override
        public void onPaused() {
            Log.d(TAG, "onPaused: -------------");
            downloadFileTask = null;
            Toast.makeText(DownloadService.this, "Download Paused", Toast.LENGTH_SHORT).show();
        }

        /**
         * 下载取消
         */
        @Override
        public void onCanceled() {
            Log.d(TAG, "onCanceled: -------------");
            downloadFileTask = null;
            stopForeground(true);
            Toast.makeText(DownloadService.this, "Download Canceled", Toast.LENGTH_SHORT).show();
        }
    };

    DownloadBinder mBinder = new DownloadBinder();

    /**
     * 返回这个DownloadBinder 实例
     *
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    // 创建 DownloadBinder 实例 
    class DownloadBinder extends Binder {
        // 开始下载
        public void startDownload(String url) {
            Log.d(TAG, "startDownload--------: 开始下载");
            if (downloadFileTask == null) {
                downloadUrl = url;
                downloadFileTask = new DownloadFileTask(listener);
                downloadFileTask.execute(downloadUrl);
                startForeground(1, getNotification("DownLoading", 0));
                Toast.makeText(DownloadService.this, "Downloading", Toast.LENGTH_SHORT).show();
            }
        }

        // 暂停下载
        public void pauseDownload() {
            Log.d(TAG, "pauseDownload--------: 暂停下载");
            if (downloadFileTask != null) {
                downloadFileTask.pauseDownload();
            }
        }

        // 取消下载
        public void cancelDownload() {
            Log.d(TAG, "cancelDownload--------: 取消下载---" + downloadFileTask);
            if (downloadFileTask != null) {
                downloadFileTask.cancelDownload();
            } else {
                if (downloadUrl != null) {
                    // 先暂停后取消   取消下载时需将文件删除,并通知关闭
                    String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
                    String directory = Environment.getExternalStoragePublicDirectory
                            (Environment.DIRECTORY_DOWNLOADS).getPath();
                    File file = new File(directory + fileName);
                    if (file.exists()) {
                        file.delete();
                    }
                    getNotificationManager().cancel(1);
                    stopForeground(true);
                    Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

    /**
     * 获取通知栏管理器
     *
     * @return
     */
    public NotificationManager getNotificationManager() {
        return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    }

    /**
     * 设置通知栏的样式 并获取通知栏的实例
     *
     * @param title
     * @param progress
     * @return
     */
    private Notification getNotification(String title, int progress) {
        Intent[] intents = new Intent[]{(new Intent(this, DownloadActivity.class))};
        PendingIntent pi = PendingIntent.getActivities(this, 0, intents, 0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
        builder.setContentIntent(pi);
        builder.setContentTitle(title);
        if (progress > 0) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, progress, false);
        }
        return builder.build();
    }
    
}

四,接下来在活动中绑定这个服务就可以了。

    1.先实现下界面,修改xml中的代码,创建三个按钮。启动暂停取消

<?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">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:text="Start Download" />

    <Button
        android:id="@+id/btnPause"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:text="Pause Download" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:text="Cancel Download" />
</LinearLayout>

    2.在活动中绑定服务,让活动与服务进行通信,因为要对文件下载,还要动态的申请写文件的权限,代码如下:

public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {
    // 开始下载按钮
    private Button btnStart;
    // 暂停下载按钮
    private Button btnPause;
    // 取消下载按钮
    private Button btnCancel;
    // 服务
    private Intent intent;
    // 下载操作的实例
    private DownloadService.DownloadBinder downloadBinder;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * 活动与服务绑定成功后
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (DownloadService.DownloadBinder) service;
        }

        /**
         * 活动与服务解绑后
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        btnStart = (Button) findViewById(R.id.btnStart);
        btnPause = (Button) findViewById(R.id.btnPause);
        btnCancel = (Button) findViewById(R.id.btnCancel);

        btnStart.setOnClickListener(this);
        btnPause.setOnClickListener(this);
        btnCancel.setOnClickListener(this);
        // 启动服务并绑定
        intent = new Intent(this, DownloadService.class);
        startService(intent);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);
        // 获取写的权限
        if (ContextCompat.checkSelfPermission(DownloadActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(DownloadActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
        }
    }

    @Override
    public void onClick(View v) {
        if (downloadBinder == null) {
            return;
        }
        switch (v.getId()) {
            case R.id.btnStart:
                String url = "https://raw.githubusercontent.com/guolindev/eclipse/master/eclipse-inst-win64.exe";
                downloadBinder.startDownload(url);
                break;
            case R.id.btnPause:
                downloadBinder.pauseDownload();
                break;
            case R.id.btnCancel:
                downloadBinder.cancelDownload();
                break;
        }
    }

    /**
     * 申请权限的返回结果
     *
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
        }
    }

    /**
     * 结束活动的时候关闭服务
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
        stopService(intent);
    }
}

最后在AndroidManifest.xml中文件中声明使用的权限,还有别忘了服务也是需要声明的。

      这个项目的总结就到这里就结束了......,最后附上源代码 源码下载地址

 

 

OKHttp实现大文件的断点续传 本文的亮点: (1)网络请求用OKHttp进行下载大文件 (2)实现了大文件的断点续传 (3)取消下载时,删除已经下载的文件。 实现效果图:          直接给出工程: (1)定义一个接口:DownloadListener.java package com.example.servicebestpractice; /** * Created by Administra 阅读详情

相关推荐

AsyncTask原理与实战:Android异步编程入门黄金跳板

AsyncTask是Android早期提供的轻量级异步任务框架,其核心基于Handler+Looper主线程通信机制与ThreadPoolExecutor后台线程调度,本质是UI线程安全模型的具象化实现。它通过onPreExecute、doInBackground、onPostExecute三段式生命周期,强制分离主线程UI操作与后台耗时任务,成为理解Android线程模型、内存泄漏防范、生命周期感知编程的基础范本。尽管已在API 30被标记为@Deprecated,但其封装逻辑直指协程、WorkManag

an4455的博客 592

OkHttp大文件下载

系统自带的DownloadManager下载 OkHttp下载

SKY_L的博客 1万+

安卓原生开发为何是初创企业的生存基础设施

安卓原生开发是移动应用开发的基础技术路径,指基于Android SDK、直接调用系统API构建高性能、高可控性客户端的能力。其核心原理在于绕过Web容器或跨端中间层,实现对硬件传感器、后台服务、离线存储及厂商推送等底层能力的稳定访问。相比H5、小程序或跨平台方案,原生开发在弱网适配、消息送达率、启动性能和隐私合规等方面具备不可替代的技术价值。典型应用场景包括社区团购实时订单同步、智能硬件蓝牙联动、车载Android Auto集成、金融类App安全保活等强交互、低延迟、高可靠性需求领域。对于资源有限的初创企业

weixin_30776545的博客 451

Android上传下载文件进度监听,大文件(500M以内)上传

1.普通文件的上传下载采用的是retrofit+rxjava的形式; 1.1 依赖 implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0' implementation 'com.squareup.retrofit2:converter-gson:2.4.0' implementation 'i.

mulanlong的博客 1730

Retrofit实战指南:Android网络请求从入门到生产级落地

Retrofit是Android开发中主流的类型安全HTTP客户端,其本质是构建在OkHttp之上的声明式网络抽象层,通过注解将Java接口映射为HTTP请求,并借助Gson等Converter完成JSON与对象的自动序列化。它解决了传统AsyncTask+HttpURLConnection模式下手动拼URL、解析JSON、线程切换易错等工程痛点,显著提升网络层可维护性与稳定性。在电商、金融、IoT等真实业务场景中,Retrofit需配合OkHttp连接池、超时控制、拦截器链及生命周期管理才能发挥最大价值。

快马扬鞭须努力! 755

从零构建安卓论坛App:MVVM架构、协程与Room数据库实战

在移动应用开发领域,构建一个功能完整、体验流畅的客户端应用是核心技能。其关键在于采用合理的架构模式实现关注点分离,并高效处理数据流与异步任务。MVVM(Model-View-ViewModel)架构是当前Android官方推荐的主流模式,它通过ViewModel管理UI相关数据,并利用LiveData或StateFlow实现数据驱动UI更新,极大地提升了代码的可测试性与可维护性。在实际工程中,网络请求、本地数据持久化与复杂的UI交互是常见挑战。通过集成Retrofit和OkHttp可以构建稳健的网络层,而R

weixin_30653023的博客 441

Android 本地更新APK(无需添加运行时权限)

Android本地更新APK(不需要获取系统的存储权限)

woai6116110的博客 1046

Retrofit在Android开发中的核心价值与生产级实践

Retrofit是Android应用中实现声明式HTTP通信的关键网络框架,其本质是将REST API调用抽象为Kotlin/Java接口,通过注解驱动URL、参数与响应处理,从而解耦网络细节与业务逻辑。它基于OkHttp构建,但屏蔽了线程切换、连接管理、JSON序列化等底层复杂性,提供类型安全、可测试、易维护的网络层基础。技术价值体现在架构分层(Repository模式支撑)、协程原生集成(suspend函数)、统一错误处理与拦截器扩展能力。典型应用场景包括电商订单请求、金融Token自动刷新、多环境Ba

com_xpp的专栏 441

【愚公系列】《移动端AI应用开发》021-Android端DeepSeek集成实战(Android端 DeepSeek SDK配置与初化)

【摘要】本文深入探讨了在Android端集成DeepSeek大模型的关键技术,重点介绍了SDK配置与初始化、API密钥管理及权限控制。通过Gradle依赖配置实现SDK集成,详细展示了OkHttp网络请求中API密钥的安全传递方式,并提供了异步任务处理API响应的完整代码示例。文章覆盖了从环境搭建到核心功能实现的完整流程,为开发者提供了在Android应用中高效调用DeepSeek AI能力的实践指导,同时强调了安全性和性能优化的重要性。

愚公智库 1万+

拼多多Android面试题及参考答案

Android 系统采用分层架构设计,从底层到上层依次为Linux 内核层硬件抽象层(HAL)系统运行时库层应用框架层应用层,共五层。位置:Linux 内核层与系统运行时库层之间,是“硬件与系统的隔离层”。核心作用:屏蔽硬件差异,为上层提供统一的硬件访问接口,解决 Android 开源与硬件厂商闭源的矛盾,具体功能如下:硬件能力抽象:将硬件的具体实现(如不同厂商的摄像头芯片、音频芯片)封装为标准化接口(如Camera HALAudio HAL。

大模型大数据攻城狮的专栏 1279

安卓原生影视App对接苹果CMS全链路实战

影视类Android App开发涉及前端播放体验、后端内容管理与API对接等核心环节。苹果CMS作为主流PHP影视CMS系统,其RESTful接口需通过OkHttp/Retrofit进行标准化调用,配合ExoPlayer实现硬解播放、防盗链处理与离线缓存。本文围绕安卓原生开发(非WebView套壳)展开,解析苹果CMS API逆向工程、双Token登录鉴权、播放器深度定制及真机兼容性适配等关键技术,覆盖从HTTP通信、权限控制到灰度发布的完整交付链路,适用于具备Android中级开发能力、熟悉HTTP与PH

weixin_34178244的博客 418

Android开发必备开源库解析与最佳实践

在Android应用开发中,开源库作为基础架构的重要组成部分,极大提升了开发效率和应用性能。从网络通信的Retrofit+OkHttp组合,到异步处理的Kotlin协程,再到图片加载的Glide,这些经过验证的解决方案都遵循单一职责原则,并提供了灵活的扩展接口。开源库的核心价值在于避免重复造轮子,使开发者能够专注于业务逻辑实现。在UI层,Jetpack Compose带来了声明式编程的新范式;在性能优化方面,LeakCanary等工具帮助开发者快速定位内存问题。合理选择和维护活跃的开源库,是构建高质量And

weixin_34194087的博客 488

okHttp+AsyncTask封装请求框架不需手动解析

已集成好极光推送功能,还有观察者模式对Activity页面进行通知并刷新,框架外多封装了一层,具有可扩展性,便于今后框架更换等.

Android面试速查手册:Android常用开源框架原理

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。Reference引用的对象被回收时,Reference 对象将被添加到 ReferenceQueue中,前提是构造 Reference 时,参数中有 ReferenceQueue。如果 obj 对象被回收了,那么 queue 将添加 r,那么我们可以查找队列,如果有r,则证明 obj 对象被回收了,监控完成。使用发布/订阅事件总线框架,基于观察者模式,将事件的接收者和发送者分开,简化了组件之间的通信,使用简单、效率高、体积小。

2401_85112897的博客 1008

QFN封装芯片手工焊接实战:从焊盘处理到拖焊技巧

本文详细介绍了QFN封装芯片手工焊接的全过程,从焊盘处理到拖焊技巧,再到常见问题的现场修复方法。通过实战经验分享,帮助工程师和DIY爱好者掌握0.5mm小间距QFN芯片的焊接技术,提升焊接成功率和质量。重点讲解了垂直与平行拖焊手法的对比应用,以及焊后清理与质量检验的关键步骤。

weixin_42652674的博客 532

别再只做游戏了!用虚幻引擎5(UE5)给你的工厂车间做个数字双胞胎(附数据对接教程)

本文详细介绍了如何利用虚幻引擎5(UE5)构建工业数字孪生系统,实现工厂车间的智能可视化。通过UE5的高质量渲染和实时数据处理能力,结合OPC UA和MQTT协议,打造高效的数据对接方案,提升异常识别和设备管理效率。文章还分享了模型优化、可视化设计及典型工业场景的实战案例,助力企业迈向工业4.0。

weixin_33738555的博客 206

小米2019秋招安卓B卷复盘:核心考点与原理深度解析

Android开发面试中,Activity启动模式、Handler消息机制、自定义View绘制流程始终是高频考点,其背后涉及的组件生命周期、内存优化与UI渲染原理,直接决定了应用的质量与稳定性。理解Handler内存泄漏需要从消息队列引用链与弱引用机制入手,而自定义View的MeasureSpec处理不当则会导致wrap_content失效。与此同时,随着设备形态多样化,多屏适配、安卓Launcher开发等场景成为新的工程挑战,要求开发者具备更全面的知识体系。以小米2019秋招安卓笔试题B卷为线索,系统梳理

weixin_34214500的博客 447

关于光耦隔离与SP3485E芯片一起使用无法拉高485使能引脚问题

最近在做的一个项目,定制了一块STM32F407ZG的工控板,工控板载4个485接口全部都是只能接收数据,不能对外发送数据。1.接线不正确:接线不正确导致485收发出现问题。 2.程序端口初始化问题:控制光耦隔离的输入脚IO的初始化和置位状态有问题。 3.GPIO复用问题:控制对应串口的GPIO不是正点原子默认的GPIO,而是经过复用的其他IO,复用问题。 4.程序设置与485E芯片收发模式不对应:对于使能引脚的电平要根据光耦的输出情况确定输入IO与RE的关系,可能模式没有对应。 5.光耦隔离或者SP485

weixin_45862642的博客 2977

安卓面试题总结

Android 的四大组件分别是:Activity、Service、Broadcast Receiver 和 Content Provider. 1)Activity:这是你与用户进行交互的入口。每一个 Activity 都是一个单独的界面,负责显示 UI和响应用户操作。 2)Service:Service 是运行在后台且不直接与用户交互的组件。适用于需要在后台执行长时间运行操作的任务,类似播放音乐或下载文件等。 3)Broadcast Receiver:用于

my_power520的博客 586

嵌入式面经篇八——进程&线程

记录一些招聘公司在招聘嵌入式软件岗位时的一些问题,此文为第八篇。进程上文:是指进程由用户态切换到内核态时需要保存用户态时CPU寄存器中的值,进程状态以及堆栈上的内容。即保存当前进程的状态,以便再次执行该进程时,能够恢复切换时的状态,继续执行。进程下文:是指切换到内核态后执行的程序,即进程运行在内核空间的部分。中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。

明天你好的博客 1854

华为OD机考双机位C卷 - 简单的解压缩算法 (Java & Python & JS & GO & C++ & C)

华为OD机考双机位C卷 - 简单的解压缩算法 , 提供Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。

算法大师! 128

Android+PHP+MySQL全链路登录认证实战指南

用户认证是移动应用最基础也最复杂的系统工程,其本质是跨端协同的身份可信验证过程。它涉及客户端生命周期管理、网络状态感知与异常恢复、服务端密码安全存储与防暴力破解、数据库索引优化与碎片治理等多层技术原理。在真实场景中,弱网重连、并发冲突、SQL注入、会话劫持、字符集错配等问题频发,仅靠‘写个接口+调个API’无法保障稳定可用。本文聚焦 Android 登录注册与 PHP MySQL 后端的深度协同,覆盖 Retrofit 协程绑定、bcrypt 密码哈希、utf8mb4 字符集配置、覆盖索引设计等关键实践,为

weixin_34112181的博客 365

嵌入式代码审计中常见的漏洞(四)

经过漫长时间的迭代更新,内容从一开始的碎片化网罗搜集,到排版与整合划分,再到学习与实践验证,最后摒弃初始,用自己的语言与实践结论通俗的将它展现出来。强化知识技能的同时也为读者带来更好的阅读体验。 本篇介绍了以下代码安全问题: 编号 漏洞 1 敏感信息硬编码 2 未初始化安全 3 越界访问 4 迭代器失效 5 迭代器尾部解引用

武天旭的博客 3315

2017年Android校招笔试B卷核心考点解析与备考实战经验

在Android开发者的成长路径中,校招笔试往往是一块重要的试金石,它不仅检验Java基础与Android核心机制的掌握程度,更看重解决实际工程问题的能力。从内存管理、Handler消息机制到自定义View与事件分发,这些技术概念构成了Android应用性能与稳定性的基石。理解其底层原理,如GC Roots可达性分析、Looper循环与epoll唤醒机制,能帮助开发者有效规避内存泄漏与UI卡顿。在实际开发中,无论是网络框架选型、图片压缩优化,还是复杂页面的流畅度保障,都离不开对这些基础能力的灵活运用。本文结

weixin_30684743的博客 433

Kafka 性能优化与常见问题排查

Kafka 的高性能依赖于合理的集群设计、参数优化与高效的监控体系。生产环境中,应重点关注I/O 瓶颈、分区规划、Producer/Consumer 参数调优,并通过监控与排查手段快速定位问题,从而保障 Kafka 在实时场景下的稳定运行。

X2829352498的博客 234

31、MSP430FR2355时钟系统与低功耗模式解析

本文详细解析了MSP430FR2355的时钟系统和低功耗模式。首先介绍了时钟系统的组成,包括XT1振荡器、DCO、MODCLK、MCLK、SMCLK和ACLK的功能与配置方法。接着探讨了MSP430FR2355的五种低功耗模式及其配置要点,并分析了时钟系统与低功耗模式之间的协同关系。最后通过实际应用案例展示了如何在具体场景中优化系统功耗。文章旨在帮助开发者充分利用MSP430FR2355的时钟与低功耗特性,实现高性能、低功耗的嵌入式系统设计。

mars5的博客 195
上一篇: Android ActionBar 转Toolbar 出现的一些问题总结
下一篇: 增强版的ListView——RecyclerView的使用
谦行
博客等级 码龄11年 182粉丝 35原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值