首先为什么要集成bugly热修复。市面上有其他的热修复框架,为什么就用bugly?这里给出2张图大家就明白了。

引用腾讯bugly官网的一段话:

  • 无需关注Tinker是如何合成补丁的
  • 无需自己搭建补丁管理后台
  • 无需考虑后台下发补丁策略的任何事情
  • 无需考虑补丁下载合成的时机,处理后台下发的策略
  • 我们提供了更加方便集成Tinker的方式
  • 我们提供应用升级一站式解决方案

进入正题:接入流程主要是以下几个步骤:

  • 打基准包安装并上报联网(注:填写唯一的tinkerId)
  • 对基准包的bug修复(可以是Java代码变更,资源的变更)
  • 修改基准包路径、填写补丁包tinkerId、mapping文件路径、resId文件路径
  • 执行tinkerPatchRelease打Release版本补丁包
  • 选择app/build/outputs/patch目录下的补丁包并上传(注:不要选择tinkerPatch目录下的补丁包,不然上传会有问题)
  • 编辑下发补丁规则,点击立即下发
  • 重启基准包,请求补丁策略(SDK会自动下载补丁并合成)
  • 再次重启基准包,检验补丁应用结果

1:新建基准包工程项目(人为制造有BUG的app版本)

[java]  view plain copy
  1. btn.setOnClickListener(new View.OnClickListener() {
  2. @Override
  3. public void onClick(View view) {
  4. /                String str = LoadBugClass.getBugString();
  5. String str = BugClass.bug();
  6. Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
  7. }
  8. });
[java]  view plain copy
  1. public class BugClass {
  2. public static String bug(){
  3. String str = null;
  4. int str_length = str.length();
  5. return "this is bug class";
  6. }
  7. }

这个可以看出点击一个按钮会报空指针异常。

2:接着就是配置相关属性和添加一个插件依赖了。

官方教程地址:点击打开链接

下面也给出我自己配置的过程。

首先在最外层的build.gradle文件中添加依赖,看下图:

其次新建sampleapplication和sampleapplicationLike两个Java类

[java]  view plain copy
  1. package com.henry.testappbugly;
  2. import android.annotation.TargetApi;
  3. import android.app.Application;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.res.AssetManager;
  7. import android.content.res.Resources;
  8. import android.os.Build;
  9. import android.support.multidex.MultiDex;
  10. import com.tencent.bugly.Bugly;
  11. import com.tencent.bugly.beta.Beta;
  12. import com.tencent.tinker.loader.app.DefaultApplicationLike;
  13. /**
  14. * Created by W61 on 2016/11/29.
  15. */
  16. public class SampleApplicationLike extends DefaultApplicationLike {
  17. public static final String TAG = "Tinker.SampleApplicationLike";
  18. public SampleApplicationLike(Application application, int tinkerFlags,
  19. boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
  20. long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,
  21. ClassLoader[] classLoader, AssetManager[] assetManager) {
  22. super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
  23. applicationStartMillisTime, tinkerResultIntent, resources, classLoader,
  24. assetManager);
  25. }
  26. @Override
  27. public void onCreate() {
  28. super.onCreate();
  29. // 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
  30. Bugly.init(getApplication(), "", true);
  31. }
  32. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  33. @Override
  34. public void onBaseContextAttached(Context base) {
  35. super.onBaseContextAttached(base);
  36. // you must install multiDex whatever tinker is installed!
  37. MultiDex.install(base);
  38. // 安装tinker
  39. // TinkerManager.installTinker(this); 替换成下面Bugly提供的方法
  40. Beta.installTinker(this);
  41. }
  42. @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
  43. public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
  44. getApplication().registerActivityLifecycleCallbacks(callbacks);
  45. }
  46. }
[java]  view plain copy
  1. package com.henry.testappbugly;
  2. import com.tencent.tinker.loader.app.TinkerApplication;
  3. import com.tencent.tinker.loader.shareutil.ShareConstants;
  4. /**
  5. * Created by W61 on 2016/11/29.
  6. */
  7. public class SampleApplication extends TinkerApplication {
  8. public SampleApplication() {
  9. super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike所在的包名路径",
  10. "com.tencent.tinker.loader.TinkerLoader", false);
  11. }
  12. }

在在Androidmanifest.xml文件中配置权限及application类名

