Android 网络链接稳定性测试解决方案

简介: Android 网络链接稳定性测试解决方案

Android开发中,我们经常需要通过网络请求来获取数据或执行某些操作。但是,网络连接并不总是稳定的,有时候会出现延迟、丢包、断开等问题,这些问题会影响用户的体验和满意度。因此,我们需要对网络连接的稳定性进行测试,找出可能存在的问题,并采取相应的优化措施。

为了测试Android应用在不同网络环境下的表现,我们可以使用一些工具或方法来模拟网络的变化,比如使用[Network Link Conditioner]或[Android Emulator]等。但是,这些工具或方法都有一些局限性,比如不能覆盖所有的网络类型和场景,或者不能实时反映网络状态的变化。

因此,需要一种更灵活和全面的解决方案,来测试Android应用在真实网络环境下的稳定性。本文将介绍一种基于后台服务和定时器的解决方案,它可以在后台持续地发送HTTP请求到一些可靠的服务器,并记录响应的时间和状态码,从而评估网络连接的稳定性。同时,它还可以将测试结果保存到本地文件,并定期上传到远程服务器,方便我们进行分析和比较。

我开始本来想使用tomcat搭建的,但后来发现缺少后台,后面就考虑用scp技术,直接把结果push到服务器。

目标

目标是设计一个Android网络连接稳定性测试解决方案,具体要求如下:

  • 可以对不同类型的网络(如WIFI、4G、以太网等)进行测试
  • 可以对不同的服务器(如百度、必硬等)进行测试
  • 可以记录每次请求的时间、状态码、时间戳等信息
  • 可以将测试结果保存到本地文件,并定期上传到远程服务器
  • 可以统计测试结果的总次数、成功次数、失败次数、成功率等指标

实现

为了实现这个目标,需要使用以下几个组件:

  • NetworkMonitorService:一个后台服务,用来发送HTTP请求,并记录响应的时间和状态码
  • FileUploadService:一个后台服务,用来将测试结果保存到本地文件,并上传到远程服务器
  • NetworkMonitorReceiver:一个广播接收器,用来启动NetworkMonitorService
  • FileUploadReceiver:一个广播接收器,用来启动FileUploadService
  • DatabaseHelper:一个数据库操作类,用来插入和查询测试结果
  • NetworkHelper:一个网络操作类,用来获取当前的网络类型和IP地址

下面我们分别介绍这些组件的具体实现。

NetworkMonitorService

NetworkMonitorService是一个继承自JobIntentService的后台服务,它可以在后台线程上处理传入的工作请求。重写了它的onHandleWork方法,在这个方法中,做了以下几件事:

  • 获取一个OkHttpClient实例,用来发送HTTP请求
  • 获取一个DatabaseHelper实例,用来操作本地数据库
  • 获取当前的网络类型和IP地址
  • 遍历可靠的服务器的URL,依次发送HTTP请求,并记录响应的时间和状态码
  • 插入一条数据到数据库,并检查数据量

代码如下:

package com.xxx.factorytest.service;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
import com.xxx.factorytest.utils.DatabaseHelper;
import com.xxx.factorytest.utils.NetworkHelper;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class NetworkMonitorService extends JobIntentService {
    // 定义一个工作ID,用来唯一标识这个服务
    private static final int JOB_ID = 1;
    // 定义最大的数据量
    private static final int MAX_DATA_COUNT = 1000;
    // 定义一些可靠的服务器的URL,用来发送HTTP请求
    private static final String[] SERVER_URLS = {
            "https://www.baidu.com",
    };
    // 定义一个静态方法,用来启动这个服务
    public static void startService(Context context) {
        Log.d("NetworkMonitorService", "startService");
        Intent intent = new Intent(context, NetworkMonitorService.class);
        enqueueWork(context, NetworkMonitorService.class, JOB_ID, intent);
    }
    // 重写onHandleWork方法,在后台线程上处理传入的工作请求
    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        Log.d("NetworkMonitorService", "onHandleWork");
        // 获取一个OkHttpClient实例,用来发送HTTP请求
        OkHttpClient client = new OkHttpClient();
        // 获取一个DatabaseHelper实例,用来操作本地数据库
        DatabaseHelper databaseHelper = new DatabaseHelper(this);
        // 获取当前的网络类型和IP地址
        String networkType = NetworkHelper.getCurrentNetworkType(getApplicationContext());
        String ipAddress = NetworkHelper.getCurrentIpAddress(getApplicationContext());
        // 遍历可靠的服务器的URL,依次发送HTTP请求,并记录响应的时间和状态码
        for (String url : SERVER_URLS) {
            // 创建一个Request对象,用来封装HTTP请求
            Request request = new Request.Builder()
                    .url(url)
                    .build();
            // 发送异步HTTP请求,并注册一个回调函数
            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    // 获取当前时间戳
                    long timestamp = System.currentTimeMillis();
                    // 插入一条数据,并检查数据量,时间设置为-1,状态码设置为-1,表示请求失败
                    databaseHelper.insertData(url, -1, -1, timestamp, networkType + "_" + ipAddress);
                }
                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    // 处理正常情况,获取响应的时间和状态码
                    // 计算请求花费的时间,单位为毫秒
                    long time = response.receivedResponseAtMillis() - response.sentRequestAtMillis();
                    // 获取响应的状态码
                    int status = response.code();
                    // 获取当前时间戳
                    long timestamp = System.currentTimeMillis();
                    // 打印日志,显示请求的URL,时间和状态码
                    Log.d("NetworkMonitorService", "URL: " + url + ", Time: " + time + " ms, Status: " + status);
                    // 插入一条数据,并检查数据量
                    databaseHelper.insertData(url, time, status, timestamp, networkType + "_" + ipAddress);
                }
            });
        }
    }
}

FileUploadService

FileUploadService也是一个继承自JobIntentService的后台服务,它可以在后台线程上处理传入的工作请求。我们重写了它的onHandleWork方法,在这个方法中,我们做了以下几件事:

  • 将测试结果从数据库中读取出来,并写入到本地文件中
  • 使用JSch库,通过SSH协议,将本地文件上传到远程服务器上

代码如下:

package com.xxx.factorytest.service;
import android.btf.VendorStorageManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.JobIntentService;
import com.xxx.factorytest.utils.DatabaseHelper;
import com.xxx.factorytest.utils.NetworkHelper;
import com.xxx.factorytest.utils.Utils;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class FileUploadService extends JobIntentService {
    public static final String LOCAL_FILE_PATH = Environment.getExternalStorageDirectory() + "/"+ getCurrentSystemTime() +"_"+ getCPUSerial()+ ".log";
    private static final int JOB_ID = 2;
    private static final String TAG = "FileUploadService";
    public static void startService(Context context) {
        Intent intent = new Intent(context, FileUploadService.class);
        enqueueWork(context, FileUploadService.class, JOB_ID, intent);
    }
    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        writeDataToFile();
        uploadFile("username", "123456", "192.168.1.22", 22, LOCAL_FILE_PATH, "/home/work2/network_test/");
    }
    // 获取当前系统时间的函数
    private static String getCurrentSystemTime() {
        // 创建一个SimpleDateFormat对象,指定日期格式为yyyyMMdd
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
        // 获取当前日期对象
        Date date = new Date(System.currentTimeMillis());
        // 格式化日期对象,返回字符串
        return simpleDateFormat.format(date);
    }
    private void uploadFile(String username, String password, String host, int port, String localFile, String remoteDir) {
        JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession(username, host, port);
            session.setPassword(password);
            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();
            Log.d(TAG, "Connected to SSH server");
            ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");
            sftp.connect();
            Log.d(TAG, "Connected to SFTP channel");
            sftp.put(localFile, remoteDir + new File(localFile).getName());
            Log.d(TAG, "File upload success");
            sftp.disconnect();
            session.disconnect();
        } catch (JSchException | SftpException e) {
            Log.e(TAG, "File upload failed", e);
            Log.e(TAG, "Exception: " + e.getMessage());
            if (e.getCause() != null) {
                Log.e(TAG, "Caused by: " + e.getCause().getClass().getName() + ": " + e.getCause().getMessage());
            }
        }
    }
    private void writeDataToFile() {
        Log.d(TAG, "writeDataToFile:"+LOCAL_FILE_PATH);
        DatabaseHelper databaseHelper = new DatabaseHelper(this);
        SQLiteDatabase db = databaseHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("SELECT * FROM " + DatabaseHelper.TABLE_NAME, null);
        File file = new File(LOCAL_FILE_PATH);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
            int totalCount = 0;
            int successCount = 0;
            int failureCount = 0;
            while (cursor.moveToNext()) {
                String url = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_URL));
                long time = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_TIME));
                int status = cursor.getInt(cursor.getColumnIndex(DatabaseHelper.COLUMN_STATUS));
                long timestamp = cursor.getLong(cursor.getColumnIndex(DatabaseHelper.COLUMN_TIMESTAMP));
                String network_ip = cursor.getString(cursor.getColumnIndex(DatabaseHelper.COLUMN_NETWORK_IP));
                String datetime = sdf.format(new Date(timestamp));
                String data = String.format(Locale.getDefault(), "Index:%d,Network:%s,URL:%s,Time:%d ms,Status:%d,Datetime:%s\n", totalCount + 1, network_ip, url, time, status, datetime);
                fos.write(data.getBytes());
                totalCount++;
                if (status == 200) {
                    successCount++;
                } else {
                    failureCount++;
                }
            }
            double successRate = (double) successCount / totalCount * 100;
            String stats = String.format(Locale.getDefault(), "总请求次数: %d, 成功次数: %d, 失败次数: %d, 成功率: %.2f%%\n", totalCount, successCount, failureCount, successRate);
            fos.write(stats.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            cursor.close();
            db.close();
        }
    }
}

