注意:请确保将用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 Activity 或 Fragment 中,原因如下:
-
避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。
-
将 LiveData 实例与特定的 Activity 或 Fragment 实例分离开,并使 对象在配置更改后继续存在。
观察 LiveData 对象
一般在 onCreate() 方法是开始观察 LiveData 对象的正确着手点,原因如下:
-
确保系统不会从 Activity 或 Fragment 的 onResume() 方法进行冗余调用。
-
确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于 STARTED 状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。
//在传递 nameObserver 参数的情况下调用 observe() 后,系统会立即调用 onChanged(),从而提供 mCurrentName 中存储的最新值。 如果 LiveData 对象尚未在 mCurrentName 中设置值,则不会调用 onChanged()。
class NameActivity : AppCompatActivity() {
private lateinit var model: NameViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Other code to setup the activity…
// Get the ViewModel.
model = ViewModelProviders.of(this).get(NameViewModel::class.java)
// Create the observer which updates the UI.
val nameObserver = Observer { newName ->
// Update the UI, in this case, a TextView.
nameTextView.text = newName
}
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
model.currentName.observe(this, nameObserver)
}
}
更新 LiveData 对象
LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,如果您需要修改存储在 LiveData 对象中的值,则必须使用这些方法。
通常情况下会在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象。
设置观察者关系后,您可以更新 LiveData 对象的值(如以下示例中所示),这样当用户点按某个按钮时会触发所有观察者:
button.setOnClickListener {
val anotherName = “John Doe”
model.currentName.setValue(anotherName)
}
注意:您必须调用 setValue(T)
方法以从主线程更新 LiveData
对象。如果在 worker 线程中执行代码,则您可以改用 postValue(T)
方法来更新 LiveData
对象。
将LiveData与Room一起使用
当数据库更新时,Room 会生成更新 LiveData
对象所需的所有代码。在需要时,生成的代码会在后台线程上异步运行查询。此模式有助于使界面中显示的数据与存储在数据库中的数据保持同步。
将协程与 LiveData 一起使用
https://developer.android.google.cn/topic/libraries/architecture/coroutines
扩展 LiveData
class StockLiveData(symbol: String) : LiveData() {
private val stockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
//LiveData有活跃观察者时
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
//LiveData没有任何活跃观察者时
override fun onInactive() {
stockManager.removeUpdates(listener)
}
}
LiveData 可以在多个 Activity、Fragment 和 Service 之间共享它们。您可以将 LiveData 类实现为单一实例
class StockLiveData(symbol: String) : LiveData() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
@MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
调用:
class MyFragment : Fragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
StockLiveData.get(symbol).observe(this, Observer { price: BigDecimal? ->
// Update the UI.
})
}
转换LiveData
LiveData 对象分派给观察者之前对存储的值进行更改,
根据另一个实例的值返回不同的 LiveData 实例
Lifecycle 软件包会提供 Transformations 类,该类包括可应对这些情况的辅助程序方法。
val userLiveData: LiveData = UserLiveData()
val userName: LiveData = Transformations.map(userLiveData) {
user -> “${user.name} ${user.lastName}”
}
与 map() 类似,对存储在 LiveData 对象中的值应用函数,并将结果解封和分派到下游。传递给 switchMap() 的函数必须返回 LiveData 对象,如以下示例中所示:
private fun getUser(id: String): LiveData {
…
}
val userId: LiveData = …
val user = Transformations.switchMap(userId) { id -> getUser(id) }
要实现自定义转换,您可以使用 MediatorLiveData
类
合并多个 LiveData 源
MediatorLiveData 是 LiveData 的子类,允许您合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。
例如,如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:
-
与存储在数据库中的数据关联的 LiveData 对象。
-
与从网络访问的数据关联的 LiveData 对象。
您的 Activity 只需观察 MediatorLiveData 对象即可从这两个源接收更新。
示例
-
Sunflower,这是一个演示应用,演示架构组件的最佳做法
导航是指支持用户导航、进入和退出应用中不同内容片段的交互。Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模式,该组件均可应对。导航组件还通过遵循一套既定原则来确保一致且可预测的用户体验。
分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。
使用 PagedList 对象的 LiveData 存储器加载和显示数据:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: LiveData<PagedList> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}
每个 PagedList
实例都会从对应的 DataSource
对象加载应用数据
@Dao
interface ConcertDao {
// The Int type parameter tells Room to use a PositionalDataSource object.
@Query(“SELECT * FROM concerts ORDER BY date DESC”)
fun concertsByDate(): DataSource.Factory<Int, Concert>
}
要详细了解如何将数据加载到 PagedList
对象中,请参阅有关如何加载分页数据的指南。
示例
界面:
PagedList
类使用 PagedListAdapter
将项加载到 RecyclerView
。这些类共同作用,在内容加载时抓取和显示内容,预取不在视线范围内的内容以及针对内容更改添加动画。
要了解详情,请参阅有关如何显示分页列表的指南。
支持不同的数据架构
获取数据的3种方式:网络,本地数据库,网络+本地数据库
网络(Retrofit):
注意:由于不同的应用处理和显示错误界面的方式不同,因此分页库的 DataSource
对象不提供任何错误处理。如果发生错误,请遵循结果回调,并在稍后重试请求。有关此行为的示例,请参阅 PagingWithNetwork 示例。
本地数据库
设置您的 RecyclerView
以观察本地存储空间,最好使用 Room 持久性库。这样,无论您何时在应用数据库中插入或修改数据,这些更改都会自动反映在显示此数据的 RecyclerView
中。
网络和数据库
在开始观察数据库之后,您可以使用 PagedList.BoundaryCallback
监听数据库中的数据何时耗尽。然后,您可以从网络中获取更多项目并将它们插入到数据库中。如果界面正在观察数据库,则您只需执行此操作即可。
处理网络错误
由于服务器不稳定或者网络异常,如果数据刷新步骤不起作用,您可以提供“重试”按钮供用户选择。如果在数据分页步骤中发生错误,则最好自动重新尝试分页请求。
更新现有应用
1.在应用中将 List
对象替换成 PagedList
对象,后者不需要对应用界面结构或数据更新逻辑进行任何更改。
2.使用 CursorAdapter
3.使用 AsyncListUtil 异步加载内容
数据库示例
使用 LiveData 观察分页数据
@Dao
interface ConcertDao {
// The Int type parameter tells Room to use a PositionalDataSource
// object, with position-based loading under the hood.
@Query(“SELECT * FROM concerts ORDER BY date DESC”)
fun concertsByDate(): DataSource.Factory<Int, Concert>
}
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: LiveData<PagedList> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}
class ConcertActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this)
.get()
val recyclerView = findViewById(R.id.concert_list)
val adapter = ConcertAdapter()
viewModel.livePagedList.observe(this, PagedList(adapter::submitList))
recyclerView.setAdapter(adapter)
}
}
class ConcertAdapter() :
PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {
fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {
val concert: Concert? = getItem(position)
// Note that “concert” is a placeholder if it’s null.
holder.bindTo(concert)
}
companion object {
private val DIFF_CALLBACK = object :
DiffUtil.ItemCallback() {
// Concert details may have changed if reloaded from the database,
// but ID is fixed.
override fun areItemsTheSame(oldConcert: Concert,
newConcert: Concert) = oldConcert.id == newConcert.id
override fun areContentsTheSame(oldConcert: Concert,
newConcert: Concert) = oldConcert == newConcert
}
}
}
使用 RxJava2 观察分页数据
如果您倾向于使用 RxJava2 而不是 LiveData
,则可以改为创建 Observable
或 Flowable
对象:
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: Observable<PagedList> =
concertDao.concertsByDate().toObservable(pageSize = 50)
}
然后,您可以使用以下代码段中的代码来开始和停止观察数据:
class ConcertActivity : AppCompatActivity() {
private val adapter: ConcertAdapter()
private lateinit var viewModel: ConcertViewModel
private val disposable = CompositeDisposable()
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val recyclerView = findViewById(R.id.concert_list)
viewModel = ViewModelProviders.of(this)
.get()
recyclerView.setAdapter(adapter)
}
override fun onStart() {
super.onStart()
disposable.add(viewModel.concertList
.subscribe(adapter::submitList)))
}
override fun onStop() {
super.onStop()
disposable.clear()
}
}
示例
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
Room 包含 3 个主要组件:
- 数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
使用 @Database
注释的类应满足以下条件:
-
是扩展
RoomDatabase
的抽象类。 -
在注释中添加与数据库关联的实体列表。
-
包含具有 0 个参数且返回使用
@Dao
注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder()
或 Room.inMemoryDatabaseBuilder()
获取 Database
的实例。
应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。最后,应用使用实体来获取和设置与数据库中的表列相对应的值。
使用 Room 将数据保存到本地数据库
基本用法
User
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = “first_name”) val firstName: String?,
@ColumnInfo(name = “last_name”) val lastName: String?
)
UserDao
@Dao
interface UserDao {
@Query(“SELECT * FROM user”)
fun getAll(): List
@Query(“SELECT * FROM user WHERE uid IN (:userIds)”)
fun loadAllByIds(userIds: IntArray): List
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
“last_name LIKE :last LIMIT 1”)
fun findByName(first: String, last: String): User
@Insert
fun insertAll(vararg users: User)
@Delete
fun delete(user: User)
}
AppDatabase
@Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
调用数据库实例:
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, “database-name”
).build()
注意:如果您的应用在单个进程中运行,则在实例化 AppDatabase
对象时应遵循单例设计模式。每个 RoomDatabase
实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()
。这样,如果您在每个进程中都有一个 AppDatabase
实例,就可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中的 AppDatabase
实例。
示例
-
Sunflower,这是一款园艺应用,展示了使用 Android Jetpack 进行 Android 开发的最佳做法。
博客
7、ViewModel -以注重生命周期的方式管理界面相关的数据
ViewModel 解决的问题:
1、某个 Activity 包含用户列表。因配置更改而重新创建 Activity 后,新 Activity 必须重新提取用户列表
2、界面控制器经常需要异步调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄露,并且在因配置更改而重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。
3、如果要求界面控制器也负责从数据库或网络加载数据,那么会使类越发膨胀。
从界面控制器逻辑中分离出视图数据所有权的做法更易行且更高效。
实现 ViewModel
架构组件为界面控制器提供了 ViewModel辅助程序类,该类负责为界面准备数据。 在配置更改期间会自动保留 ViewModel
对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。
例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel
,而不是 Activity 或 Fragment,如以下示例代码所示:
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
然后,您可以从 Activity 访问该列表,如下所示:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Create a ViewModel the first time the system calls an activity’s onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
val model = ViewModelProviders.of(this)[MyViewModel::class.java]
model.getUsers().observe(this, Observer<List>{ users ->
// update UI
})
}
}
如果重新创建了该 Activity,它接收的 MyViewModel
实例与第一个 Activity 创建的实例相同。当所有者 Activity 完成时,框架会调用 ViewModel
对象的 onCleared()
方法,以便它可以清理资源。
注意:ViewModel
绝不能引用视图、Lifecycle
或可能存储对 Activity 上下文的引用的任何类。
ViewModel 的生命周期
ViewModel
对象存在的时间范围是获取 ViewModel
时传递给 ViewModelProvider
的 Lifecycle
。ViewModel
将一直留在内存中,直到限定其存在时间范围的 Lifecycle
永久消失:对于 Activity,是在 Activity 完成时;而对于 Fragment,是在 Fragment 分离时。
系统首次调用 Activity 对象的 onCreate()方法时请求 ViewModel,系统可能会在 Activity 的整个生命周期内多次调用 onCreate(),ViewModel存在的时间范围是从您首次请求 ViewMode 直到 Activity 完成并销毁。
在 Fragment 之间共享数据
AFragment和BFragment数据传递,两个 Fragment 都需要定义接口描述,这两个 Fragment 都必须处理另一个 Fragment 尚未创建或不可见的情况。
可以使用 ViewModel对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel来处理此类通信,如以下示例代码所示:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception(“Invalid Activity”)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
private lateinit var model: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this)[SharedViewModel::class.java]
} ?: throw Exception(“Invalid Activity”)
model.selected.observe(this, Observer { item ->
// Update the UI
})
}
}
请注意,这两个 Fragment 都会检索包含它们的 Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider
时,它们会收到相同的 SharedViewModel
实例(其范围限定为该 Activity)。
此方法具有以下优势:
-
Activity 不需要执行任何操作,也不需要对此通信有任何了解。
-
除了
SharedViewModel
约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。 -
每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
将加载器替换为 ViewModel
诸如 [CursorLoader]( )
之类的加载器类经常用于使应用界面中的数据与数据库保持同步。您可以将 ViewModel
与一些其他类一起使用来替换加载器。使用 ViewModel
可将界面控制器与数据加载操作分离,这意味着类之间的强引用更少
ViewModel
与 Room 和 LiveData 一起使用可替换加载器。ViewModel
确保数据在设备配置更改后仍然存在。Room 在数据库发生更改时通知 LiveData
,LiveData 进而使用修订后的数据更新界面。
将协程与 ViewModel 一起使用
ViewModel
支持 Kotlin 协程。如需了解详情,请参阅将 Kotlin 协程与 Android 架构组件一起使用。
示例
如需更多与协程相关的信息,请参阅以下链接:
8、WorkManager - 管理您的 Android 后台作业
当前稳定版:
| 2020 年 1 月 22 日 | 2.3.0 |
dependencies {
def work_version = “2.3.0”
// (Java only)
implementation “androidx.work:work-runtime:$work_version”
// Kotlin + coroutines
implementation “androidx.work:work-runtime-ktx:$work_version”
// optional - RxJava2 support
implementation “androidx.work:work-rxjava2:$work_version”
// optional - GCMNetworkManager support
implementation “androidx.work:work-gcm:$work_version”
// optional - Test helpers
androidTestImplementation “androidx.work:work-testing:$work_version”
}
WorkManager 旨在用于可延迟运行(即不需要立即运行)并且在应用退出或设备重启时必须能够可靠运行的任务。例如:
-
向后端服务发送日志或分析数据
-
定期将应用数据与服务器同步
WorkManager 不适用于应用进程结束时能够安全终止的运行中后台工作,也不适用于需要立即执行的任务。请查看后台处理指南,了解哪种解决方案符合您的需求。
创建后台任务
要创建后台任务,请扩展 Worker 类并替换 doWork() 方法。例如,要创建上传图像的 Worker:
-
class UploadWorker(appContext: Context, workerParams: WorkerParameters)
- Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here–in this case, upload the images.
uploadImages()
// Indicate whether the task finished successfully with the Result
return Result.success()
}
}
从 doWork()
返回的 Result
会通知 WorkManager 任务是否:
-
已成功完成:
Result.success()
-
已失败:
Result.failure()
-
需要稍后重试:
Result.retry()
注意:Worker
是运行工作的最简单方式。要了解 Worker 的更多高级选项,请参阅 WorkManager 线程指南。
WorkManager 提供了四种不同类型的工作基元:
-
Worker
是最简单的实现,前面几节已经有所介绍。WorkManager 会在后台线程上自动运行它(您可以将它替换掉)。请参阅工作器中的线程处理,详细了解Worker
中的线程处理。 -
建议 Kotlin 用户实现
CoroutineWorker
。CoroutineWorker
针对后台工作公开挂起函数。默认情况下,它们运行默认的Dispatcher
,您可以对其进行自定义。请参阅 CorventineWorker 中的线程处理,详细了解CoroutineWorker
中的线程处理。 -
建议 RxJava2 用户实现
RxWorker
。如果您有很多现有异步代码是用 RxJava 建模的,则应使用 RxWirkers。与所有 RxJava2 概念一样,您可以自由选择所需的线程处理策略。请参阅 RxWorker 中的线程处理,详细了解RxWorker
中的线程处理。 -
ListenableWorker
是Worker
、CoroutineWorker
和RxWorker
的基类。该类专为需要与基于回调的异步 API(例如FusedLocationProviderClient
)进行交互并且不使用 RxJava2 的 Java 开发者而设计。请参阅 ListenableWorker 中的线程处理,详细了解ListenableWorker
中的线程处理。
配置运行任务的方式和时间
Worker
定义工作单元,WorkRequest
则定义工作的运行方式和时间。任务可以是一次性的,也可以是周期性的。对于一次性 WorkRequest
,请使用 OneTimeWorkRequest
,对于周期性工作,请使用 PeriodicWorkRequest
。
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.build()
WorkRequest
中还可以包含其他信息,例如任务在运行时应遵循的约束、工作输入、延迟,以及重试工作的退避时间政策。关于这些选项,在定义工作指南中有更详细的说明。
在本指南中,您将了解如何自定义工作请求来处理常见用例:
-
处理网络可用性等任务约束
-
保证任务执行的延迟时间最短
-
处理任务重试和退避
-
处理任务输入和输出
-
使用标记对任务进行分组
1、工作约束:
您可以向工作添加 Constraints,以指明工作何时可以运行:
// Create a Constraints object that defines when the task should run
val constraints = Constraints.Builder()
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.build()
// …then create a OneTimeWorkRequest that uses those constraints
val compressionWork = OneTimeWorkRequestBuilder()
.setConstraints(constraints)
.build()
2、初始延迟
任务设置为在加入队列后至少经过 10 分钟再运行:
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setInitialDelay(10, TimeUnit.MINUTES)
.build()
3、重试和退避政策
如果您需要让 WorkManager 重新尝试执行您的任务,可以从工作器返回 Result.retry()
。
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
最后
总之啊,家里没矿的同学们,如果你们想以后的日子过得好一些,多想想你们的业余时间怎么安排吧;
技术方面的提升肯定是重中之重,但是技术外的一些“软实力”也不能完全忽视,很多时候升职确实是因为你的技术足够强,但也与你的“软实力”密切相关
在这我也分享一份大佬自己收录整理的 Android学习PDF+架构视频+面试文档+源码笔记 ,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅并给下属员工学习的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。
总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
相信自己,没有做不到的,只有想不到的
t.retry()`]( )。
val uploadWorkRequest = OneTimeWorkRequestBuilder()
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-fOn9QGol-1711900388684)]
[外链图片转存中…(img-WCIlMNH0-1711900388685)]
[外链图片转存中…(img-iMq4G6BS-1711900388685)]
[外链图片转存中…(img-WxnSmMhQ-1711900388685)]
[外链图片转存中…(img-6hVbtioO-1711900388686)]
[外链图片转存中…(img-o5YFkPfM-1711900388686)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-E7YHSBFe-1711900388686)]
最后
总之啊,家里没矿的同学们,如果你们想以后的日子过得好一些,多想想你们的业余时间怎么安排吧;
技术方面的提升肯定是重中之重,但是技术外的一些“软实力”也不能完全忽视,很多时候升职确实是因为你的技术足够强,但也与你的“软实力”密切相关
在这我也分享一份大佬自己收录整理的 Android学习PDF+架构视频+面试文档+源码笔记 ,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅并给下属员工学习的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。
总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
[外链图片转存中…(img-7ahubqDs-1711900388687)]
[外链图片转存中…(img-ju4thucv-1711900388687)]
相信自己,没有做不到的,只有想不到的