[java]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.henry.testappbugly">
  4. <application
  5. android:name=".SampleApplication"
  6. android:allowBackup="true"
  7. android:icon="@mipmap/ic_launcher"
  8. android:label="@string/app_name"
  9. android:supportsRtl="true"
  10. android:theme="@style/AppTheme">
  11. <activity android:name=".MainActivity">
  12. <intent-filter>
  13. <action android:name="android.intent.action.MAIN" />
  14. <category android:name="android.intent.category.LAUNCHER" />
  15. </intent-filter>
  16. </activity>
  17. <!--API 24以上配置-->
  18. <provider
  19. android:name="android.support.v4.content.FileProvider"
  20. android:authorities="com.tencent.bugly.hotfix.fileProvider"
  21. android:exported="false"
  22. android:grantUriPermissions="true">
  23. <meta-data
  24. android:name="android.support.FILE_PROVIDER_PATHS"
  25. android:resource="@xml/provider_paths"/>
  26. </provider>
  27. </application>
  28. <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  29. <uses-permission android:name="android.permission.INTERNET" />
  30. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  31. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  32. <uses-permission android:name="android.permission.READ_LOGS" />
  33. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  34. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  35. </manifest>

在到res目录下:

[java]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <paths xmlns:android="http://schemas.android.com/apk/res/android">
  3. <!-- 这里配置的两个外部存储路径是升级SDK下载的文件可能存在的路径 -->
  4. <!-- /storage/emulated/0/Download/com.bugly.upgrade.demo/.beta/apk-->
  5. <external-path name="beta_external_path" path="Download/"/>
  6. <!--/storage/emulated/0/Android/data/com.bugly.upgrade.demo/files/apk/-->
  7. <external-path name="beta_external_files_path" path="Android/data/"/>
  8. </paths>

在到app目录下新建:

[java]  view plain copy
  1. # you can copy the tinker keep rule at
  2. # build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
  3. -keep class com.tencent.tinker.loader.** {
  4. *;
  5. }
  6. -keep class com.tencent.bugly.hotfix.SampleApplication {
  7. *;
  8. }
  9. -keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {
  10. *;
  11. }
  12. -keep public class * extends com.tencent.tinker.loader.TinkerLoader {
  13. *;
  14. }
  15. -keep public class * extends com.tencent.tinker.loader.app.TinkerApplication {
  16. *;
  17. }
  18. # here, it is your own keep rules.
  19. # you must be careful that the class name you write won't be proguard
  20. # but the tinker class above is OK, we have already keep for you!

然后在混淆文件.pro中添加这几句代码(bugly都有说明解释)

[java]  view plain copy
  1. -dontwarn com.tencent.bugly.**
  2. -keep public class com.tencent.bugly.**{*;}

最后就是app目录下的build.gradle文件配置了:

[java]  view plain copy
  1. apply plugin: 'com.android.application'
  2. dependencies {
  3. compile fileTree(include: ['*.jar'], dir: 'libs')
  4. compile 'com.android.support:appcompat-v7:24.1.1'
  5. // 多dex配置
  6. compile "com.android.support:multidex:1.0.1"
  7. // 集成Bugly热更新aar(灰度时使用方式)
  8. //    compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
  9. compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
  10. }
  11. android {
  12. compileSdkVersion 23
  13. buildToolsVersion "23.0.2"
  14. // 编译选项
  15. compileOptions {
  16. sourceCompatibility JavaVersion.VERSION_1_7
  17. targetCompatibility JavaVersion.VERSION_1_7
  18. }
  19. // recommend
  20. dexOptions {
  21. jumboMode = true
  22. }
  23. // 签名配置
  24. signingConfigs {
  25. // 签名配置
  26. signingConfigs {
  27. release {
  28. try {
  29. storeFile file("./keystore/release.keystore")
  30. storePassword "testres"
  31. keyAlias "testres"
  32. keyPassword "testres"
  33. } catch (ex) {
  34. throw new InvalidUserDataException(ex.toString())
  35. }
  36. }
  37. debug {
  38. storeFile file("./keystore/debug.keystore")
  39. }
  40. }
  41. }
  42. defaultConfig {
  43. applicationId "com.henry.testappbugly"
  44. minSdkVersion 14
  45. targetSdkVersion 23
  46. versionCode 2
  47. versionName "2.0"
  48. // 开启multidex
  49. multiDexEnabled true
  50. // 以Proguard的方式手动加入要放到Main.dex中的类
  51. multiDexKeepProguard file("keep_in_main_dex.txt")
  52. }
  53. buildTypes {
  54. release {
  55. minifyEnabled true
  56. signingConfig signingConfigs.release
  57. proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
  58. }
  59. debug {
  60. debuggable true
  61. minifyEnabled false
  62. signingConfig signingConfigs.debug
  63. }
  64. }
  65. sourceSets {
  66. main {
  67. jniLibs.srcDirs = ['libs']
  68. }
  69. }
  70. repositories {
  71. flatDir {
  72. dirs 'libs'
  73. }
  74. }
  75. lintOptions {
  76. checkReleaseBuilds false
  77. abortOnError false
  78. }
  79. }
  80. def gitSha() {
  81. try {
  82. String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
  83. if (gitRev == null) {
  84. throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
  85. }
  86. return gitRev
  87. } catch (Exception e) {
  88. throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
  89. }
  90. }
  91. def bakPath = file("${buildDir}/bakApk/")
  92. /**
  93. * you can use assembleRelease to build you base apk
  94. * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
  95. * add apk from the build/bakApk
  96. */
  97. ext {
  98. // for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
  99. tinkerEnabled = true
  100. // for normal build
  101. // old apk file to build patch apk
  102. tinkerOldApkPath = "${bakPath}/app-release-1201-09-46-25.apk"
  103. // proguard mapping file to build patch apk
  104. tinkerApplyMappingPath = "${bakPath}/app-release-1201-09-46-25-mapping.txt"
  105. // resource R.txt to build patch apk, must input if there is resource changed
  106. tinkerApplyResourcePath = "${bakPath}/app-release-1201-09-46-25-R.txt"
  107. // only use for build all flavor, if not, just ignore this field
  108. tinkerBuildFlavorDirectory = "${bakPath}/app-release-1201-09-46-25"
  109. }
  110. def getOldApkPath() {
  111. return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
  112. }
  113. def getApplyMappingPath() {
  114. return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
  115. }
  116. def getApplyResourceMappingPath() {
  117. return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
  118. }
  119. def getTinkerIdValue() {
  120. return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
  121. }
  122. def buildWithTinker() {
  123. return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
  124. }
  125. def getTinkerBuildFlavorDirectory() {
  126. return ext.tinkerBuildFlavorDirectory
  127. }
  128. /**
  129. * 更多Tinker插件详细的配置,参考:https://github.com/Tencent/tinker/wiki
  130. */
  131. if (buildWithTinker()) {
  132. // 依赖tinker插件
  133. apply plugin: 'com.tencent.tinker.patch'
  134. apply plugin: 'com.tencent.bugly.tinker-support'
  135. tinkerSupport {
  136. }
  137. // 全局信息相关配置项
  138. tinkerPatch {
  139. oldApk = getOldApkPath() //必选, 基准包路径
  140. ignoreWarning = false // 可选,默认false
  141. useSign = true // 可选,默认true, 验证基准apk和patch签名是否一致
  142. // 编译相关配置项
  143. buildConfig {
  144. applyMapping = getApplyMappingPath() //  可选,设置mapping文件,建议保持旧apk的proguard混淆方式
  145. applyResourceMapping = getApplyResourceMappingPath() // 可选,设置R.txt文件,通过旧apk文件保持ResId的分配
  146. tinkerId = "可以是签名版本号字符串等等比如:assdhfkdshfksdhfuksfhuk" // 必选,默认为null
  147. }
  148. // dex相关配置项
  149. dex {
  150. dexMode = "jar" // 可选,默认为jar
  151. usePreGeneratedPatchDex = true // 可选,默认为false
  152. pattern = ["classes*.dex",
  153. "assets/secondary-dex-?.jar"]
  154. // 必选
  155. loader = ["com.tencent.tinker.loader.*",
  156. "SampleApplication所在的全路径",
  157. ]
  158. }
  159. // lib相关的配置项
  160. lib {
  161. pattern = ["lib/armeabi/*.so"]
  162. }
  163. // res相关的配置项
  164. res {
  165. pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
  166. ignoreChange = ["assets/sample_meta.txt"]
  167. largeModSize = 100
  168. }
  169. // 用于生成补丁包中的'package_meta.txt'文件
  170. packageConfig {
  171. configField("patchMessage", "tinker is sample to use")
  172. configField("platform", "all")
  173. configField("patchVersion", "1.0")
  174. }
  175. // 7zip路径配置项,执行前提是useSign为true
  176. sevenZip {
  177. zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
  178. //  path = "/usr/local/bin/7za" // optional
  179. }
  180. }
  181. List<String> flavors = new ArrayList<>();
  182. project.android.productFlavors.each { flavor ->
  183. flavors.add(flavor.name)
  184. }
  185. boolean hasFlavors = flavors.size() > 0
  186. /**
  187. * bak apk and mapping
  188. */
  189. android.applicationVariants.all { variant ->
  190. /**
  191. * task type, you want to bak
  192. */
  193. def taskName = variant.name
  194. def date = new Date().format("MMdd-HH-mm-ss")
  195. tasks.all {
  196. if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {
  197. it.doLast {
  198. copy {
  199. def fileNamePrefix = "${project.name}-${variant.baseName}"
  200. def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"
  201. def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
  202. from variant.outputs.outputFile
  203. into destPath
  204. rename { String fileName ->
  205. fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
  206. }
  207. from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
  208. into destPath
  209. rename { String fileName ->
  210. fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
  211. }
  212. from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
  213. into destPath
  214. rename { String fileName ->
  215. fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
  216. }
  217. }
  218. }
  219. }
  220. }
  221. }
  222. project.afterEvaluate {
  223. //sample use for build all flavor for one time
  224. if (hasFlavors) {
  225. task(tinkerPatchAllFlavorRelease) {
  226. group = 'tinker'
  227. def originOldPath = getTinkerBuildFlavorDirectory()
  228. for (String flavor : flavors) {
  229. def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
  230. dependsOn tinkerTask
  231. def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
  232. preAssembleTask.doFirst {
  233. String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
  234. project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
  235. project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
  236. project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"
  237. }
  238. }
  239. }
  240. task(tinkerPatchAllFlavorDebug) {
  241. group = 'tinker'
  242. def originOldPath = getTinkerBuildFlavorDirectory()
  243. for (String flavor : flavors) {
  244. def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
  245. dependsOn tinkerTask
  246. def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
  247. preAssembleTask.doFirst {
  248. String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
  249. project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
  250. project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
  251. project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
  252. }
  253. }
  254. }
  255. }
  256. }
  257. }