NetworkMonitorReceiver

NetworkMonitorReceiver是一个继承自BroadcastReceiver的广播接收器,它可以接收系统或应用发送的广播,并做出相应的处理。我们重写了它的onReceive方法,在这个方法中,我们做了以下一件事:

  • 启动一个定时器和一个定时任务,每隔一定时间就启动NetworkMonitorService

代码如下:

package com.xxx.factorytest.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.xxx.factorytest.service.NetworkMonitorService;
import java.util.Timer;
import java.util.TimerTask;
public class NetworkMonitorReceiver extends BroadcastReceiver {
    // 定义一个定时器对象,用来执行定时任务
    private Timer timer;
    // 定义一个定时任务对象,用来启动后台服务
    private TimerTask timerTask;
    // 定义一个常量,表示每隔多少毫秒执行一次
    private static final long PERIOD = 1000;
    // 重写onReceive方法,在接收到广播时启动定时器和定时任务
    @Override
    public void onReceive(Context context, Intent intent) {
        // Toast.makeText(context, "upload-->", Toast.LENGTH_SHORT).show();
        // 创建一个定时器对象
        timer = new Timer();
        // 创建一个定时任务对象
        timerTask = new TimerTask() {
            @Override
            public void run() {
                // 启动后台服务
                NetworkMonitorService.startService(context);
            }
        };
        // 启动定时器和定时任务,延迟0毫秒,每隔1秒执行一次
        timer.schedule(timerTask, 0, PERIOD);
    }
}

FileUploadReceiver

FileUploadReceiver也是一个继承自BroadcastReceiver的广播接收器,它可以接收系统或应用发送的广播,并做出相应的处理。我们重写了它的onReceive方法,在这个方法中,我们做了以下一件事:

  • 启动一个定时器和一个定时任务,每隔一定时间就启动FileUploadService

代码如下:

package com.xxx.factorytest.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.btf.factorytest.service.FileUploadService;
import java.util.Timer;
import java.util.TimerTask;
public class FileUploadReceiver extends BroadcastReceiver {
    // 定义一个定时器对象,用来执行定时任务
    private Timer timer;
    // 定义一个定时任务对象,用来启动后台服务
    private TimerTask timerTask;
    // 定义一个常量,表示每隔多少毫秒执行一次
    private static final long PERIOD = 10000;
    // 重写onReceive方法,在接收到广播时启动定时器和定时任务
    @Override
    public void onReceive(Context context, Intent intent) {
       // Toast.makeText(context, "upload-->", Toast.LENGTH_SHORT).show();
        // 创建一个定时器对象
        timer = new Timer();
        // 创建一个定时任务对象
        timerTask = new TimerTask() {
            @Override
            public void run() {
                // 启动后台服务
                FileUploadService.startService(context);
            }
        };
        // 启动定时器和定时任务,延迟0毫秒,每隔10秒执行一次
        timer.schedule(timerTask, 0, PERIOD);
    }
}

DatabaseHelper

DatabaseHelper是一个继承自SQLiteOpenHelper的数据库操作类,它可以创建和管理本地数据库。我们重写了它的onCreate和onUpgrade方法,在这些方法中,我们做了以下几件事:

  • 创建一个名为network_test的数据库表,用来存储测试结果
  • 定义了表中的五个字段:url、time、status、timestamp、network_ip,分别表示请求的URL、请求花费的时间、响应的状态码、请求的时间戳、请求的网络类型和IP地址
  • 定义了一些常量,用来表示表名和字段名

我们还定义了两个方法:insertData和queryData,分别用来插入和查询数据。

代码如下:

public class DatabaseHelper extends SQLiteOpenHelper {
    // 定义数据库的名称和版本号
    private static final String DATABASE_NAME = "network_test.db";
    private static final int DATABASE_VERSION = 1;
    // 定义表名和字段名的常量
    public static final String TABLE_NAME = "network_test";
    public static final String COLUMN_URL = "url";
    public static final String COLUMN_TIME = "time";
    public static final String COLUMN_STATUS = "status";
    public static final String COLUMN_TIMESTAMP = "timestamp";
    public static final String COLUMN_NETWORK_IP = "network_ip";
    // 定义创建表的SQL语句
    private static final String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(" +
            COLUMN_URL + " TEXT," +
            COLUMN_TIME + " INTEGER," +
            COLUMN_STATUS + " INTEGER," +
            COLUMN_TIMESTAMP + " INTEGER," +
            COLUMN_NETWORK_IP + " TEXT" +
            ")";
    // 定义删除表的SQL语句
    private static final String DROP_TABLE_SQL = "DROP TABLE IF EXISTS " + TABLE_NAME;
    // 构造方法,调用父类的构造方法,并传入数据库的名称和版本号
    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    // 重写onCreate方法,在创建数据库时执行创建表的SQL语句
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_SQL);
    }
    // 重写onUpgrade方法,在升级数据库时执行删除表和创建表的SQL语句
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL(DROP_TABLE_SQL);
        db.execSQL(CREATE_TABLE_SQL);
    }
    // 定义一个插入数据的方法,接收五个参数,分别表示请求的URL、请求花费的时间、响应的状态码、请求的时间戳、请求的网络类型和IP地址,并返回插入结果(true或false)
    public boolean insertData(String url, long time, int status, long timestamp, String network_ip) {
        // 获取一个可写的数据库对象
        SQLiteDatabase db = getWritableDatabase();
        // 创建一个ContentValues对象,用来存放要插入的数据
        ContentValues values = new ContentValues();
        values.put(COLUMN_URL, url);
        values.put(COLUMN_TIME, time);
        values.put(COLUMN_STATUS, status);
        values.put(COLUMN_TIMESTAMP, timestamp);
        values.put(COLUMN_NETWORK_IP, network_ip);
        // 调用insert方法,将数据插入到数据库中,并返回插入结果(-1表示失败,其他表示成功)
        long result = db.insert(TABLE_NAME, null, values);
        // 关闭数据库对象
        db.close();
        // 判断插入结果,如果不等于-1,表示成功,返回true,否则返回false
        return result != -1;
    }
    // 定义一个查询数据的方法,返回一个Cursor对象,用来遍历查询结果
    public Cursor queryData() {
        // 获取一个可读的数据库对象
        SQLiteDatabase db = getReadableDatabase();
        // 调用query方法,查询所有的数据,并按照时间戳降序排序
        Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, COLUMN_TIMESTAMP + " DESC");
        // 返回Cursor对象
        return cursor;
    }
}

