jetpack 协程_Jetpack常用组件使用_weixin_39610956的博客-程序员ITS304

技术标签: jetpack 协程  

Jetpack是Google官方推出的一套Android库,它帮助开发者更方便、更快速的开发出稳健性极佳的软件,简化开发流程与提高效率。Jetpack库常用的如下几个组件,它们都可以单独使用或者组合使用:

组建名称

介绍

Android KTX

Kotlin扩展程序,包括扩展函数、扩展属性、协程等

AppCompat

提供向后兼容性的Android组件

WorkManager

管理应用退出或设备重启时仍应运行等可延迟异步任务

Room

Sqlite数据库持久化抽象层

ViewModel

以注重生命周期的方式存储和管理界面相关的数据

LiveData

可观察到数据存储器类,在发生改变时自动更新UI

Android KTX

Android KTX分为多个模块,每个模块都含有一个或多个软件包。Android KTX包含一个核心模块,这个模块为通用框架提供Kotlin扩展程序。如果要在项目中使用核心模块,需要在build.gradle中声明以下依赖:

dependencies {

implementation "androidx.core:core-ktx:1.3.0"

}

Android KTX中还含有下面几个模块,它们的依赖声明如下:

dependencies {

// Collection KTX

implementation "androidx.collection:collection-ktx:1.1.0"

// Fragment KTX

implementation "androidx.fragment:fragment-ktx:1.2.5"

// Lifecycle KTX

implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

// LiveData KTX

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

// Room KTX

implementation "androidx.room:room-ktx:2.2.5"

// SQLite KTX

implementation "androidx.sqlite:sqlite-ktx:2.1.0"

// ViewModel KTX

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

// WorkManager KTX

implementation "androidx.work:work-runtime-ktx:2.3.4"

// Navigation KTX

implementation "androidx.navigation:navigation-runtime-ktx:2.3.0-rc01"

implementation "androidx.navigation:navigation-fragment-ktx:2.3.0-rc01"

implementation "androidx.navigation:navigation-ui-ktx:2.3.0-rc01"

// Palette KTX

implementation "androidx.palette:palette-ktx:1.0.0"

// Reactive KTX

implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.2.0"

}

ViewModel

当程序退出或者屏幕发生旋转导致软件方向发生变化时,Activity或Fragment都会被销毁或者重新创建,此时存储在其中的任何临时性页面相关的数据都会丢失,虽然可以通过Bundle对此类数据进行存储,但是这种方法仅仅适合可以序列化再反序列化的少量数据,而不适合数量较大的数据。此外,Activity或者Fragment常常需要异步调用(如先需要联网下载资源后再显示在页面中),这些调用可能需要一些时间才能返回结果。Activity或Fragment如果还负责加载数据,对用户操作进行响应或处理系统通信,会使得类越发膨胀,导致后续的维护及其困难,所以有必要将界面与数据控制分离。

使用ViewModel与LiveData等具有生命周期功能等组件时,可以直接在build.gradle中添加以下依赖:

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

// 例子中还使用了协程

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

ViewModel类主要负责为界面准备数据,在界面发生重新创建时会自动保留ViewModel对象,以便它们存储的数据立即可供下一个Activity或Fragment使用。ViewModel创建如下所示:

data class User(val name: String = "")

class UserViewModel : ViewModel() {

private val users: MutableLiveData> by lazy {

MutableLiveData>().also {

loadUsers()

}

}

fun getUserList() = users

private fun loadUsers() {

// 加载用户

}

}

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

val model: UserViewModel = ViewModelProviders.of(this).get(UserViewModel::class.java)

// 或者用 val model: UserViewModel by viewModels() 这是activity-ktx的Kotlin属性委派

model.getUserList().observe(this, Observer { users ->

// 更新界面

textView.text = "${it[0].name} - ${it[1].name} - ${it[2].name}"

})

}

}

需要注意的是ViewModel中不能引用视图、Lifecycle或可能存储对Activity上下文的引用的任何类,这是因为ViewModel的生命周期比试图或者LifecycleOwners(在这个例子中是我们传给ViewModelProvides.of()函数的对象)更长,所以一旦含有视图等等引用,可能会导致内存泄露。ViewModel生命周期如下:

ViewModel生命周期

上面的UserViewModel中并没有构造参数,如果我们想要为ViewModel的构造传入参数的话,可以实现我们自己的Factory,并将其作为参数传入到ViewModelProviders.of()中就可以,对类进行以下改造:

class UserViewModel(private val usersCache: List) : ViewModel() {

private val users: MutableLiveData> by lazy {

MutableLiveData>().also {

loadUsers()

}

}

fun getUserList() = users

private fun loadUsers() {

// 模拟加载用户

GlobalScope.launch(Dispatchers.Main) {

withContext(Dispatchers.IO) {

delay(1000L)

}

users.value = listOf(User("a"), User("b"), User("c"))

}

}

}

// 实现自己的ViewModelFactory

class UserViewModelFactory(private val users: List) : ViewModelProvider.Factory {

override fun create(modelClass: Class): T {

return UserViewModel(users) as T

}

}

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

val cache = listOf(User("x"), User("y"), User("z"))

val model: UserViewModel = ViewModelProviders.of(this, UserViewModelFactory(cache)).get(UserViewModel::class.java)

model.getUserList().observe(this, Observer {

textView.text = "${it[0].name} - ${it[1].name} - ${it[2].name}"

})

}

}

这个例子的效果是界面首先显示x - y - z,经过一秒后变为a - b - c。

ViewModelProviders.of()在新版本中已被废弃,可以直接使用ViewModelProvider()获取。

如果ViewModel需要Application上下文,那么可以继承AndroidViewModel,它接收Application作为参数。

LiveData

LiveData是一种可观察的数据存储类,它和普通的可观察类的不同之处在于它拥有生命周期感知能力,这种能力可以确保LiveData仅更新处于活跃生命周期状态(即处于STARTED或RESUMED状态)的应用组件观察者。

使用LiveData的步骤如下:

创建LiveData实例来存储某种数据,通常放在ViewModel中;

创建可定义onChanged()方法的Observer对象,该方法会在LiveData对象存储的数据发生变化时被调用,通过是在Activity中或者Fragment中创建Observer对象;

使用LiveData.observe()方法将第二步创建的Observer对象附加到LiveData对象上,并传入一个LifecycleOwner(通常是Activity或Fragment);

当更新存储在LiveData中的数据时,它会自动触发所有已经注册的观察者(只要这个观察者处于活跃状态)进行更新。可以看一下上面ViewModel中的例子。

大部分情况下,在组件的onCreate()方法中开始观察LiveData对象是比较正确的方法,这样做不仅可以确保系统不会从Activity或Fragement的onResume()方法进行冗余调用,还可以确保Activity或Fragement变为活跃状态后具有可以立刻要显示的数据。

LiveData规范的用法如下:

class UserViewModel(userCache: List) : ViewModel() {

private val _users = MutableLiveData>()

val users: LiveData>

get() = _users

// 或者

fun getUserList(): LiveData> = _users

}

它与上面的例子不同之处在于ViewModel仅仅对外暴露了不可变的LiveData,而不是MutableLiveData,这样可以保证View层无法直接对LiveData进行修改,而只能通过ViewModel中的方法进行数据更新。

使用Transformations.map()函数可以将LiveData中存储的数据应用函数传到另外一个LiveData对象中,该函数返回LiveData中存储的数据类型:

val userLiveData: LiveData> = UserLiveData()

val userNameLiveData: LiveData> = Transformations.map(users) { userList ->

val userNames = mutableListOf()

userList.forEach {

userNames.add(it.name)

}

userNames

}

使用Transformations.switchMap()函数可以将LiveData对象解封并应用函数,该函数参数必须返回LiveData类型:

val userNameLiveData: LiveData> = Transformations.switchMap(users) { userList ->

val result = MutableLiveData>()

val userNames = mutableListOf()

userList.forEach {

userNames.add(it.name)

}

result.value = userNames

result

}

MediatorLiveData是LiveData的子类,允许合并多个LiveData源。只要任何原始的LiveData源对象发生更改,就会触发 MediatorLiveData 对象的观察者更新。上面的map()函数与switchMap()返回的实际上都是MediatorLiveData。