最后run as生成有bug的基准包app

在去腾讯bugly官网将这个基准包上传上去即可。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来,制作补丁包。

由于刚才点击按钮报空指针,下面将代码稍做改动如下:

[java]  view plain copy
  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. btn = (Button) findViewById(R.id.btn);
  5. btn.setOnClickListener(new View.OnClickListener() {
  6. @Override
  7. public void onClick(View view) {
  8. String str = LoadBugClass.getBugString();
  9. //                String str = BugClass.bug();
  10. Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
  11. }
  12. });
  13. }
[java]  view plain copy
  1. public class LoadBugClass {
  2. /**
  3. * 获取bug字符串.
  4. *
  5. * @return 返回bug字符串
  6. */
  7. public static String getBugString() {
  8. //        BugClass bugClass = new BugClass();
  9. return "iS OK";
  10. }
  11. }

这样点击按钮就会弹出is ok了不会报错。

修改配置文件:

这里注意点,补丁包是基于基准包所生成的patch文件并不是版本升级,所以此处补丁包不需要修改versioncode,versionname

然后双击下图中所指地方:

稍等片刻就会出现下图中类容:

其中的patch_signed_7zip.apk就是补丁包了。将这个补丁包上传到腾讯bugly即可。

注意:上传完补丁包点击了立即下发,就需要重新启动基准包策略。从有bug版本的app到修复有一个时间差的。估计1到2分钟左右才能看到效果。

附上集成过程中可能遇到的坑解决办法地址:点击打开链接

最后附上自己写的demo地址:点击打开链接

