对象存储

  • 对象存储 > SDK 下载 > Android SDK

    Android SDK

    最近更新时间: 2024-08-05 18:24:28

    Android SDK 只包含了最终用户使用场景中的必要功能。相比服务端 SDK 而言,客户端 SDK 不会包含对云存储服务的管理和配置功能。

    版本

    Qiniu SDK 版本 最低 Android版本 依赖库版本
    8.8.x Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.7.x Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.6.x Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.5.x Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.4.x Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.3.1+ Android 4.0+ okhttp 4+ (如果要支持 Android 4.0+,需调整 okhttp 至 v3.12.+)
    8.3.0 Android 5.0+ okhttp 4+
    8.2.x Android 5.0+ okhttp 4+
    8.1.x Android 5.0+ okhttp 4+
    8.0.x Android 5.0+ okhttp 4+
    7.6.x Android 5.0+ okhttp 4+
    7.5.x Android 5.0+ okhttp 4+
    7.4.6 Android 4.0+ okhttp 3.12.6
    7.3.x Android 2.3+ okhttp 3.11.0
    7.2.x Android 2.3+ okhttp 3+
    7.1.x Android 2.3+ okhttp 2.6+
    7.0.8,7.0.9 Android 2.2+ android-async-http 1.4.9
    7.0.7 Android 2.2+ android-async-http 1.4.8

    注意

    • 如果要兼容 Android 4.x ,对应 okhttp 版本请调整至 3.12.+;maven 依赖版本调整方法可参考下方 1.2 通过 maven

    1 安装

    1.1 直接安装

    将 sdk jar文件 复制到项目中去, jar包下载地址 , 下载对应的 jar 包,以及搜索下载对应的依赖库

    happy-dns下载地址

    1.2 通过 maven

    Android Studio中添加 dependencies 或者在项目中添加 maven 依赖

    // 1 直接导入
    implementation 'com.qiniu:qiniu-android-sdk:8.8.+'
    
    // 2 如果要修改okhttp依赖的版本,可采用以下方式(强烈建议使用七牛库依赖的okhttp版本)
    implementation ('com.qiniu:qiniu-android-sdk:8.8.+'){
        exclude (group: 'com.squareup.okhttp3', module: 'okhttp')
    }
    implementation 'com.squareup.okhttp3:okhttp:4.2.2'
    

    如果是 eclipse, 也可以直接添加依赖来处理。

    相关链接

    如果需要下载历史 jar 包,也可以在 maven 上直接下载,请参考 这里

    2 推荐的使用姿势

    Android SDK 作为移动端的 SDK,在使用时会被嵌入到移动端的应用中,为了保证用户信息的安全以及业务的灵活性,我们给出以下几点建议:

    2.1 AccessKey 和 SecretKey 下发

    账户以及 iam 子账户的密钥对 AccessKey 和 SecretKey 不要固化在客户端中,其涉及相关参数通过用户自己的业务服务端来生成并下发(比如上传的 Token)。业务服务端集中管理 AccessKey 和 SecretKey ,避免泄漏;其次 AccessKey 、SecretKey 和客户端应用需解耦,其因泄露更换时不会产生不必要麻烦。

    2.2 上传域名下发,并可以下发多个域名

    SDK 上传过程:

    1. 获取七牛 Bucket 的上传域名;
      • 指定域名。通过指定区域 id 或直接给上传域名列表方式指定域名,详情见 域名配置 FixedZone;
      • SDK 自动查询域名。SDK 内部自动调用七牛服务端接口,查询 Bucket 的上传域名,详情见 域名配置 AutoZone;
    2. 使用上传域名进行上传,上传过程会伴随主备重试,既多个域名时,一个域名上传失败,会尝试使用其他域名进行上传。

    建议上传域名下发的方式

    用户服务在下发上传 Token 时同时下发上传域名(可以在 App 需要进行上传时,先请求获取获取上传 Token 及上传域名),可下发多个域名,App 在拿到 Token 和上传域名后,配置上传 Config 以及 UploadManager,进而进行上传操作。

    具体代码示例如下:

    // Upload_Domain1, Upload_Domain2 为服务下发的上传域名
    FixedZone fixedZone = new FixedZone(new String[]{"Upload_Domain1", "Upload_Domain2"});
    Configuration config = new Configuration.Builder()
         .zone(fixedZone)
         .build();
    UploadManager manager = new UploadManager(config);
    

    域名下发的优点:

    1. 跳过域名查询过程,上传更高效。
    2. 避免在 App 硬编码上传域名,导致使用不灵活,比如:
      • 在客户更换/增加区域时,由于 App 硬编码了区域,已发布的 App 无法处理此问题。
      • 某些客户生产环境使用的 Bucket 和 测试环境使用的 Bucket 不在同一个区域,在实际上线时错用了测试 Bucket 的区域,导致 Bucket 和区域不匹配,App 相关功能不可用。
      • 自定义域名,由于硬编码,老版本 App 也无法灵活应对。

    除了上面的问题,还有很多情景不再列举,上面问题的根本原因是由于 App 硬编码了区域的上传域名导致 App 不能灵活应对上传域名需要修改的场景,所以建议客户通过服务下发上传的方式来配置 SDK 的上传域名, 而不是直接硬编码上传域名。

    3 域名配置

    域名配置分为 FixZone 和 AutoZone 两种方式。

    3.1 FixZone

    在 FixZone 中,上传域名被固定,且不可更改。有两种配置方式:

    3.1.1 通过指定上传区域 ID 方式

    区域 ID 和区域域名对应关系参考 这里

    // 指定上传区域为 华东-浙江
    FixedZone zone = FixedZone.createWithRegionId("z0");
    

    这种方式硬编码了上传域名的区域,且不可更改,所以不建议在生产环境中使用,仅供测试使用。

    3.1.2 直接配置上传域名

    直接配置上传域名的方式可以灵活配置域名,我们不建议上传域名配置为固定域名,而是通过服务下发的方式进行配置。具体上传域名下发请参考上传域名下发介绍。

    // Upload_Domain1, Upload_Domain2 为服务下发的上传域名
    FixedZone zone = new FixedZone(new String[]{"Upload_Domain1", "Upload_Domain2"});
    

    3.2 AutoZone

    如果用户不想关心上传域名相关的逻辑,而且没有任何自定义的需求,完全依赖七牛的上传域名,则可以使用 AutoZone。AutoZone 相对于 FixZone 来说,在第一次上传时查询上传域名,后续会使用缓存的上传域名进行上传。在缓存有效期内不会更换上传域名,当前域名缓存有效期为 24 小时。
    示例代码:

    // 不指定 uc 域名,使用七牛公有云默认域名
    AutoZone zone = new AutoZone();
    
    // 指定 uc 域名,公有云无需指定
    AutoZone zone = new AutoZone();
    zone.setUcServers(new String[]{"UCHost0", "UCHost1"});
    

    不过我们推荐用户使用上传域名下发的方式来配置上传域名,防止后续业务变更需要。

    4 上传

    4.1 上传示例

    SDK 内置两种上传方式:表单上传和分片上传,并根据具体情况,内部做了自动切换。表单上传使用一个 HTTP POST 请求完成文件的上传,因此比较适合较小的文件和较好的网络环境。相比而言,分片上传更能适应不稳定的网络环境,也比较适合上传比较大的文件(例如数百 MB 或更大)。

    若需深入了解上传方式之间的区别,请参阅上传类型中 表单上传分片上传 v1 版分片上传 v2 版 接口说明。

    UploadManager.put 参数说明:

    参数 类型 说明
    data byte[]/String/File 数据,可以是 byte 数组、文件路径、文件、数据流和 Uri 资源
    key String 保存在服务器上的资源唯一标识,请参阅 键值对
    token String 服务器分配的 token
    completionHandler UpCompletionHandler 上传回调函数,必填
    options UploadOptions 如果需要进度通知、中途取消、指定 mimeType,则需要填写相应字段,详见下面的 UploadOptions 参数说明

    UploadOptions 参数说明:

    参数 类型 说明
    params Map<String, String> 自定义变量,key 必须以 x: 开始
    mimeType String 指定文件的 mimeType
    progressHandler UpProgressHandler 上传进度回调
    cancellationSignal UpCancellationSignal 取消上传,当 isCancelled() 返回 true 时,不再执行更多上传
    import com.qiniu.android.common.FixedZone;
    import com.qiniu.android.http.ResponseInfo;
    import com.qiniu.android.storage.Configuration;
    import com.qiniu.android.storage.FileRecorder;
    import com.qiniu.android.storage.KeyGenerator;
    import com.qiniu.android.storage.Recorder;
    import com.qiniu.android.storage.UpCancellationSignal;
    import com.qiniu.android.storage.UpCompletionHandler;
    import com.qiniu.android.storage.UpProgressHandler;
    import com.qiniu.android.storage.UploadManager;
    import com.qiniu.android.storage.UploadOptions;
    import com.qiniu.android.utils.Utils;
    
    Recorder recorder = null;
    try {
        recorder = new FileRecorder(Utils.sdkDirectory() + "/recorder");
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    // uploadHosts 建议通过服务方式下发,
    String[] uploadHosts = new String[]{"Host0", "Host1"};
    FixedZone zone = new FixedZone(uploadHosts);
    Configuration configuration = new Configuration.Builder()
            .zone(zone)                         // 配置上传区域
            .putThreshold(4 * 1024 * 1024) // 分片上传阈值:4MB,大于 4MB 采用分片上传,小于 4MB 采用表单上传
            .useConcurrentResumeUpload(true)   // 开启分片上传
            .recorder(recorder)  // 文件分片上传时断点续传信息保存,表单上传此配置无效
            .resumeUploadVersion(Configuration.RESUME_UPLOAD_VERSION_V2) // 使用分片 V2
            .build();
    UploadManager uploadManager = new UploadManager(configuration);
    
    UploadOptions options = new UploadOptions(null, null, true,
            new UpProgressHandler() {
                @Override
                public void progress(String key, double percent) {
                    // 上传进度
                }
            }, new UpCancellationSignal() {
        @Override
        public boolean isCancelled() {
            // 当需要取消时,此处返回 true,SDK 内部会多次检查返回值,当返回值为 true 时会取消上传操作
            return false;
        }
    });
    
    String filePath = "";    // 文件路径
    String key = "";         // 文件 key
    String uploadToken = ""; // 上传的 Token
    uploadManager.put(filePath, key, uploadToken, new UpCompletionHandler() {
        @Override
        public void complete(String key, ResponseInfo info, JSONObject response) {
            if (info != null && info.isOK()) {
                // 上传成功
            } else {
                // 上传失败
            }
        }
    }, options);
    

    4.2 上传配置介绍

    4.2.1 开启传输加速

    默认情况下,SDK 未开启使用加速域名上传的功能,如需开启需按下面方式进行配置:

    // 1. 在七牛云存储控制台修改 Bucket 配置,域名管理 -> 开启传输加速(SDK 开启使用加速域名上传的功能仅仅是 SDK 支持
    //    使用加速域名,所以要想使用传输加速,七牛云服务端需开启此功能)
    // 
    // 2. SDK 配置 
    // 方式一:使用 FixZone 配置
    // Upload_Domain1 和 Upload_Domain2 为加速域名,可以参考七牛云存储控制台域名管理页面,建议通过用户服务下发,不要硬编码
    Zone zone = new FixedZone(new String[]{"Upload_Domain1", "Upload_Domain2"});
    Configuration configuration = new Configuration.Builder()
                    .zone(zone)    // 区域必须使用 AutoZone,默认是 AutoZone,所以 zone 也可以不配置
                    .build();
    
    // 方式二:使用 AutoZone(v8.8.0 开始支持)
    Configuration configuration = new Configuration.Builder()
                    .accelerateUploading(true) // 开启传输加速,此参数仅对使用 QNAutoZone 时有效
                    .zone(new AutoZone())      // 默认是 AutoZone,所以 zone 也可以不配置
                    .build();
    

    4.2.2 使用 HTTP/3 协议

    导入 HTTP/3 client 插件,http3 client 插件依赖于 Android SDK v8.5.0及以上版本

    安装导入

    // 移除 qiniu-android-sdk 依赖 : implementation 'com.qiniu:qiniu-android-sdk:x.x.+' 
    implementation 'com.qiniu:qiniu-android-curl-plugin:1.0.0'
    

    使用 CurlClient

    import com.qiniu.client.curl.CurlClient;
    import com.qiniu.android.storage.GlobalConfiguration;
    import com.qiniu.android.storage.Configuration;
    import com.qiniu.android.storage.UploadManager;
    
    // 开启 HTTP/3
    GlobalConfiguration.getInstance().enableHttp3 = true;
    
    // @param caPath: SSL 证书本地路径;如果想自定义 CA 可设置此选项,此处为 CA 文件的本地路径。
    // 				  如果未定义(caPath 配置 null)则使用 SDK 内部提供的 CA 证书,证书来源:https://curl.se/ca/cacert.pem
    CurlClient client = new CurlClient(caPath);
    Configuration config = new Configuration.Builder()
                    .requestClient(client) // 指定 HTTP/3 Client
                    .build();
    UploadManager manager = new UploadManager(config);
    

    4.2.3 DNS 预解析

    DNS 预解析是对 SDK 上传的一种优化,SDK 会先对上传域名提前做 DNS 解析并缓存(会根据有效期定期刷新缓存),等上传时直接使用缓存的 IP 地址进行上传,可以跳过 DNS 解析阶段,提高上传的效率,在弱网场景效果比较明显。

    • 从 7.4.0 开始增加了 DNS 预解析和缓存策略,减少 DNS 解析错误。
    • 从 8.0.0 开始增加了每 2 分钟(进程非挂起状态)自动刷新缓存策略。同时增加了 DNS 预解析 开关控制,默认开启,可以通过 GlobalConfiguration.getInstance().isDnsOpen = false 关闭 DNS 预解析。
    • 从 8.3.2 开始对 DNS 预解析缓存增加缓存有效期,默认 10 分钟,支持手动修改有效期 GlobalConfiguration.getInstance().dnsCacheMaxTTL 时长,单位为 s。

    SDK 内置服务系统 DNS 解析和 HTTP DNS 解析(从 8.4.0 开始),如果这两个 DNS 解析不能满足您的需求,那么可以选择自定义 DNS 解析。如果您自定义了 DNS 解析,那么在 DNS 预取时会优先使用您自定义的 DNS 解析,SDK 内部有 DNS 解析缓存相关的逻辑,用户无需考虑 DNS 解析缓存相关的逻辑。一般情况下,我们不建议用户自己自定义 DNS 预解析。

    下方为 8.4.0 以上版本使用外部 DNS 组件 HappyDns 的示例代码(实例仅为使用方式演示,请根据您的需求自行处理 DNS 解析细节),如果找不到 Dns 类,可在工程的 Gradle 配置中依赖 HappyDns

    implementation 'com.qiniu:happy-dns:2.0.1'
    

    具体实例代码如下:

    import com.qiniu.android.dns.DnsManager;
    import com.qiniu.android.dns.Domain;
    import com.qiniu.android.dns.IResolver;
    import com.qiniu.android.dns.NetworkInfo;
    import com.qiniu.android.dns.Record;
    import com.qiniu.android.dns.dns.DnsUdpResolver;
    import com.qiniu.android.dns.dns.DohResolver;
    import com.qiniu.android.http.dns.Dns;
    import com.qiniu.android.http.dns.IDnsNetworkAddress;
    
    import java.io.IOException;
    import java.net.UnknownHostException;
    import java.util.ArrayList;
    import java.util.List;
    
    class CustomDns {
    
        public static Dns buildDefaultDns() {
    //         可添加修改 多个 IResolver
    //         适当调整不同 IResolver 的加入顺序,比如:223.5.5.5,119.29.29.29,114.114.114.114,8.8.8.8 等
            IResolver[] resolvers = new IResolver[2];
            // 自定义 DNS 服务器地址: UDP
            resolvers[0] = new DnsUdpResolver("8.8.8.8");
            // 自定义 DNS 服务器地址:Doh
            resolvers[1] = new DohResolver("https://dns.alidns.com/dns-query");
    
            final DnsManager dnsManager = new DnsManager(NetworkInfo.normal, resolvers);
            Dns dns = new Dns() {
                // 若抛出异常 Exception ,sdk 会使用 okhttp 组件默认 dns 解析结果
                @Override
                public List<IDnsNetworkAddress> lookup(String hostname) throws UnknownHostException {
                    Domain domain = new Domain(hostname);
                    List<IDnsNetworkAddress> addressList = null;
                    try {
                        Record[] records = dnsManager.queryRecords(domain);
                        if (records != null && records.length > 0) {
                            addressList = new ArrayList<>();
                            for (Record record : records) {
                                String source = "customized";
                                DemoDnsNetworkAddress address = new DemoDnsNetworkAddress(hostname, record.value, (long) record.ttl, source, record.timeStamp);
                                addressList.add(address);
                            }
                        }
                    } catch (IOException ignored) {
                    }
                    return addressList;
                }
            };
            return dns;
        }
    
        public static class DemoDnsNetworkAddress implements IDnsNetworkAddress {
            private final String hostValue;
            private final String ipValue;
            private final Long ttlValue;
            private final String sourceValue;
            private final Long timestampValue;
    
            DemoDnsNetworkAddress(String hostValue,
                                  String ipValue,
                                  Long ttlValue,
                                  String sourceValue,
                                  Long timestampValue) {
                this.hostValue = hostValue;
                this.ipValue = ipValue;
                this.ttlValue = ttlValue;
                this.sourceValue = sourceValue;
                this.timestampValue = timestampValue;
            }
    
            @Override
            public String getHostValue() {
                return hostValue;
            }
    
            @Override
            public String getIpValue() {
                return ipValue;
            }
    
            @Override
            public Long getTtlValue() {
                return ttlValue;
            }
    
            @Override
            public String getSourceValue() {
                return sourceValue;
            }
    
            @Override
            public Long getTimestampValue() {
                return timestampValue;
            }
        }
    }
    
    // 在 UploadManager 初始化之前配置    
    GlobalConfiguration.getInstance().dns = CustomDns.buildDefaultDns();
    uploadManager = new UploadManager();
    

    4.2.4 其他配置

    // 自定义请求 client,需要自行实现
    IRequestClient client = null;
    
    // 定义分片上传时,断点续传信息保存的 Recorder
    Recorder recorder = null;
    try {
        recorder = new FileRecorder(Utils.sdkDirectory() + "/recorder");
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    // 断点续传信息保存时使用的 key 的生成器,根据 key 可以获取相应文件的上传信息
    // 需要确保每个文件的 key 是唯一的,下面为默认值
    // 可以自定义,也可以不配置(keyGenerator 参数可以不传)
    KeyGenerator keyGenerator = new KeyGenerator() {
        @Override
        public String gen(String key, File file) {
            return key + "_._" + new StringBuffer(file.getAbsolutePath()).reverse();
        }
        @Override
        public String gen(String key, String sourceId) {
            if (sourceId == null) {
                sourceId = "";
            }
            return key + "_._" + sourceId;
        }
    };
    
    // 定义代理
    ProxyConfiguration proxy = new ProxyConfiguration("192.168.1.1", 8888);
    Configuration configuration = new Configuration.Builder()
            .requestClient(client)  // 自定义请求 client,内部所有请求都会走此 client
            .zone(new AutoZone())              // 配置上传区域,使用 AutoZone
            .recorder(recorder, keyGenerator)  // 文件分片上传时断点续传信息保存,表单上传此配置无效
            .proxy(proxy)
            .useHttps(false)
            .chunkSize(4*1024*1024) // 文件采用分片上传时,分片大小为 4MB
            .putThreshold(4 * 1024 * 1024) // 分片上传阈值:4MB,大于 4MB 采用分片上传,小于 4MB 采用表单上传
            .connectTimeout(10) // 请求连接超时 10s
            .writeTimeout(30) // 请求写超时 30s
            .responseTimeout(10) // 请求响应超时 10s
            .retryMax(1) // 单个域名/IP请求失败后最大重试次数为 1 次
            .retryInterval(500) // 重试时间间隔
            .allowBackupHost(true) // 是否使用备用域名进行重试
            .urlConverter(new UrlConverter() {
                @Override
                public String convert(String url) {
                    // 公有云不可配置
                    return url;
                }
            })
            .useConcurrentResumeUpload(true)  // 开启并发分片上传
            .concurrentTaskCount(3)           // 使用并发分片上传时,一个文件并发上传的分片个数
            .resumeUploadVersion(Configuration.RESUME_UPLOAD_VERSION_V2) // 使用分片 V2
            .build();
    
    // 用于服务器上传回调通知的自定义参数,参数的key必须以x: 开头  eg: x:foo
    Map<String, String> params = new HashMap<>();
    // 用于设置meta数据,参数的key必须以x-qn-meta- 开头  eg: x-qn-meta-key
    Map<String, String> metaDataParam = new HashMap<>();
    // 指定文件 mime type
    String mimeType = "";
    UploadOptions options = new UploadOptions(params, null, mimeType, true,
            new UpProgressHandler() {
                @Override
                public void progress(String key, double percent) {
                    // 上传进度
                }
            }, new UpCancellationSignal() {
        @Override
        public boolean isCancelled() {
            // 当需要取消时,此处返回 true,SDK 内部会多次检查返回值,当返回值为 true 时会取消上传操作
            return false;
        }
    }, null);
    
    // 关闭 DNS 预解析
    GlobalConfiguration.getInstance().isDnsOpen = false;
    // 关闭网络状态检测,当因网络异常而失败时,会检测网络状态,进而根据情况调整重试逻辑
    GlobalConfiguration.getInstance().connectCheckEnable = false;
    

    5 下载文件

    该 SDK 并未提供下载文件相关的功能接口,因为文件下载是一个标准的 HTTP GET 过程。开发者只需理解资源 URI 的组成格式即可非常方便的构建资源 URI,并在必要的时候加上 下载凭证 ,即可使用 HTTP GET 请求获取相应资源。

    从安全性和代码可维护性的角度考虑,我们建议下载 URL 的拼接过程也在业务服务器进行,让客户端从业务服务器请求。

    API 参考

    常见问题

    1. 混淆处理
    七牛 的 SDK 不需要做特殊混淆处理,如果有混淆,请将 七牛 相关的包都排除。

    • 在 Android Studio 中,混淆配置在 proguard-rules.pro 里面加上下面几行混淆代码就行:
    -keep class com.qiniu.**{*;}
    -keep class com.qiniu.**{public <init>();}
    -ignorewarnings
    

    注意:

    • ignorewarnings 这个也是必须加的,如果不加这个,编译的时候可能可以通过,但是 release 的时候还是会出现错误。
    • 在 eclipse 中,混淆配置在 proguard-project.txt 里面加上下面几行混淆代码就行:
    -keep class com.qiniu.**{*;}
    -keep class com.qiniu.**{public <init>();}
    -ignorewarnings
    

    2. 为什么进度会在 95% 停很久?

    因为上传进度是用 sdk 写入 socket 的字节数/总字节数作为进度,但写入 socket 不等于服务器收到并且处理完成,中间还有一段时间,如果只是用字节数就会出现更怪异的情况,在 100% 停留很久,所以综合考虑就使用了 95% 这个值。

    如果您发现上传卡在 95% 很长的时间,请检查是否使用了上传回调业务服务器的功能,请确保业务服务器接受到上传回调后可以返回正确的状态码和回复内容。

    3. 如何才能得到下载的 url ?

    上传没有域名概念,只有 bucket ,一个 bucket 可以绑定多个域名。下载的 url 可以用 bucket 里的域名加 key 拼接而成,私有 url 还要加上 token。

    4. 如何跳过 https 证书验证?

    在部分测试环境发生 Trust anchor for certification path not found 异常,可以设置跳过 https 证书验证:在 Configuration 配置 useHttps( false ) 开启。

    相关资源

    如果您有任何关于我们文档或产品的建议和想法,欢迎您通过以下方式与我们互动讨论:

    • 技术论坛 - 在这里您可以和其他开发者愉快的讨论如何更好的使用七牛云服务
    • 提交工单 - 如果您的问题不适合在论坛讨论或希望及时解决,您也可以提交一个工单,我们的技术支持人员会第一时间回复您
    • 博客 - 这里会持续更新发布市场活动和技术分享文章
    • 微博
    • 常见问题

    贡献代码

    1. Fork

    2. 创建您的特性分支 git checkout -b my-new-feature

    3. 提交您的改动 git commit -am ‘Added some feature’

    4. 将您的修改记录提交到远程 git 仓库 git push origin my-new-feature

    5. 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request

    许可证

    Copyright © 2021 qiniu.com

    基于 MIT 协议发布:

    以上内容是否对您有帮助?
  • Qvm free helper
    Close