从接口声明上来看我们似乎可以改写map使其功能和switchMap类似,这种思路在同步代码上是可行的,但是异步代码下用map代替switchMap会报错。下面是一个完整的例子,使用Retrofit2库进行网络请求并将结果显示在页面上:

依赖如下:

implementation 'androidx.core:core-ktx:1.3.0'

implementation 'androidx.appcompat:appcompat:1.1.0'

implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'

implementation 'com.squareup.retrofit2:retrofit:2.6.1'

implementation 'com.squareup.retrofit2:converter-gson:2.6.1'

ViewModel类文件:

data class Update(val version: String = "1.0.0",

@SerializedName("download_url") val downloadUrl: String = "",

@SerializedName("update_content") val updateContent: String = "",

@SerializedName("have_update") val haveUpdate: String = "0")

class UpdateViewModel(cache: Update) : ViewModel(){

private var currVersion: MutableLiveData = MutableLiveData(cache)

var click = false

val latestVersion: LiveData = Transformations.switchMap(currVersion) {

if (click) {

Repository.checkUpdate(it.version)

} else {

MutableLiveData(cache)

}

}

fun getLatestVersion(version: Update) {

click = true

currVersion.value = version

}

}

class UpdateViewModelFactory : ViewModelProvider.Factory {

override fun create(modelClass: Class): T {

return UpdateViewModel(Update()) as T

}

}

object Repository {

fun checkUpdate(version: String): LiveData {

val liveData = MutableLiveData()

GlobalScope.launch(Dispatchers.Main) {

val deferred = async(Dispatchers.IO) {

val retrofit = Retrofit.Builder()

.baseUrl("https://allpass.aengus.top/api/")

.addConverterFactory(GsonConverterFactory.create())

.build()

val appService = retrofit.create(AppService::class.java)

appService.getAll(version).await()

}

liveData.value = deferred.await()

}

return liveData

}

页面文件:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

val model: UpdateViewModel = ViewModelProvider(this, UpdateViewModelFactory()).get(UpdateViewModel::class.java)

model.latestVersion.observe(this, Observer {

textView.text = "${it.version}"

})

get_button.setOnClickListener {

model.getLatestVersion(Update("1.0.0"))

}

}

}

软件运行时会在页面显示“1.0.0”,点击按钮后变为“1.2.2”。

Room

Room是在SQLite的基础上的抽象层,Room使用起来非常像Mybatis,通常的使用方式包含创建数据类(与数据库表一一对应),创建DAO编写SQL语句,创建数据库管理DAO,创建Repository控制数据库。用法如下:

一、创建数据类。下面注解中的tableName和name都可以省略,若省略则默认和类名与属性名相同;

@Entity(tableName = "users")

data class User(

@PrimaryKey val id: Int,

@ColumnInfo(name = "username") val username: String = "",

@ColumnInfo(name = "password") val password: String = ""

)

二、创建DAO。

@Dao

interface UserDao {

@Query("SELECT * FROM users")

fun getAllUsers(): LiveData> // Room内有对LiveData的支持

@Insert

fun insert(user: User)

@Insert

fun insertAll(vararg users: User)

@Update

fun update(user: User)

@Delete

fun delete(user: User)

}

三、创建数据库类。version指定数据库版本,将来升级时基于此。

@Database(entities = [User::class], verson = 1)

abstract class UserDatabase : RoomDatabase() {

abstract fun userDao(): UserDao

companion object {

@Volatile

private var INSTANCE: UserDatabase? = null

fun getDatabase(context: Context, scope: CoroutineScope): UserDatabase {

return INSTANCE ?: synchronized(this) {

val instance = Room.databaseBuilder(

context.applicationContext,

UserDatabase::class.java,

name = "users_demo"

).build()

INSTANCE = instance

return instance

}

}

}

}

四、创建Repository。

class UserRepository(private val userDao: UserDao) {

val userList = userDao.getAllUsers()

fun insert(user: User) = userDao.insert(user)

fun update(user: User) = userDao.update(user)

fun delete(user: User) = userDao.delete(user)

}

五、使用。下面的AndroidViewModel在前面说过。