NetworkHelper

NetworkHelper是一个网络操作类,它提供了两个静态方法:getCurrentNetworkType和getCurrentIpAddress,分别用来获取当前的网络类型和IP地址。

代码如下:

public class NetworkHelper {
    // 定义一个获取当前网络类型的方法,接收一个Context对象作为参数,返回一个字符串表示网络类型
    public static String getCurrentNetworkType(Context context) {
        // 获取一个ConnectivityManager对象,用来管理网络连接
        ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        // 获取当前激活的网络信息
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        // 判断网络信息是否为空,如果为空,表示没有网络连接,返回"NONE"
        if (networkInfo == null) {
            return "NONE";
        }
        // 获取当前网络的类型
        int type = networkInfo.getType();
        // 根据不同的类型,返回不同的字符串
        switch (type) {
            case ConnectivityManager.TYPE_WIFI:
                return "WIFI";
            case ConnectivityManager.TYPE_MOBILE:
                return "MOBILE";
            case ConnectivityManager.TYPE_ETHERNET:
                return "ETHERNET";
            default:
                return "UNKNOWN";
        }
    }
    // 定义一个获取当前IP地址的方法,接收一个Context对象作为参数,返回一个字符串表示IP地址
    public static String getCurrentIpAddress(Context context) {
        // 获取一个WifiManager对象,用来管理WIFI连接
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        // 获取当前WIFI连接的信息
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        // 获取当前WIFI分配的IP地址,是一个整数
        int ipAddress = wifiInfo.getIpAddress();
        // 将整数转换为字符串,使用点号分隔四个字节
        String ip = (ipAddress & 0xFF) + "." +
                ((ipAddress >> 8) & 0xFF) + "." +
                ((ipAddress >> 16) & 0xFF) + "." +
                ((ipAddress >> 24) & 0xFF);
        // 返回IP地址字符串
        return ip;
    }
}

注册组件和申请权限

在AndroidManifest.xml文件中,我们需要注册我们的四个组件:两个后台服务和两个广播接收器,并指定它们的名称和属性。还需要申请一些必要的权限,如访问网络、访问WIFI、访问外部存储等。代码如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.networktest">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
      <!-- 定义一个后台服务的组件,使用JobIntentService类 -->
        <service
            android:name=".service.NetworkMonitorService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false" />
        <!-- 定义一个后台服务的组件,使用JobIntentService类 -->
        <service
            android:name=".service.FileUploadService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false" />
        <!-- 定义一个广播接收器的组件,使用FileUploadReceiver类 -->
        <receiver
            android:name=".receiver.FileUploadReceiver"
            android:exported="false">
            <!-- 指定要接收的广播类型,比如开机启动,网络变化等 -->
            <intent-filter>
                <action android:name="com.example.fileupload.ACTION_START_UPLOAD" />
            </intent-filter>
        </receiver>
        <receiver
            android:name=".receiver.NetworkMonitorReceiver"
            android:exported="false">
            <!-- 指定要接收的广播类型,比如开机启动,网络变化等 -->
            <intent-filter>
                <action android:name="com.example.networkmonitor.ACTION_START_MONITOR" />
            </intent-filter>
        </receiver>

测试

  • 在远程服务器上查看上传的文件,并分析测试结果

总结

本文介绍了一个Android网络连接稳定性测试解决方案,它可以对不同类型的网络和服务器进行测试,并记录和上传测试结果。

希望本文对你有所帮助,如果你有任何问题或建议,欢迎在评论区留言。谢谢!

