1、前言

随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的文件,为此,RxHttp 在2.4.0版本中就正式适配了分区存储,并且,可以非常优雅的实现文件上传/下载/进度监听,三步即可搞懂任意请求。

老规矩,先看看请求三部曲

如果你想了解RxHttp更过功能,请查看以下系列文章

RxHttp 2000+star,协程请求,仅需三步

RxHttp 让你眼前一亮的Http请求框架

gradle依赖

//使用kapt依赖rxhttp-compiler,需要导入kapt插件
apply plugin: 'kotlin-kapt'android {defaultConfig {javaCompileOptions {annotationProcessorOptions {arguments = [//必须,告知RxHttp你依赖的okhttp版本,目前已适配 v3.12.0 - v4.9.0版本  (v4.3.0除外)rxhttp_okhttp: '4.9.0',//使用asXxx方法时必须,告知RxHttp你依赖的rxjava版本,可传入rxjava2、rxjava3rxhttp_rxjava: 'rxjava3', rxhttp_package: 'rxhttp'   //非必须,指定RxHttp类包名]}}}//必须,java 8或更高compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}
dependencies {//以下3个为必须,implementation 'com.ljx.rxhttp:rxhttp:2.4.1'implementation 'com.squareup.okhttp3:okhttp:4.9.0' //rxhttp v2.2.2版本起,需要手动依赖okhttpkapt 'com.ljx.rxhttp:rxhttp-compiler:2.4.1' //生成RxHttp类,纯Java项目,请使用annotationProcessor代替kaptimplementation 'com.ljx.rxlife:rxlife-coroutine:2.0.1' //管理协程生命周期,页面销毁,关闭请求//rxjava2   (RxJava2/Rxjava3二选一,使用asXxx方法时必须)implementation 'io.reactivex.rxjava2:rxjava:2.2.8'implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'implementation 'com.ljx.rxlife2:rxlife-rxjava:2.0.0' //管理RxJava2生命周期,页面销毁,关闭请求//rxjava3implementation 'io.reactivex.rxjava3:rxjava:3.0.6'implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'implementation 'com.ljx.rxlife3:rxlife-rxjava:3.0.0' //管理RxJava3生命周期,页面销毁,关闭请求//非必须,根据自己需求选择 RxHttp默认内置了GsonConverterimplementation 'com.ljx.rxhttp:converter-fastjson:2.4.1'implementation 'com.ljx.rxhttp:converter-jackson:2.4.1'implementation 'com.ljx.rxhttp:converter-moshi:2.4.1'implementation 'com.ljx.rxhttp:converter-protobuf:2.4.1'implementation 'com.ljx.rxhttp:converter-simplexml:2.4.1'
}

2、Android 10/11 分区存储

当我们App的targetSdkVersion更改为28以上,并且运行在Android 10以上设备时,我们无法再以绝对路径的方式,去读写非沙盒目录下的文件,当然,如果App是覆盖安装(如:targetSdkVersion 28 覆盖安装为 29),则会保持原来的访问方式。

requestLegacyExternalStorage属性

如果我们的app将targetSdkVersion更改为28以上,且想保持原来的访问方式,则需要在清单文件中将 requestLegacyExternalStorage 的值设置为 true,如下:

<manifest ...>
<!-- This attribute is "false" by default on apps targetingAndroid 10 or higher. --><application android:requestLegacyExternalStorage="true" ... >...</application>
</manifest>

此时,便可继续以原来的方式去读写文件,然而,在Android 11上,Google又给了它新的含义,来看看官网的原话

也就是说,在Android 11设备上,targetSdkVersion为29以上的app,将强制开启分区存储,requestLegacyExternalStorage属性失效

注意,只要同时满足以上两个条件,不管是覆盖安装还是requestLegacyExternalStorage = true,都会强制开启分区存储

分区存储优势

  • 对用户来说,解决了文件乱放的现象

  • 对于开发者来说,我们无需写权限,就可以在分区目录下创建文件,并且访问自己创建的文件,不需要读权限(访问其它应用创建的文件,还是需要读权限)

新的文件访问方式

此图来源于作者[连续三届村草]分享的Android 10(Q)/11(R) 分区存储适配一文,感谢作者的总结

3、上传

3.1、简单上传

在介绍Android 10文件上传前,我们先来看看Android 10之前是如何上传文件的,如下:

//kotlin 协程
val result = RxHttp.postForm("/service/...")             .add("key", "value").addFile("file", new File("xxx/1.jpg"))                                   .awaitString()   //awaitXxx系列方法是挂断方法  //RxJava
RxHttp.postForm("/service/...")      .add("key", "value").addFile("file", new File("xxx/1.jpg")) .asString()                             .subscribe({                       //成功回调                              }, {                       //异常回调                              })                             

以上,我们仅需调用 addFile方法添加文件对象即可,RxHttp提供了一系列addFile方法,列出几个常用的,如下:

//添加单个文件
addFile(String, File)
//添加多个文件,每个文件对应相同的key
addFile(String, List<? extends File> fileList)
//添加多个文件,每个文件对应不同的key
addFile(Map<String, ? extends File> fileMap)
//等等其它addFile方法

在Android 10,我们需要通过Uri对象去上传文件,在RxHttp中,通过addPart方法添加Uri对象,如下:

val context = getContext();  //获取上下文对象
//获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
val uri = Uri.parse("content://media/external/downloads/13417")//kotlin 协程
val result = RxHttp.postForm("/service/...")             .add("key", "value").addPart(context, "file", uri)                                   .awaitString()   //awaitXxx系列方法是挂断方法    //RxJava
RxHttp.postForm("/service/...")        .add("key", "value").addPart(context, "file", uri)                              .asString()                                                 .subscribe({                                           //成功回调                                                  }, {                                           //异常回调                                                  })

同样的,RxHttp内部提供了一系列addPart方法供大家选择,列出几个常用的,如下:

//添加单个Uri对象
addPart(Context, String, Uri)
//添加多个Uri对象,每个Uri对应相同的key
addParts(Context,String, List<? extends Uri> uris)
//添加多个Uri对象,每个Uri对应不同的key
addParts(Context context, Map<String, ? extends Uri> uriMap)
//等等其它addPart方法

3.2、带进度上传

老规矩,看看Android 10之前是如何监听上传进度的,如下:

//kotlin 协程
val result = RxHttp.postForm("/service/...")             .add("key", "value").addFile("file", new File("xxx/1.jpg")) .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程      //上传进度回调,0-100,仅在进度有更新时才会回调       val currentProgress = it.getProgress() //当前进度 0-100   val currentSize = it.getCurrentSize()  //当前已上传的字节大小   val totalSize = it.getTotalSize()      //要上传的总字节大小    }.awaitString()   //awaitXxx系列方法是挂断方法  //RxJava
RxHttp.postForm("/service/...")      .add("key", "value").addFile("file", new File("xxx/1.jpg")).upload(AndroidSchedulers.mainThread()) {            //上传进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小    }                                                               .asString()                             .subscribe({                       //成功回调                              }, {                       //异常回调                              })                   

相比于单纯的上传文件,我们仅需额外调用upload操作符,传入线程调度器及进度回调即可。

同样的,对于Andorid 10,我们仅需要将File对象换成Uri对象即可,如下:

val context = getContext();  //获取上下文对象
//获取Uri对象,这里为了方便,随便写了一个Downlaod目录下的Uri地址
val uri = Uri.parse("content://media/external/downloads/13417")//kotlin 协程
val result = RxHttp.postForm("/service/...")             .add("key", "value").addPart(context, "file", uri)     .upload(this) {//this为当前协程CoroutineScope对象,用于控制回调线程      //上传进度回调,0-100,仅在进度有更新时才会回调       val currentProgress = it.getProgress() //当前进度 0-100   val currentSize = it.getCurrentSize()  //当前已上传的字节大小   val totalSize = it.getTotalSize()      //要上传的总字节大小    }.awaitString()   //awaitXxx系列方法是挂断方法     //RxJava
RxHttp.postForm("/service/...")      .add("key", "value").addPart(context, "file", uri)     .upload(AndroidSchedulers.mainThread()) {            //上传进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小    }                                                          .asString()                             .subscribe({                       //成功回调                              }, {                       //异常回调                              })                

怎么样?是不是so easy!!

4、下载

下载较于上传,要丰富很多,RxHttp内部提供类一系列下载方法来满足不同的需求,如下:

//kotlin
fun IRxHttp.toDownload(destPath: String,context: CoroutineContext? = null,progress: (suspend (ProgressT<String>) -> Unit)? = null
)
fun IRxHttp.toDownload(context: Context,uri: Uri,coroutineContext: CoroutineContext? = null,progress: (suspend (ProgressT<Uri>) -> Unit)? = null
)
fun <T> IRxHttp.toDownload(osFactory: OutputStreamFactory<T>,context: CoroutineContext? = null,progress: (suspend (ProgressT<T>) -> Unit)? = null
)

4.1、简单下载

在Android 10之前,我们仅需传入一个本地文件路径即可,如下:

val localPath = "/sdcard/.../xxx.apk"
//kotlin 协程
val result = RxHttp.get("/service/.../xxx.apk")       .toDownload(localPath).await()  //这里返回sd卡存储路径//RxJava
RxHttp.get("/service/.../xxx.apk")       .asDownload(localPath).subscribe({                //成功回调,这里返回sd卡存储路径                      }, {                //异常回调                   })

而到了Android 10,我们需要自定义一个Android10DownloadFactory类,继承UriFactory类,如下:

class Android10DownloadFactory @JvmOverloads constructor(context: Context,fileName: String,queryUri: Uri? = null
) : UriFactory(context, queryUri, fileName) {override fun getUri(response: Response): Uri {return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {ContentValues().run {put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) //文件名//取contentType响应头作为文件类型put(MediaStore.MediaColumns.MIME_TYPE, response.body?.contentType().toString())//下载到Download目录put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)val uri = queryUri ?: MediaStore.Downloads.EXTERNAL_CONTENT_URIcontext.contentResolver.insert(uri, this)} ?: throw NullPointerException("Uri insert fail, Please change the file name")} else {Uri.fromFile(File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), displayName))}}
}

这里简单介绍下上面的代码,本文后续会详细介绍为啥定义这样一个类,以及如何构建一个Uri对象。

  • 首先就是继承UriFactory抽象类,实现getUri(Response)方法

  • 接着就在实现方法里判断SDK版本,Android 10以上,就通过contentResolver构建Uri对象,否则就根据File对象构建Uri对象,这样就可以兼容到所有系统版本

  • 最后返回Uri对象即可

注:以上代码,基本可以满足大部分人的需求,如你有特殊需求,构建Uri的过程的作出简单的修改即可

有了Android10DownloadFactory类,执行Android 10下载就会及其方便,如下:

val factory = Android10DownloadFactory(context, "test.apk")//kotlin 协程
val uri = RxHttp.get("/service/.../xxx.apk")       .toDownload(factory).await()  //这里返回工厂类构建的Uri对象//RxJava
RxHttp.get("/service/.../xxx.apk")       .asDownload(factory).subscribe({                //成功回调,这里返回工厂类构建的Uri对象    }, {                //异常失败                       })

以上asDownloadtoDownload方法都接收一个UriFactory类型参数,故我们可以直接传入Android10DownloadFactory对象。

4.2、带进度下载

对于带进度下载,我们只需要调用asDownloadtoDownload方法时,传入线程调度器及进度回调即可,如下:

Android 10之前

val localPath = "/sdcard/.../xxx.apk"//kotlin 协程
val result = RxHttp.get("/service/.../xxx.apk")       .toDownload(localPath, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小}.await()  //这里返回sd卡存储路径//RxJava
RxHttp.get("/service/.../xxx.apk")       .asDownload(localPath, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小 }.subscribe({                //成功回调,这里返回sd卡存储路径                      }, {                //异常失败                       }) 

Android 10以上,传入我们定义的Android10DownloadFactory对象的同时,再传入传入线程调度器及进度监听即可,如下:

val factory = Android10DownloadFactory(context, "test.apk")//kotlin 协程
val uri = RxHttp.get("/service/.../xxx.apk")       .toDownload(factory, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小}.await()  //这里返回工厂类构建的Uri对象//RxJava
RxHttp.get("/service/.../xxx.apk")       .asDownload(factory, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小 }.subscribe({                //成功回调,这里返回工厂类构建的Uri对象                      }, {                //异常失败                       }) 

4.3、带进度断点下载

对于断点下载,我们需要调用一系列asAppendDownload、toAppendDownload方法,可以把它们理解为追加下载,实现如下:

Android 10之前

val localPath = "/sdcard/.../xxx.apk"//kotlin 协程
val result = RxHttp.get("/service/.../xxx.apk")       .toAppendDownload(localPath, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小}.await()  //这里返回sd卡存储路径//RxJava
RxHttp.get("/service/.../xxx.apk")       .asAppendDownload(localPath, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小 }.subscribe({                //成功回调,这里返回sd卡存储路径                      }, {                //异常失败                       }) 

在Android 10上,有一点需要注意的是,我们在构建Android10DownloadFactory对象时,需要传入第三个参数queryUri,可以把它理解为要查询的文件夹,断点下载,RxHttp内部会根据文件名在指定的文件夹下查找对应的文件,得到当前文件的长度,也就是断点位置,从而告诉服务端从哪里开始下载,如下:

val queryUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI
//在Download目录下查找test.apk文件
val factory = Android10DownloadFactory(context, "test.apk", queryUri)//kotlin 协程
val uri = RxHttp.get("/service/.../xxx.apk")       .toAppendDownload(factory, Dispatchers.Main) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小}.await()  //这里返回工厂类构建的Uri对象//RxJava
RxHttp.get("/service/.../xxx.apk")       .asAppendDownload(factory, AndroidSchedulers.mainThread()) {//下载进度回调,0-100,仅在进度有更新时才会回调       int currentProgress = it.getProgress() //当前进度 0-100   long currentSize = it.getCurrentSize() //当前已上传的字节大小   long totalSize = it.getTotalSize()     //要上传的总字节大小 }.subscribe({                //成功回调,这里返回工厂类构建的Uri对象              }, {                //异常失败                       }) 

5、如何构建Uri对象?

在上面代码中,我们自定义了Android10DownloadFactory类,其中最为关键的代码就是如何构建一个Uri对象,接下来,就教大家如何去构建一个Uri,马上开始,如下:

public Uri getUri(Context context) {  ContentValues values = new ContentValues();//1、配置文件名                                                          values.put(MediaStore.MediaColumns.DISPLAY_NAME, "1.jpg");//2、配置文件类型values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")//3、配置存储目录values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM); //4、将配置好的对象插入到某张表中,最终得到Uri对象return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}                                                                                                  
  • 第一步,配置文件名称,这个就没啥好说的了

  • 第二步,配置文件类型,每个文件都应该有一个类型描述,这样,后续查找时,就可以根据这个类型去查找出同一类型的文件,如:查找相册,此属性是可选的,如果不配置,后续就无法根据类型查找到这个文件

  • 第三步,配置存储目录,这个是相对路径,总共有10个目录可选,如下:

    • Environment.DIRECTORY_DOCUMENTS  对应路径:/storage/emulated/0/Documents/

    • Environment.DIRECTORY_DOWNLOADS  对应路径:/storage/emulated/0/Download/

    • Environment.DIRECTORY_DCIM  对应路径:/storage/emulated/0/DCIM/

    • Environment.DIRECTORY_PICTURES  对应路径:/storage/emulated/0/Pictures/

    • Environment.DIRECTORY_MOVIES  对应路径:/storage/emulated/0/Movies/

    • Environment.DIRECTORY_ALARMS  对应路径:/storage/emulated/0/Alrams/

    • Environment.DIRECTORY_MUSIC  对应路径:/storage/emulated/0/Music/

    • Environment.DIRECTORY_NOTIFICATIONS  对应路径:/storage/emulated/0/Notifications/

    • Environment.DIRECTORY_PODCASTS  对应路径:/storage/emulated/0/Podcasts/

    • Environment.DIRECTORY_RINGTONES  对应路径:/storage/emulated/0/Ringtones/

如果需要在以上目录下,创建子目录,则传入的时候,直接带上即可,如下

values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/RxHttp");
复制代码
  • 第四步,插入到对应的表中,总共有5张表可选,如下:

    需要特殊说明下,以上5张表中,只能存入对应文件类型的信息,如我们不能将音频文件信息,插入到MediaStore.Images.Media.EXTERNAL_CONTENT_URI图片表中,插入时,系统会直接抛出异常

    • 存储图片:MediaStore.Images.Media.EXTERNAL_CONTENT_URI

    • 存储视频:MediaStore.Video.Media.EXTERNAL_CONTENT_URI

    • 存储音频:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

    • 存储任意文件:MediaStore.Downloads.EXTERNAL_CONTENT_URI

    • 存储任意文件:MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)

注意事项

以上5张表中,除了对插入的文件类型有限制外,还对要插入的相对路径有限制,如,我们将一个apk文件下载/storage/emulated/0/Download/RxHttp/目录下,并插入到图片表中,如下:

public Uri getUri(Context context) {  ContentValues values = new ContentValues();values.put(MediaStore.MediaColumns.DISPLAY_NAME, "1.apk");values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/RxHttp"); return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}         

当执行到insert操作时,系统将会直接报错,报错信息如下:

Primary directory Download not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]

大致意思就是,Download目录不允许插入到MediaStore.Images.Media.EXTERNAL_CONTENT_URI表中,该表只允许插入DCIMPictures目录

6、小结

开源不易,写文章更不易,喜欢的话,还需劳烦大家给本文点个赞,可以的话,再给个star,我将感激不尽,????????????????????????????????????????????????

作者:不怕天黑
链接:https://juejin.im/post/6884986439587594247

RxHttp 完美适配Android 10/11 上传/下载/进度监听相关推荐

  1. Android-封装网络请求Retrofit+Rxjava可实现上传下载进度监听

    1.添加依赖 build.gradle: //gson解析 api rootProject.ext.dependencies["gson"] api rootProject.ext ...

  2. Android OkHttp+RxJava 史上最优雅的实现文件上传/下载进度的监听

    本文已授权「刘望舒」微信公众号独家原创发布 前言 本文将直接使用RxHttp库实现文件上传.下载.断点下载.进度的监听,不对RxHttp做过多讲解,如果对RxHttp不了解,请移步 RxHttp 一条 ...

  3. Android FTP 客户端 上传/下载 带进度条实战源码

    Android FTP 开发,我个人是使用commons-net-3.1.jar,别问我为什么是度娘教我的,附上commons-net-3.1.jar的开源下载地址http://grepcode.co ...

  4. 一行代码实现Okhttp,Retrofit,Glide下载上传进度监听

    2019独角兽企业重金招聘Python工程师标准>>> 发表上篇文章 我一行代码都不写实现Toolbar!你却还在封装BaseActivity? 已是一个月前的事情~ 上篇文章的研究 ...

  5. FTP客户端--实现FTP文件的上传下载功能

    现在是2017.6.16的1点多,这几天刚好做了个FTP客户端的计网实验,就把思路过程和源码发上来吧! 一.设计思路:首先,登陆指定的FTP服务器(指定服务器的IP和用户名,密码,端口号若无就默认为2 ...

  6. 基于OkHttp 、Retrofit 、Volley 、RxJava、Novate多种网络框架整合的快速项目开发框架,一行代码实现Ftp文件上传、文件下载、文件删除和进度监听的工具类的使用

    基于OkHttp .Retrofit .Volley .RxJava.Novate多种网络框架整合的快速项目开发框架,Ftp文件上传.文件下载的工具类的使用. 依赖于Ftp的jar包,对上传.下载.删 ...

  7. Android开发中使用七牛云存储进行图片上传下载

    Android开发中的图片存储本来就是比较耗时耗地的事情,而使用第三方的七牛云,便可以很好的解决这些后顾之忧,最近我也是在学习七牛的SDK,将使用过程在这记录下来,方便以后使用. 先说一下七牛云的存储 ...

  8. Android --- Retrofit 上传/下载文件扩展实现进度的监听

    本文使用okhttp作为client来做,其实说白了跟用okhttp做下载上传进度监听几乎一样,参考了这篇文章:Android OkHttp文件上传与下载的进度监听扩展 1. 首先我们写两个接口用来下 ...

  9. 安卓项目实战之强大的网络请求框架okGo使用详解(一):实现get,post基本网络请求,下载上传进度监听以及对Callback自定义的深入理解

    1.添加依赖 //必须使用 compile 'com.lzy.net:okgo:3.0.4'//以下三个选择添加,okrx和okrx2不能同时使用,一般选择添加最新的rx2支持即可 compile ' ...

最新文章

  1. Maven : 将Jar安装到本地仓库和Jar上传到私服[转]
  2. PostgreSQL 10.1 手册_前言_2. PostgreSQL简史
  3. 【中级软考】前驱图是什么?(貌似又名“前趋图”)
  4. 详解为何在嵌套ESXi环境下要求开启Promiscuous Mode
  5. deeply understanding Binary tree--二叉树
  6. 65. 雇员管理系统(2)
  7. 店宝宝电脑版_母婴店主干货分享:母婴店利润究竟有多大?
  8. 算法4-中兴捧月杯热身赛1素数判断-热身赛2亲和串-热身赛3旅游路线-
  9. vue引入企业微信JS-SDK;企业微信开发步骤;vue开发企业微信;企业微信侧边栏应用开发
  10. 《软考系统架构师》(二、信息系统基础知识)
  11. 在 AWS上域名备案流程和文件
  12. python一行输入两个数据中间用空格隔开
  13. 什么是大小端?怎样判断?
  14. 一体化伺服电机编码器值清零或设置原点如何操作?
  15. 注册国外邮箱须知(ZIP CODE)
  16. python小技巧:求32位二进制负数的补码,附剑指offer中的应用
  17. C#毕业设计——基于C#+asp.net+sqlserver的教务管理平台设计与实现(毕业论文+程序源码)——教务管理平台
  18. 《Storytelling With Data》读书心得1
  19. 影视管理系统-七星修改二开米酷影视7.2完整版源码
  20. 如何学习编程?如何学号C语言?编程学习网站汇总

热门文章

  1. 腾讯帝国的下坡路 | 畅言
  2. 11、异常 异常解决方案
  3. 架构搜索文献笔记(9):《CurveLane-NAS: Unifying Lane-Sensitive Architecture Search and Adaptive Point》
  4. 网易互联网,网易互娱 2021届校招内推开始啦
  5. python函数快查快用
  6. 目前最快速最好最有效的【淡化疤痕的方法】是什么
  7. poscms统计数据调用
  8. 8000字干货:那些很厉害的人是怎么构建知识体系的
  9. Python编程:sh模块执行shell命令
  10. Excel 将两列合并变成第三列,中间加一个连字符