class UserViewModel(application) : AndroidViewModel(application) {

private val repository: UserRepository

val usersLiveData: LiveData

init {

val userDao = UserDatabase.getDatabase(application, viewModelScope).userDao()

repository = UserRepository(userDao)

userLiveData = repository.userList

}

fun insert(user: User) = viewModelScope.launch(Dispatchers.IO) {

repository.insert(user)

}

fun update(user: User) = viewModelScope.launch(Dispatchers.IO) {

repository.update(user)

}

fun delete(user: User) = viewModelScope.launch(Dispatchers.IO) {

repository.delete(user)

}

}

Room数据库升级的步骤是:首先需要更改数据库版本,然后定义一个Migration对象,并在databaseBuilder()后运行:

@Database(entities = [User::class], verson = 1)

abstract class UserDatabase : RoomDatabase() {

abstract fun userDao(): UserDao

val MIGRATION_1_2: Migration = object : Migration(1, 2) {

override fun migrate(database: SupportSQLiteDatabase) {

database.execSQL("ALTER TABLE users ADD COLUMN age INT")

}

}

companion object {

@Volatile

private var INSTANCE: UserDatabase? = null

fun getDatabase(context: Context, scope: CoroutineScope): UserDatabase {

return INSTANCE ?: synchronized(this) {

val instance = Room.databaseBuilder(

context.applicationContext,

UserDatabase::class.java,

name = "users_demo"

)

.addMigrations(MIGRATION_1_2) // 在此使用Migration对象

.build()

INSTANCE = instance

return instance

}

}

}

}

利用同样的方法可以定义从2-3,3-4,或者1-4的数据库版本迁移。

除了addMigrations(vararg migrations: Migration)方法,还有fallbackToDestructiveMigrationFrom(varargs startVersions: Int)与fallbackToDestructiveMigration(),这两个函数允许Room当要求的数据库版本与实际的数据库版本不同时破坏性的重新创建数据库表,不同之处在于前者可以从指定版本号的数据库表进行迁移。

WorkManager

WorkManager可以用来管理即使在应用退出后或者设备重启时仍运行的任务,支持一次性或周期性任务,并可以添加网络可用性或充电状态等约束,遵循省电模式等功能。WorkManager不适合应用结束后进行安全终止(如应用数据保存)的后台工作,也不适合需要立刻执行的工作。

要使用WorkManager,需要在build.gradle中添加如下依赖:

implementation "androidx.work:work-runtime:2.3.4" // Java

implementation "androidx.work:work-runtime-ktx:2.3.4" // Kotlin + coroutines

使用WorkManager,常常有如下几个步骤:

一、创建任务Worker,重写doWork()函数返回Result,失败使用Result.failure(),需要重试使用Result.retry()。

class MyWorker(appContext: Context, workerParams: WorkerParams) : Worker(appContext, workerParams) {

override fun doWork(): Result {

// 做一些工作

return Result.success() // 任务成功

}

}

二、配置运行任务的方式和时间。

val myWorkRequest = OneTimeWorkRquestBuilder() // 一次性的

val anotherWorkRequest = PeriodicWorkRequest() // 周期性的

PeriodicWorkRequest()会不断运行直到任务被取消,更精细的操作,需要使用PeriodicWorkRequestBuilder(),可以定义的最短重复时间间隔为15分钟。示例如下:

val constraints = Constraints.Builder()

.setRequiresCharging(true) // 约束为充电状态

.build()

// 一小时执行一次,但是必须在接通电源时运行

val saveRequest = PeriodicWorkRequestBuilder(1, TimeUnit.HOURS)

.setConstraints(constraints).build()

Contraints.Builder()还有以下方法:

// 当一个本地content(Uri)更新时任务是否需要运行

addContentUriTrigger(uri: Uri, triggerForDescendants: Boolean)

// 任务运行是否要求设备处于特定网络模式

setRequiredNetworkType(networkType: NetworkType)

// 任务运行是否要求设备不处于低电量模式

setRequiredBatteryNotLow(requiresBatteryNotLow: Boolean)

// 任务运行是否要求设备处于空闲时

setRequiredDeviceIdle(requiresDeviceIdle: Boolean)