集成腾讯bugly的热修复功能sdk步骤相关推荐

  1. 热修复——Bugly让热修复变得如此简单

    一.简述 在上一篇<热修复--Tinker的集成与使用>中,根据Tinker官方Wiki集成了Tinker,但那仅仅只是本地集成,有一个重要的问题没有解决,那就是补丁从服务器下发到用户手机 ...

  2. Android-第三方开源框架:Bugly让热修复变得如此简单

    作者:GitLqr 纸上说来终觉浅,时间比较充裕的小伙伴建议去B站观看视频讲解:Android第三方开源库系列-热修复框架使用.原理及项目实战(已完结) 一.简述 在上一篇<热修复--Tinke ...

  3. 如何使用阿里百川hotfix热修复功能(三)

    这章我们进行patch补丁操作,参考 :  如何使用阿里百川hotfix热修复功能(一) 如何使用阿里百川hotfix热修复功能(二) 1.下载打包工具 patch补丁包生成需要使用到打补丁工具BCF ...

  4. Java集成腾讯云音视频录制功能

    Java集成腾讯云音视频录制功能 为什么要实现音视频录制功能 因为我们做的是一个医院的项目,医生和患者可能进行视频通话和语音通话,为了保证通话的质量以及后续的问题, 我们就需要进行音视频录制,以便后续 ...

  5. Bugly 之热修复学习

    bugly 官网:https://bugly.qq.com/v2/index 注册流程我就不写了,直接写集成步骤: 1) 在 Projet 的build.gradle 里面 导入classPtah: ...

  6. 安卓开发腾讯Bugly热修复集成和使用思路

    文章目录 一,官方集成 一.获取App ID 二.添加插件依赖 三.集成SDK 四.配置Tinker 1.overrideTinkerPatchConfiguration 2.baseApkDir 3 ...

  7. 一步步手动实现热修复(一)-dex文件的生成与加载

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 热修复技术自从QQ空间团队搞出来之后便渐渐趋于成熟. 我们这个系列主要介绍如何一步步手动实现基本的热修复功能,无需使用第三方框架. ...

  8. 深入解析阿里Android热修复技术原理

    前言:本文框架 什么是热修复? 热修复框架分类 技术原理及特点 Tinker框架解析 各框架对比图 总结 通过阅读本文,你会对热修复技术有更深的认知,本文会列出各类框架的优缺点以及技术原理,文章末尾简 ...

  9. Android 热修复之DexPatch 介绍

    简介:Android 热修复之DexPatch 介绍 1. 方案介绍 为了解决Native模块上线后的问题,mPaas[1] 提供了热修复功能,实现不发布客户端apk场景下的热修复.目前Android ...

最新文章

  1. 最全Pycharm教程(43)——Pycharm扩展功能之UML类图使用 代码结构
  2. 设置VSCode自动保存
  3. Linux进程管理: 多进程编程
  4. 代码详解 | 用Pytorch训练快速神经网络的9个技巧
  5. mysql unix 安装教程_在UNIX系统下安装MySQL_MySQL
  6. 经典php代码,10个非常经典的php代码片段.doc
  7. 64 bit Ubuntu support 32 bit binary
  8. stp协议c语言,STP(生成树协议)
  9. find命令---Linux学习笔记
  10. 外刊评终极平板电脑十大功能:防眩目屏幕在列
  11. 探讨C#中字符串的加密
  12. 第一季5:Hi3518EV200的环境搭建
  13. Html Picture
  14. 关于ng-class中添加多个样式类的解决方案
  15. [Swust OJ 632]--集合运算(set容器)
  16. [转载]VHDL的testbench的编写
  17. webWMS开发过程记录(三)- 需求分析(略)
  18. Layer 提示框tips使用(批量提示)
  19. 北斗导航 | BDS RTK高精度定位算法在形变检测中的应用(算法原理讲解)
  20. 华为EC6108V9C/ E6108V9强刷固件及教程

热门文章

  1. (1) iphone开发,自定义Window-based Application 模板及委托运行机制
  2. 公司名称变更后原因的名字能马上使用吗?企业名称变更所需材料
  3. js mysql query_nodejs mysql query data
  4. 自定义View和控件
  5. 阿里飞猪简历面 20.3.11
  6. 18个为人处世的心机,学会一半做个有城府高情商的人
  7. photoswipe.js——移动端图片文字放大缩小
  8. 马云回忆往昔:改变人生从走出国门开始
  9. Echarts屏幕缩放自适应
  10. MATLAB三角消元法,matlab中用的高斯消元法怎么使用!