当前位置: 首页 > news >正文

网站备案查询不到辽宁建设工程信息网盲盒系统

网站备案查询不到,辽宁建设工程信息网盲盒系统,广州市官网网站建设怎么样,手机百度建设网站文章目录 协程结构化异常流程协程结构化异常流程和取消流程的区别子协程异常为什么要连带取消父协程#xff1f; CoroutineExceptionHandler异常协程异常的最后一道拦截#xff1a;CoroutineExceptionHandlerCoroutineExceptionHandler 为什么只能设置给最外层协程才有效 CoroutineExceptionHandler异常协程异常的最后一道拦截CoroutineExceptionHandlerCoroutineExceptionHandler 为什么只能设置给最外层协程才有效 async() 对异常的处理SupervisorJob总结 协程的异常很多人都觉得很难[难] 主要体现在两个地方表面原因是它很复杂深层原因是大多数人没有理解协程的异常结构化管理有什么用。 所以接下来我们就从这两个 [难] 的原因由浅到深的讲解协程的结构化异常管理。 下面的内容将会在 协程的结构化取消 的基础上讲解建议提前了解才能更好掌握协程结构化异常流程。 协程结构化异常流程 协程结构化异常流程和取消流程的区别 了解结构化流程我们直接上示例代码 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)scope.launch {println(Parent started)launch {println(Child started)throw RuntimeException()println(Child finished)}println(Parent finished)}delay(10000) }输出结果 Parent started Parent finished Child started // 抛出异常上面的代码在子协程抛出了异常会导致父协程也被取消。更具体的说当协程抛出异常时除了 CancellationException它往上、往下的整个父子协程树全都会因为这个异常而被取消掉。 实际上协程的异常处理和协程的取消用的是同一套逻辑。我们可以通过打印协程的状态验证 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)var childJob: Job? nullval parentJob scope.launch {childJob launch {println(Child started)delay(3000)println(Child done)}delay(1000)throw IllegalStateException(Wrong user!)}// 抛异常前打印状态delay(500)println(before, isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive})println(before, isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled})// 抛异常后打印状态delay(1000)println(after, isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive})println(after, isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled})delay(10000) }输出结果 Child started // 子协程只打印了一行 before, isActive: parent - true, child - true before, isCancelled: parent - false, child - false // 抛出异常 after, isActive: parent - false, child - false after, isCancelled: parent - true, child - true // 抛异常会导致协程都取消上面的代码会延时 500ms 时打印父子协程的状态在父协程 1s 后抛出异常抛异常后再打印状态。可以看到父协程抛出了异常父子协程的 isActive 都打印为 falseisCancelled 都打印为 true说明父子协程都被取消了。 如果我们把异常换成了 CancellationException fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)var childJob: Job? nullval parentJob scope.launch {childJob launch {println(Child started)delay(3000)println(Child done)}delay(1000)throw CancellationException(Wrong user!)}// 抛异常前打印状态delay(500)println(before, isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive})println(before, isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled})// 抛异常后打印状态delay(1000)println(after, isActive: parent - ${parentJob.isActive}, child - ${childJob?.isActive})println(after, isCancelled: parent - ${parentJob.isCancelled}, child - ${childJob?.isCancelled})delay(10000) }输出结果 Child started before, isActive: parent - true, child - true before, isCancelled: parent - false, child - false after, isActive: parent - false, child - false after, isCancelled: parent - true, child - true可以看到除了 CancellationException 不会导致打印异常之外父子协程状态 isActive 都打印 falseisCancelled 都打印 true父子协程的状态更改都是一样的也能印证异常流程和取消流程用的是同一套逻辑。 也可以理解为 当在协程抛出的是 CancellationException 时协程会走简化版的异常流程也就是取消流程。 那么取消流程比异常流程简化掉了什么呢 取消流程的连带取消只是向内的即协程取消只会连带性的取消它的子协程 异常流程的连带取消是双向的不仅会向内取消它的子协程还会向外取消它的父协程每一个被取消的父协程序也会把它们的每个子协程取消直到整个协程树都被取消 每个协程被取消时它的父协程都会调用 childCancelled 函数 JobSupport.ktpublic open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob {// 每个协程被取消的时候它的父协程都会调用这个函数// cause触发子协程取消的那个异常// cancelImpl(cause) 就是取消父协程自己public open fun childCancelled(cause: Throwable): Boolean {if (cause is CancellationException) return true // 协程取消不会取消父协程return cancelImpl(cause) handlesException // 普通异常会取消父协程} }在 childCancelled 函数判断异常类型为 CancellationException 时协程取消不会连带性把父协程取消直接返回 true如果是其他异常时它会执行父协程的取消流程 cancelImpl。 用一个简单的示例验证下 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)scope.launch {launch {// 开启一个孙子协程launch {println(Grand child started)delay(3000)println(Grand child done)}delay(1000)// 在子协程取消throw CancellationException(Child cancelled!)// throw IllegalStateException(User invalid!)}println(Parent started)delay(3000)println(Parent done)}delay(500)delay(10000) }输出结果 // 子协程抛出 CancellationException Parent started Grand child started Parent done // 子协程取消孙子协程没有打印父协程有打印没有被取消// 子协程抛出其他异常 Parent started Grand child started // 父协程和孙子协程都没有打印都被取消了上面的代码在子协程抛出 CancellationException 时也就是子协程被取消了可以看到父协程还是正常打印但在子协程启动的孙子协程后续没有再打印了而抛出其他异常时父协程和孙子协程都没有再打印。 总结下结构化异常流程和结构化取消流程的区别 异常流程的子协程抛异常会导致父协程被取消取消流程只会取消子协程 异常流程只有内部抛异常的方式触发也很好理解调用函数触发异常也太奇怪了取消流程可以从外部 job.cancel() 或者内部抛 CancellationException 取消 异常流程抛的异常除了用来取消协程还会把这个异常暴露给线程世界取消流程抛 CancellationException 只用来取消 子协程异常为什么要连带取消父协程 或许看了上面的代码和示例代码验证后你会很疑惑为什么异常流程除了取消子协程还要取消父协程子协程抛出异常就只会影响它的子协程取消 因为子协程一旦抛异常影响了外部大流程的执行让其他协程的执行失去了意义父协程的执行也失去了意义。 协程的结构化有两个特性 父子协程连带性取消的性质从正常逻辑来讲当一个大流程都被取消的时候它内部的子流程也就直接没用 父协程等待子协程的性质父协程会等待所有子协程都完成之后才完成自己这也是子流程都完成了整个大流程才能算是完成 子流程被取消对外部大流程来说可能是一个正常的事件而已所以子协程的取消并不会导致父协程取消。 但如果子协程是抛异常了非 CancellationException 的异常这通常被理解为子协程坏掉了导致大流程无法完成也就相当于父协程也坏掉了。所以协程因为普通异常而被取消它的父协程也被以这个异常为原因而取消。 简单理解就是子协程的异常通常意味着父协程的损坏所以干脆把父协程取消。 CoroutineExceptionHandler 当我们在协程抛出异常时和在线程内抛异常一样最终会导致崩溃但在协程世界和线程世界之间如果还想捕获异常还有一个方法CoroutineExceptionHandler。 异常协程异常的最后一道拦截CoroutineExceptionHandler 有一些协程的初学者在写协程代码的时候可能会这么写 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)try {scope.launch {throw RuntimeException()}} catch (e: Exception) {//}delay(10000) }输出结果 // 抛出异常上面的代码很简单用 try-catch 包住协程的执行想要捕获协程的异常但是最终发现并没有捕获到。 为什么按上面这么写无法捕获到里面的异常呢我把上面的代码换一下你就明白了 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)try {// 换成了线程线程内部抛异常也是没法捕获thread {throw RuntimeException()}} catch (e: Exception) {//}delay(10000) }我们把协程换成了线程在线程内部抛异常外部的 try-catch 也是无法捕获异常的都在不同的线程了肯定不能捕获到异常。 那么刚才的协程也是同理try-catch 只能捕获协程启动的代码协程启动结束了 try-catch 代码块也就结束了。 协程太简洁了让我们产生了错觉觉得 launch 的大括号里面和外面是同一套流程但实际上协程都是并行的流程是无法互相 try-catch。 当然这也是因为协程是可以套着写启动子协程写多了就会有一个错觉能用 try-catch 捕获异常在线程我们很少像协程一样套着启动线程所以没这个想法 scope.launch {try {launch {}} catch (e: Exception) {}launch {} }如果我们想捕获协程的异常可以用 CoroutineExceptionHandler 传给最外面的父协程 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)val handler CoroutineExceptionHandler { _, exception - println(Caught $exception)}// CoroutineExceptionHandler 设置给最外层的协程scope.launch(handler) {launch {throw RuntimeException(Error!)}}delay(10000) }输出结果 Caught java.lang.RuntimeException: Error!对于一个多层结构的协程树只要给它们最外层的父协程设置一个 CoroutineExceptionHandler它里面所有子协程包括最外层父协程的异常都会集中到它这里来统一处理。 需要注意的是CoroutineExceptionHandler 只能设置到最外面的父协程设置到内层协程是没用的。 CoroutineExceptionHandler 为什么只能设置给最外层协程才有效 在上面我们有提到使用 CoroutineExceptionHandler 必须将它设置给最外层的父协程才能生效捕获到子协程抛出的异常。 为什么只能设置给最外层呢在讲解这个原理之前我们先了解下 Java 提供的 UncaughtExceptionHandler。 fun main() runBlocking {// 对所有线程异常都捕获的 UncaughtExceptionHandlerThread.setDefaultUncaughtExceptionHandler { t, e - println(Caught default: $e)// 记录异常日志收尾后将程序杀死或重启}val thread Thread {throw RuntimeException(Thread error!)}// 针对单个线程的 UncaughtExceptionHandler// 优先级比 Thread.setDefaultUncaughtExceptionHandler 高// 如果线程处理的东西比较独立也可以针对线程设置// 但大多数时候都是用通用的设置 // thread.setUncaughtExceptionHandler { t, e - // println(Caught $e) // }thread.start() }输出结果 Caught default: java.lang.RuntimeException: Thread error!示例代码分别提供了两种方式 thread.setUncaughtExceptionHandler指定线程单独设置线程抛出异常时捕获异常 Thread.setDefaultUncaughtExceptionHandler通用设置对所有线程都捕获异常 设置 UncaughtExceptionHandler 能让线程在抛出异常时被捕获到它主要的作用是在线程发生了意料之外的未知异常线程内没有 try-catch 我们有意识处理的异常时用于记录使用而不是将异常捕获吞掉让程序运行在异常混乱的状态。 UncaughtExceptionHandler 是异常处理的最后一道拦截此时线程已经结束执行无法修复只能收尾后杀死应用或重启。 比如在 Android 抛出异常就会直接 force close也是用的 UncaughtExceptionHandler 捕获异常记录后让应用停止运行。 说完 UncaughtExceptionHandler现在我们说回 CoroutineExceptionHandler。 CoroutineExceptionHandler 其实和针对单个线程设置 thread.setUncaughtExceptionHandler 是一样的没法像线程那样设置 Thread.setDefaultUncaughtExceptionHandler 对全局协程处理。 因为协程是在线程之上的所以有未知异常设置了 CoroutineExceptionHandler 就会在它这里捕获到如果没有设置那也会被线程的 UncaughtExceptionHandler 捕获 fun main() runBlocking {Thread.setDefaultUncaughtExceptionHandler { t, e - println(Caught default: $e)}val scope CoroutineScope(EmptyCoroutineContext)val handler CoroutineExceptionHandler { _, e - println(Caught in Coroutine: $e)}scope.launch(handler) {launch {throw RuntimeException(Error!)}}delay(10000) }输出结果 // scope.launch 设置 CoroutineExceptionHandler Caught in Coroutine: java.lang.RuntimeException: Error!// scope.launch 没设置 CoroutineExceptionhandler Caught default: java.lang.RuntimeException: Error!通常我们使用协程可以独立的完成一个完整的功能在某个子协程抛出异常时能取消掉整个协程树的条件下这在线程是很麻烦的又能将异常汇报到一个地方CoroutineExceptionHandler然后做一些善后工作就很方便。 CoroutineExceptionHandler 的作用就是针对单个协程树的未知异常做善后工作因为注册 CoroutineExceptionHandler 的目的是做善后工作那么自然的它就得在最外层的父协程设置是最方便处理。 async() 对异常的处理 启动协程有两种方式launch 和 async。 在之前的讲解协程结构化异常都是用的 launch不过 async 对异常的处理其实跟 launch 基本上是一致的但还是有一些区别 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)val handler CoroutineExceptionHandler { _, e -println(Caught in Coroutine: $e)}scope.launch(handler) {val deferred async {delay(1000)throw RuntimeException(Error!)}launch {// async 抛出了异常await() 也会抛出异常// 进而在它调用所在的协程也会影响抛出异常try {deferred.await()} catch (e: Exception) {println(Caught in await: $e)}// 验证协程是否被取消try {delay(1000)} catch (e: Exception) {println(Caught in delay $e)}}}delay(10000) }输出结果 // 先触发了 async 抛出的异常而不是 CancellationException // 因为结构化的异常流程还没走完就先提前触发了 RuntimeException Caught in await: java.lang.RuntimeException: Error! Caught in delay kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; jobStandaloneCoroutine{Cancelling}1924f17a Caught in Coroutine: java.lang.RuntimeException: Error!上面的例子用 async 启动了一个协程然后在另一个协程调用 await()当 async 抛异常时调用 deferred.await() 的位置也会抛异常进而让所在的协程也被取消最终最外层协程设置的 CoroutineExceptionHandler 捕获了异常。 但看输出的日志打印deferred.await() 并不是抛出的 CancellationException而是在 async 抛出的 RuntimeException。按照我们对结构化异常流程的理解为什么在 deferred.await() 的 try-catch 捕获的不是 CancellationException 实际上 在 async 里面抛异常时是有 [双重影响] 的它不仅会用这个异常来触发它所在的协程树的结构化异常处理流程取消协程还会直接让它的 await() 调用也抛出这个异常。 async 和 launch 的另一个异常流程的区别是即使 async 作为最外层父协程对 async 设置 CoroutineExceptionHandler 也是没有效果的 fun main() runBlocking {Thread.setDefaultUncaughtExceptionHandler { _, e -println(Caught default: $e)}val scope CoroutineScope(EmptyCoroutineContext)val handler CoroutineExceptionHandler { _, e -println(Caught in Coroutine: $e)}val deferred scope.async (handler) {launch {throw RuntimeException(Error!)}}deferred.await()delay(10000) }输出结果 // 异常被抛到线程世界没有被 CoroutineExceptionHandler 拦截 Caught default: java.lang.RuntimeException: Error! ** async 不会往线程世界抛异常因为 async 抛出的异常要给 await()await() 还是运行在协程的而 launch 会把内部的异常抛给线程世界是因为它已经是整个流程的终点了CoroutineExceptionHandler 只能用在 launch 启动的最外层的父协程**。 SupervisorJob 在了解协程结构化异常管理机制后我们知道子协程在抛出异常时整个协程树都会被连带性的取消 JobSupport.ktpublic open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob {// 每个协程被取消的时候它的父协程都会调用这个函数// cause触发子协程取消的那个异常// cancelImpl(cause) 就是取消父协程自己public open fun childCancelled(cause: Throwable): Boolean {if (cause is CancellationException) return true // 协程取消不会取消父协程return cancelImpl(cause) handlesException // 普通异常会取消父协程} }那么是否会有一种需求子协程在抛出异常时不希望父协程也被连带性取消。为此协程也提供了 SupervisorJob。 SupervisorJob 中的 [Supervisor] 就是主管的意思它和 Job 的区别是它重写了 childCancelled() 直接返回 false Supervisor.ktprivate class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {override fun childCancelled(cause: Throwable): Boolean false }SupervisorJob 的作用是它的子协程因为非 CancellationException 异常取消时父协程不会连带性的被取消。简单说就是 [取消你的时候是你的父协程你抛异常的时候不是你的父协程]。 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)val supervisorJob SupervisorJob()// supervisorJob 作为父协程scope.launch(supervisorJob) {throw RuntimeException(Error!)}delay(1000)println(Parent Job cancelled: ${supervisorJob.isCancelled})delay(10000) }输出结果 // 打印异常 Parent Job cancelled: false // 父协程没有被取消SupervisorJob 在实际使用场景会有两种常见方式。 第一种常见的方式是 SupervisorJob 作为子协程和父协程之间的桥梁类似半链条的方式 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)val parentJob scope.launch {// 加上 coroutineContext.job 是为了让 SupervisorJob 和外部协程是父子关系// 此时 SupervisorJob 是外面协程的子协程也是里面协程的父协程// 这时 SupervisorJob 子协程抛出异常不会影响外部父协程被取消// 外部父协程取消时又能正常取消子协程SupervisorJob 半链条的写法很常用launch(SupervisorJob(coroutineContext.job)) {throw RuntimeException(Error!)}}delay(1000)println(Parent Job cancelled: ${parentJob.isCancelled})delay(10000) }输出结果 Parent Job cancelled: false将 SupervisorJob 传给子协程作为它的父协程同时为了不断开和外部协程的关系SupervisorJob 也传入 coroutineContext.job 让外部协程取消时能正常取消子协程。 第二种方式是将 SupervisorJob 提供给 CoroutineScope val scope CoroutineScope(SupervisorJob()) scope.launch {}用这个 scope 启动的所有协程在抛异常时都不会触发外面的 CoroutineScope 的取消一旦外面的 CoroutineScope 取消scope 启动的所有协程都会被取消。这也符合 SupervisorJob 的特性。 SupervisorJob 在异常管理时也会完全像一个 [最外层父协程] 一样工作 fun main() runBlocking {val scope CoroutineScope(EmptyCoroutineContext)val parentJob scope.launch {val handler CoroutineExceptionHandler { _, e - println(Caught in handler: $e)}// 按照结构化异常管理机制不是最外层父协程设置 CoroutineExceptionHandler 是无效的// 但在这里却生效能捕获了说明 SupervisorJob 充当了 [最外层的父协程]launch(SupervisorJob(coroutineContext.job) handler) {launch {throw RuntimeException(Error!)}}}delay(1000)println(Parent Job cancelled: ${parentJob.isCancelled})delay(10000) }输出结果 Caught java.lang.RuntimeException: Error! Parent Job cancelled: false当子协程抛出异常时SupervisorJob 充当了 [最外层的父协程] 的角色设置 CoroutineExceptionHandler 捕获子协程异常生效了。 SupervisorJob 就是一个 [会取消子协程但不会被子协程取消] 的 Job。 总结 一、协程结构化异常流程 协程的异常处理和协程的取消用的是同一套逻辑 取消流程的连带取消只是向内的即协程取消只会连带性的取消它的子协程 异常流程的连带取消是双向的不仅会向内取消它的子协程还会向外取消它的父协程每一个被取消的父协程序也会把它们的每个子协程取消直到整个协程树都被取消 结构化异常流程和结构化取消流程的区别 异常流程的子协程抛异常会导致父协程被取消取消流程只会取消子协程 异常流程只有内部抛异常的方式触发取消流程可以从外部 job.cancel() 或者内部抛 CancellationException 取消 异常流程抛的异常除了用来取消协程还会把这个异常暴露给线程世界取消流程抛 CancellationException 只用来取消 二、子协程异常为什么要连带取消父协程 子协程一旦抛异常影响了外部大流程的执行让其他协程的执行失去了意义父协程的执行也失去了意义 子流程被取消对外部大流程来说可能是一个正常的事件而已所以子协程的取消并不会导致父协程取消 子协程抛异常非 CancellationException 的异常这通常被理解为子协程坏掉了导致大流程无法完成也就相当于父协程也坏掉了。所以协程因为普通异常而被取消它的父协程也被以这个异常为原因而取消 一句话总结子协程的异常通常意味着父协程的损坏所以干脆把父协程取消。 三、CoroutineExceptionHandler 1、UncaughtExceptionHandler 和 CoroutineExceptionHandler 的类比 在线程分别提供了两种方式拦截异常 thread.setUncaughtExceptionHandler指定线程单独设置线程抛出异常时捕获异常 Thread.setDefaultUncaughtExceptionHandler通用设置对所有线程都捕获异常 UncaughtExceptionHandle 的作用是在线程发生了意料之外的未知异常线程内没有 try-catch 我们有意识处理的异常时用于记录使用而不是将异常捕获吞掉让程序运行在异常混乱的状态。 UncaughtExceptionHandler 是异常处理的最后一道拦截此时线程已经结束执行无法修复只能收尾后杀死应用或重启。 CoroutineExceptionHandler 和针对单个线程设置 thread.setUncaughtExceptionHandler 是一样的没法像线程那样设置 Thread.setDefaultUncaughtExceptionHandler 对全局协程处理。 对于一个多层结构的协程树只要给它们最外层的父协程设置一个 CoroutineExceptionHandler它里面所有子协程包括最外层父协程的异常都会集中到它这里来统一处理。 2、为什么 CoroutineExceptionHandler 只能设置在最外层协程才有效 CoroutineExceptionHandler 的作用就是针对单个协程树的未知异常做善后工作又因为协程结构化异常会有连带性的特性异常流程双向取消并抛异常最终异常会走到最外层的协程让它从协程世界将异常抛到线程世界那么自然的它就得在最外层的父协程设置是最方便处理。 四、async 的异常处理 在 async 里面抛异常时是有 [双重影响] 的它不仅会用这个异常来触发它所在的协程树的结构化异常处理流程取消协程还会直接让它的 await() 调用也抛出这个异常。 async 作为最外层父协程对 async 设置 CoroutineExceptionHandler 也是没有效果的。async 不会往线程世界抛异常因为 async 抛出的异常要给 await()await() 还是运行在协程的而 launch 会把内部的异常抛给线程世界是因为它已经是整个流程的终点了CoroutineExceptionHandler 只能用在 launch 启动的最外层的父协程 五、SupervisorJob SupervisorJob 的作用是它的子协程因为非 CancellationException 异常取消时父协程不会连带性的被取消。简单说就是 [取消你的时候是你的父协程你抛异常的时候不是你的父协程]。 当子协程抛出异常时SupervisorJob 充当了 [最外层的父协程] 的角色设置 CoroutineExceptionHandler 捕获子协程异常会生效。 一句话总结SupervisorJob 就是一个 [会取消子协程但不会被子协程取消] 的 Job。
http://www.w-s-a.com/news/229809/