// 任务运行是否要求设备具有较多的存储空间

setRequiredStorageNotLow(requiresStorageNotLow: Boolean)

// 任务预定好时,content: Uri第一次发生改变后的延时

setTriggerContentMaxDelay(duration: Duration)

setTriggerContentMaxDelay(duration: Long, timeUnit: TimeUnit)

// 任务预定好时,content: Uri发生改变后的延时

setTriggerContentUpdateDelay(duration: Duration)

setTriggerContentUpdateDelay(duration: Long, timeUnit: TimeUnit)

一次性任务和周期性任务都可以使用setInitialDelay(duraiton: Long, timeUnit: TimeUnit)设置初始延迟(注意调用后还需要调用build()才能返回WorkRequest)。

可以用setBackoffCriteria(backoffPolicy: BackoffPolicy, backoffDelay: Long, timeUnit: TimeUnit)设置退避延迟政策,也就是两个任务冲突时如需要重试,那么在重试工作前需要等待的最短时间,退避政策有BackoffPolicy.EXPONENTIAL与BackoffPolicy.LINEAR,前者代表指数增长等待时间,后者代表线性增长等待时间;backoffDelay是等待时间延迟,对于一次性任务和周期性任务分别有它们的MIN_BACK_MILLIS=5*60*1000和MAX_BACKOFF_MILLIS=5*60*60*1000。

任务也可以有输入输出,输入输出都是以键值对的形式存储在Data对象中,示例如下:

val inputData = workDataof("key" to "value")

// 输入数据

val myWorkRequest = OneTimeWorkRequestBuilder()

.setInputData(inputData)

.build()

// 获取输出数据

class MyWorker(appContext: Context, workerParams: WorkerParams) : Worker(appContext, workerParams) {

override fun doWork(): Result {

// 做一些工作

return Result.success(outputData) // 任务成功,获取输出数据

}

}

可以使用addTag(tag: String)为WorkRequest添加标签,并使用WorkManager.cancelAllWorkByTag(tag: String)取消特定标签的任务。

三、将任务提交给系统。

WorkManager.getInstance(myContext).enqueue(myWorkRequest)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39610956/article/details/111812988

智能推荐

基于多重继承与信息内容的知网词语相似度计算 - 论文及代码讲解_机智翔学长的博客-程序员ITS304

论文:《基于多重继承与信息内容的知网词语相似度计算》-2017-张波,陈宏朝等 查看代码:https://github.com/yaleimeng/Final_word_Similarity总体感受:太乱了,有可能是之前没怎么接触这块。看论文,搞不懂怎么回事,义项、义原是啥,怎么这么多定义,到头来还是不懂两个词的相似度怎么计算,比哈工大词林那篇论文复杂多了。看代码,函数调来调去,一会这个...

《计算机操作系统》重点知识笔记整理(一)_Barry Yan的博客-程序员ITS304_计算机操作系统笔记

《计算机操作系统》重点知识总结1(1-4章)????注意:​ 这篇总结文档参考的配套书籍为《计算机操作系统》(第四版) 相关知识点关联的页码可能只与本书配套。????说明:​ 由于时间关系,该总结的部分知识点可能有所疏落或存在错误,请认真研读不要盲目学习,读者如有补充或问题更正请联系作者[[email protected]],作者将会表示感谢!​ 最后,希望尊重作者劳动成果,请大家转载时注明出处,Thanks!????第一章 操作系统引论1 操作系统的定义

高斯模糊(高斯滤波)原理以及计算过程_StriveZs的博客-程序员ITS304_高斯模糊公式

高斯模糊/高斯滤波通常,图像处理软件会提供模糊滤镜,使图片产生模糊效果。模糊的算法有很多,其中有一种叫高斯模糊(Gaussian Blur),它将正态分布用于图像处理。文本介绍了高斯模糊的算法,你会看到这是一个非常简单易懂的算法。本质上,它是一种数据平滑技术(data smoothing),适用于多个场合,图像处理恰好提供了一个直观的应用实例。高斯模糊的原理所谓模糊,可以理解成每一个像素都取周边像素的平均值。上图中,2是中间点,周边点都是1.中间点取周围点的平均值之后,就会从2变成了1.