目录
打赏
0
0
0
0
80
分享
相关文章
5G引领家庭网络升级:速度、稳定性与智能化的新时代
5G引领家庭网络升级:速度、稳定性与智能化的新时代
226 69
Docker网关冲突导致容器启动网络异常解决方案
当执行`docker-compose up`命令时,服务器网络可能因Docker创建新网桥导致IP段冲突而中断。原因是Docker默认的docker0网卡(172.17.0.1/16)与宿主机网络地址段重叠,引发路由异常。解决方法为修改docker0地址段,通过配置`/etc/docker/daemon.json`调整为非冲突段(如192.168.200.1/24),并重启服务。同时,在`docker-compose.yml`中指定网络模式为`bridge`,最后通过检查docker0地址、网络接口列表及测试容器启动验证修复效果。
2025商业版拓展校园圈子论坛网络的创新解决方案:校园跑腿小程序系统架构
校园跑腿小程序系统是一款创新解决方案,旨在满足校园配送需求并拓展校友网络。跑腿员可接单配送,用户能实时跟踪订单并评价服务。系统包含用户、客服、物流、跑腿员及订单模块,功能完善。此外,小程序增设信息咨询发布、校园社区建设和活动组织等功能,助力校友互动、经验分享及感情联络,构建紧密的校友网络。
89 1
2025商业版拓展校园圈子论坛网络的创新解决方案:校园跑腿小程序系统架构
Android网络小说阅读器的实现
小说阅读Demo,。此Demo使用Jsoup解析HTML,实现小说数据抓取(数据源自网络),并包含自定义View、六章小说缓存等功能,但未实现下载。项目还包括屏幕适配、字体设置等,借助第三方框架完成优化。以下是主页、详情页、阅读页等界面展示。
Android网络请求演变:从Retrofit到Flow的转变过程。
通过这个比喻,我们解释了 Android 网络请求从 Retrofit 到 Flow 的转变过程。这不仅是技术升级的体现,更是反映出开发者在面对并发编程问题时,持续探索和迭求更好地解决方案的精神。未来,还会有更多新的技术和工具出现,我们期待一同 witness 这一切的发展。
138 36
|
3月前
|
拓展校友网络的创新解决方案:校园论坛圈子小程序+跑腿+二手市场系统
这是一款基于小程序的校园跑腿服务平台,支持多种注册登录方式、下单支付、跑腿接单配送、订单跟踪评价及物流查询功能,并配备客服模块提升用户体验。系统包含用户、客服、物流、跑腿员和订单五大核心模块,功能完善。此外,平台还拓展了校友网络功能,如信息咨询发布、校园社区建设和活动组织等,旨在增强校友互动与联系,形成紧密的校友生态。
111 4
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
123 15
Hyper-V网络连接无响应解决方案
当Hyper-V虚拟机出现网络连接无响应时,可从以下方面排查:1) 检查物理网络连接,确保设备正常;2) 验证虚拟网络配置,包括虚拟交换机和网络适配器设置;3) 更新驱动程序以解决兼容性问题;4) 调整防火墙和安全软件设置;5) 重启相关服务和设备;6) 使用命令行工具诊断网络问题;7) 检查BIOS中虚拟化技术是否启用;8) 排查IP冲突和其他日志错误。综合以上步骤,可有效修复网络连接故障。
为何教育机构需要强大的网络安全解决方案
近年来,教育行业从传统课堂快速转向在线课程和虚拟教室,疫情加速了这一进程。然而,数字化转型也带来了网络安全风险。身份治理与管理(IGA)解决方案如ManageEngine的ADManager Plus,能有效保护教育机构免受网络攻击,确保数据安全、简化用户管理并实现合规性。通过自动化流程,它不仅提升了安全性,还减轻了IT管理员的工作负担,确保资源访问的无缝性和准确性。
122 11
2025年测试用例管理看这一篇就够了 ----Codes 开源免费、全面的测试管理解决方案
Codes 是国内首款重新定义 SaaS 模式的开源项目管理平台,支持云端认证、本地部署、全部功能开放,并且对 30 人以下团队免费。它通过整合迭代、看板、度量和自动化等功能,简化测试协同工作,使敏捷测试更易于实施。并提供低成本的敏捷测试解决方案,如同步在线离线测试用例、流程化管理缺陷、低代码接口自动化测试和 CI/CD,以及基于迭代的测试管理和测试用时的成本计算等,践行敏捷测试。
2025年测试用例管理看这一篇就够了 ----Codes 开源免费、全面的测试管理解决方案
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问