相关文章:

  • wordpress建站 云打印昆明 网站设计
  • 太原网站建设设计网站建设策划书(建设前的市场分析)
  • 哪里有制作网站电商新手入门知识
  • 制作网站的后台文昌网站建设 myvodo
  • 网站 购买移动网站制作
  • 南京网站网站建设学校英山做网站多少钱
  • 珠海网站建设网如何注册公司公众号
  • 手机网站页面制作网站怎么做快照
  • asp网站怎么仿站推广软件下载平台
  • 电子商务网站建设期末试题08答案互联网怎么做
  • 规范门户网站的建设和管理办法微信网站开发公司电话
  • 免费行情网站凡客的官网
  • 做网站运营的女生多吗海淀企业网站建设
  • 网站运行环境配置网站建设个一般需要花费多少钱
  • 广西平台网站建设报价wordpress 免费 企业 主题
  • 四川省建设厅职称查询网站辽宁省住房和城乡建设部网站
  • 公司网站后台登陆网站放到云服务器上怎么做
  • 济南 网站定制做网站购买域名
  • 代理分佣后台网站开发怎么用源码做网站视频
  • 天津网站建设招标wordpress七牛图片插件
  • 建设合同施工合同示范文本汕头市网络优化推广平台
  • 网站关键词修改老王搜索引擎入口
  • 那个网站做搬家推广比较好建设部网站办事大厅栏目
  • 做企业销售分析的网站广州网站设计建设
  • 建站流程wordpress怎么开伪静态
  • 服务器不是自己的做违法网站videopro wordpress
  • 北京建网站的公司哪个比较好网站开通告知书
  • 网站负责人 主体负责人黑龙江 建设监理协会网站
  • 手机网站焦点图代码建设工程质量检测网站
  • 墙绘做网站推广有作用没html网页制作用什么软件