ubuntu安装mysql离线包_lmlby的博客-程序员ITS304

Ubuntu安装mysql离线包测试环境:ubuntu12.04-amd64Mysql离线包:mysql-5.5.25-linux2.6-x86_64.tar.gz官方安装步骤如下:注意事项:如果执行scripts/mysql_install_db –user=mysql时出现如下错误:InstallingMySQL system tables..../bin/mysq

MySQL学习之子查询、合并查询结果、别名_MakerGaoGao的博客-程序员ITS304

学习峰哥java教程自学笔记:http://www.java1234.com/javaxuexiluxiantu.html表结构如下:t_book:t_booktype:t_price:1、带in关键字的查询(在后面的查询结果中查询前面的)SELECT * FROM t_book WHE

checkbox实现全选的多种方法_墙角的爬山虎的博客-程序员ITS304_checkbox全选

<script language=javascript> //第一种方法 function selectall1() { var a = document.getElementsByTagName("input"); if(a[0].checked==true){ for (var i=0; i<a.length; i++) if (a[i].type == "checkbox") a[i].checked = false; }

随便推点

EXT4.0 (4~9章)学习资料_clever027的博客-程序员ITS304

第四章 MVC学习 从这个图中我们可以很清楚的看到M 、V、C在ExtJS4.0里面所对应数据类型。 靠右边是对应的代码结构。 下描述一下这model、store、view、controller以及application这几者之间的关系。(1)application:它是MVC的入口,用来告诉ExtJS到那里去找对应js文件以及启动加载controlle

2021年华为认证考试费用是多少_20004的博客-程序员ITS304_华为ip证书多少钱

有一些朋友打算在2021年参加华为网络工程师这方面的考试,所以想知道这方面的考试费用是多少,自己好有一个准备,那么网络工程师成长日记,作者小编来给大家介绍好让大家有一个准备如果你是找工作为目的的话,一般来说你至少要考华为hcip,也就是中级网络工程师以上这个认证,如果你非常清楚华为的初级,中级高级这三个级别,那么你也可以把最高级别这个认证作为你的考试目标所以首先你要考哪个级别是你需要自己心里清楚的华为的初级考试费用也就是1000多块钱华为的中级考试费用是480美金,折合成人民...

Flutter筑基——学好 Dart,才能玩转 Flutter_willwaywang6的博客-程序员ITS304

目录前言正文Dart 开发环境的搭建最后参考前言我们知道 Flutter 这个 UI 框架是使用 Dart 语言开发的,这说明要玩转 Flutter,就要先学好 Dart。那么,怎么学好 Dart 呢?有的同学抱着“不就是一门语言嘛”的心态,直接开始写 Flutter,然后遇到问题了,再去查看 Dart 的文档。这也是一种学习 Dart 的方式,但这种方式可能不适合大多数同学。庆幸地是,可以去查看 Dart 官网上的示例,比如Language-tour,就讲解了 Dart 的语法。但是,官网上的

破解使用radius实现802.1x认证的企业无线网络_Sword-heart的博客-程序员ITS304

0x01前言概述针对开放式(没有密码)无线网络的企业攻击,我个人感觉比较经典的攻击方式有2种,一种是eviltwin,一种是karma。karma应该是eviltwin攻击手法的升级版,攻击者只需要简单的监听客户端发的ssid探测和响应包就可以实现中间人了,受害者很少会有察觉。而且坊间曾有一个错误的认识,认为隐藏的ssid是不受karma影响的。但是实际情况是,客户端如果曾经连接过隐藏的ssid,也会广播这些网络的探测包。尽管karma这种攻击方式已经有10多年的历史了,但是在MAC OSX,ubunt

ffmpeg代码分析(1)--编译裁剪_茜茜她老爹的博客-程序员ITS304_ffmpeg 裁剪编译

我们很少使用到ffmpge完整的功能库,大部分时候,只需要特定的编解码 传输协议,那么该怎么裁剪ffmpeg呢?答案就是configure命令。configure 是一个文本文件,打开以后我们会发现它有很多命令,如下Help options:  --help                   print this message  --list-decoders        

推荐文章

热门文章

相关标签