JetPack :Paging、WorkManager、Slices介绍和使用方

今年谷歌I/O大会,谷歌发布了.这是新一代组件、工具和架构指导,用谷歌官方的话就是旨在加快开发者的应用开发速度 。组件将现有的支持库与架构组件联系起来,并将它们分成四个类别:
结构图.png
组件以“未捆绑的”库形式提供 , 这些库不是基础平台的一部分 。这就意味着,我们可以根据自己的需求采用每一个组件 。在新的功能发布后,我们可以将其添加到自己的应用中,将我们的应用部署到应用商店并向用户提供新功能 , 如果我们的行动足够快,所有这些可以在一天内完成!
那么谷歌发布的目的是什么呢?
三大优点.png
除了这三点外,谷歌想给开发者定制一套标准,比如框架的标准,我们平时MVC,MVP,MVVM等等,现在谷歌自己搞了一套MVP-CLEAN 。
我们从的四大部分也可以看出,谷歌想要结束混乱的局面,给开发者一个规范,这个对我们开发者也是一件好事,跟着官方走总不会差的 。更多参见App体系结构指南 。
那么我在这一篇给大家介绍一下、、和他们的使用方式,篇幅较长,请大家酌情找尿点 。
(分页)
背景:
很多应用程序从包含大量项目的数据源中获取数据,但一次只显示一小部分数据 。加载应用程序中显示的数据可能很大并且代价高昂,因此要避免一次下载,创建或呈现太多数据 。为了可以更轻松地在我们的应用程序中逐渐加载数据谷歌方法提供了这个组件 , 可以很容易地加载和现在的大数据集与我们的快速,无限滚动 。它可以从本地存储,网络或两者加载分页数据,并且可以让我们自定义如何加载内容 。它可以与Room,和一起使用 。
分为三部分:, ,
:
它就像是一个抽水泵,而不是真正的水源 , 它负责从数据源加载数据 , 可以看成是 与数据源之间的接口 。
是数据源相关的类,Key是加载数据的条件信息,Value是返回结果,针对不同场景我们需要用不同的,提供了三个子类来供我们选择 。
:
它就像是一个蓄水池,抽的水放到中 。它是List的子类,它包含着我们的数据并告诉数据源何时加载数据 。我们也可以配置一次加载多少数据,以及应该预取多少数据 。它提供适配器的更新作为页面中加载的数据 。
有五个重要的参数:
:
这个类是.的实现,它提供来自的数据并以作为参数来计算数据的差异并为你做所有的更新工作 。
看十遍不如敲一遍,搞起,搞起~~(本篇全部用语言,涉及到 , ,Room,请大家系好安全带)
功能:本地增加和删除Item的列表
1、添加依赖
def paging_version = "1.0.0"def lifecycle_version = "1.1.1"def room_version = "1.1.0"//这是 Paging的依赖implementation "android.arch.paging:runtime:$paging_version"// alternatively - without Android dependencies for testingtestImplementation "android.arch.paging:common:$paging_version"// optional - RxJava support, currently in release candidateimplementation 'android.arch.paging:rxjava2:1.0.0-rc1'//这是ViewModel and LiveData 的依赖implementation "android.arch.lifecycle:extensions:$lifecycle_version"implementation "android.arch.persistence.room:runtime:$room_version"//这是room的依赖implementation "android.arch.persistence.room:rxjava2:$room_version"// optional - Guava support for Room, including Optional and ListenableFutureimplementation "android.arch.persistence.room:guava:$room_version"// Test helperstestImplementation "android.arch.persistence.room:testing:$room_version"
网上有很多文章都是如此添加依赖,但是我们用到了ROOM这个组件,对于是有问题的 。因为需要-kapt插件,用来引入注解处理库,java的话可以用,我们需要apply : '-kapt',然后在上面的依赖中添加
//java用这个// annotationProcessor "android.arch.persistence.room:compiler:$room_version"//kotlin 用这个kapt 'android.arch.persistence.room:compiler:1.0.0'
不然就会有 does not exist
at .arch..room.Room.的错误,这个坑让我爬了一上午,很是狼狈 。而且谷歌demo的项目代码和我们创建的有区别,所以会有很多注意不到的坑 。
2、加载单一数据源的数据
image.png
.kt
@Entitydata class Student(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
.kt
对数据库数据的操作接口(增删改查等方法类)
@Daointerface StudentDao {/*** Room knows how to return a LivePagedListProvider, from which we can get a LiveData and serve* it back to UI via ViewModel.*/@Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")fun allStudentByName(): DataSource.Factory@Insertfun insert(student: List)@Insertfun insert(student: Student)@Deletefun delete(student: Student)}
.kt
实用工具方法,用于在专用后台线程上运行块,用于io/数据库工作,下面的类里会用到
private val IO_EXECUTOR = Executors.newSingleThreadExecutor()/*** Utility method to run blocks on a dedicated background thread, used for io/database work.*/fun ioThread(f : () -> Unit) {IO_EXECUTOR.execute(f)}
.kt
数据库的创建类
@Database(entities = arrayOf(Student::class), version = 1)abstract class StudentDb : RoomDatabase() {abstract fun studentDao(): StudentDaooverride fun clearAllTables() {}companion object {private var instance: StudentDb? = null@Synchronizedfun get(context: Context): StudentDb {if (instance == null) {instance = Room.databaseBuilder(context.applicationContext,StudentDb::class.java, "student.db").addCallback(object : RoomDatabase.Callback() {override fun onCreate(db: SupportSQLiteDatabase) {fillInDb(context.applicationContext)}}).build()}return instance!!}/*** fill database with list of cheeses*/private fun fillInDb(context: Context) {// 在Room中的插入是在当前线程上执行的,因此我们将插入到后台线程中ioThread {get(context).studentDao().insert(CHEESE_DATA.map { Student(id = 0, name = it) })}}}}private val CHEESE_DATA = http://www.kingceram.com/post/arrayListOf("Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi","Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale","Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Student","Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell","Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc","Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss","Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon","Barry's Bay Cheddar", "Basing", "Basket Student", "Bath Student", "Bavarian Bergkase","Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Student", "Bel Paese","Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy","Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille","Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore","Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)","Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves","Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur","Breakfast Student", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon","Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin","Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)","Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine","Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza","Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)","Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta","Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie","Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat","Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano","Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain","Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou","Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar","Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno","Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack","Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper","Cotherstone", "Cotija", "Cottage Student", "Cottage Student (Australian)","Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Student","Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza","Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley","Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino","Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina","Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby","Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin","Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester","Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue","Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz","Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich","Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue","Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle","Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia","Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis","Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus","Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison","Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois","Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse","Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Student","Frying Student", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise","Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra","Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola","Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost","Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel","Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve","Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi","Halloumy (Australian)", "Haloumi-Style Student", "Harbourne Blue", "Havarti","Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin","Plateau de Herve", "Plymouth Student", "Podhalanski", "Poivre d'Ane", "Polkolbin","Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre","Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone","Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale","Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie","Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri","Tete de Moine", "Tetilla", "Texas Goat Student", "Tibet", "Tillamook Cheddar","Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano");
.kt
继承,构造属于我们的
class StudentAdapter : PagedListAdapter(diffCallback) {override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {holder.bindTo(getItem(position))}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder =StudentViewHolder(parent)companion object {/*** 这个diff回调通知PagedListAdapter在新列表到来的时候如何计算列表差异** 当您使用“add”按钮添加一个Student的时候 , PagedListAdapter使用diffCallback t去检测到与以前的Item的不同,所以它只需要重画和重新绑定一个视图 。** @see android.support.v7.util.DiffUtil*/private val diffCallback = object : DiffUtil.ItemCallback() {override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean =oldItem.id == newItem.id/*** 注意 kotlin的== 等价于java的equas()方法 */override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean =oldItem == newItem}}}
.kt
class StudentViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.student_item, parent, false)) {private val nameView = itemView.findViewById(R.id.name)var student : Student? = null/*** Items might be null if they are not paged in yet. PagedListAdapter will re-bind the* ViewHolder when Item is loaded.*/fun bindTo(student : Student?) {this.student = studentnameView.text = student?.name}}
.kt
继承类,这也是推荐的视图数据关联方式,避免了和的任务繁重 。数据用包装 , 这样可以避免生命周期对数据的影响,减少内存泄漏 。
class StudentiewModel(app: Application) : AndroidViewModel(app) {val dao = StudentDb.get(app).studentDao()companion object {private const val PAGE_SIZE = 30/**如果启用了占位符,PagedList将报告完整的大小,但是有的Item在onBind方法中可能会为空(PagedListAdapter在加载数据时触发重新绑定)如果禁用了占位符,onBind将永远不会收到null 。如果你禁用占位符那么你应该禁用滚动条,不然随着页面已加载的增多,滚动条将随着新页面的加载而抖动*/private const val ENABLE_PLACEHOLDERS = true}#Config可以设置页面显示的数量,是否启动占位符等等val students = LivePagedListBuilder(dao.allStudentByName(), PagedList.Config.Builder().setPageSize(PAGE_SIZE).setEnablePlaceholders(ENABLE_PLACEHOLDERS).build()).build()//直接插入到数据库fun insert(text: CharSequence) = ioThread {dao.insert(Student(id = 0, name = text.toString()))}//从数据库删除fun remove(cheese: Student) = ioThread {dao.delete(cheese)}}
.kt
class MainActivity : AppCompatActivity() {private val viewModel by lazy(LazyThreadSafetyMode.NONE) {ViewModelProviders.of(this@MainActivity).get(StudentiewModel::class.java)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val adapter = StudentAdapter()cheeseList.adapter = adapter// 将adapter添加订阅到ViewModel,当列表改变时,Adapter中的item会被刷新viewModel.students.observe(this, Observer(adapter::submitList))initAddButtonListener()initSwipeToDelete()}private fun initSwipeToDelete() {ItemTouchHelper(object : ItemTouchHelper.Callback() {// //使Item能向左或向右滑动override fun getMovementFlags(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder): Int =makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder): Boolean = false//当项被滑动时,通过ViewModel删除该项 。列表项将会自动删除,因为adapter正在观察这个Live List 。override fun onSwiped(viewHolder: RecyclerView.ViewHolder?, direction: Int) {(viewHolder as? StudentViewHolder)?.student?.let {viewModel.remove(it)}}}).attachToRecyclerView(cheeseList)}private fun addStudnet() {val newCheese = inputText.text.trim()if (newCheese.isNotEmpty()) {viewModel.insert(newCheese)inputText.setText("")}}private fun initAddButtonListener() {addButton.setOnClickListener {addStudnet()}// 当用户点击屏幕键盘上的“完成”按钮时,保存item.inputText.setOnEditorActionListener({ _, actionId, _ ->if (actionId == EditorInfo.IME_ACTION_DONE) {addStudnet()return@setOnEditorActionListener true}false // action that isn't DONE occurred - ignore})// 当用户单击按钮或按enter时,保存该 item.inputText.setOnKeyListener({ _, keyCode, event ->if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {addStudnet()return@setOnKeyListener true}false // event that isn't DOWN or ENTER occurred - ignore})}}
.xml

.xml

38613e9c7f258ae4b7ea387891dae468
ok,到此demo完工,大家可以试试效果,不过从代码量来看比java简洁太多了,不过这demo里面基本把里的,,,Room都用到了 , 很多细节和用法 , 需要大家连贯起来学习 。从的代码来看很简洁,意思也很明确,比java的阅读性要高很多 , 不过如果没有学的同学看到这些代码我想内心是MMP的 。
可以轻松指定可延迟的异步任务以及何时运行 。这些API可让我们创建任务并将其交给,以便立即或在适当的时间运行 。例如 , 应用程序可能需要不时从网络下载新资源 。使用这些类 , 可以设置一个任务,选择适合它运行的环境(例如“仅在设备充电和联网时”) , 并在符合条件时将其交给运行 。即使您的应用程序强制退出或设备重新启动,该任务仍可保证运行 。
注意:适用于需要保证即使应用退出也能运行系统的任务,例如将应用数据上传到服务器 。如果应用程序进程消失 , 它不适用于可以安全终止的进程内后台工作; 对于这样的情况,推荐使用 。
以上是官方的介绍,那么我们就来白话一下
谷歌出这个到底是干嘛?。?不是有,,, , 等等了吗?怎么又来一套?
其实不是的,这回谷歌真的替我们做了很多我们平时比较头疼的东西,什么呢?的作用是在应用退出或者某些原因终止了之后,任务还可以进行,至于采取什么方法,这个我们不需要去管 , 都替我们处理了 。会根据系统版本来选择用, 的, 或是 。
至于, , 这三个和是没有冲突的 , 人家是为了保证任务的可靠运行,但是, ,,这三兄弟app退出人家就不干活了,和的职责有着本事区别 。一个是风雨无阻完成任务,一个有点事就撂挑子不干活了 。
调用流程
我们表扬下的好处
1、 易于调度
2、易于取消
3、易于查询
4、支持所有的版本
由以下几个部分组成
了解完 , 该撸代码了 , 前方高能依然是 。请抓好安全带 。
这个小demo的功能是执行延时任务 , 获取广告信息,然后通知UI显示广告,看看能不能做一些无赖的事情,比如一有广告直接调起app显示 。
1、添加依赖
implementation "android.arch.work:work-runtime-ktx:1.0.0-alpha01"
.kt
做了一个任务的开关,是输入信息是对外输出信息,也就是根据的信息去做不同的事情,然后把结果通过送出来 。
的状态 ,RETRY,;
RETRY:可以再次重试该工作
:发生了一个或多个错误
:任务成功完成
class AdWorker : Worker() {override fun doWork(): WorkerResult {//输入dataval is_open = this.inputData.getBoolean("is_open_ad", false)if (is_open) {//模拟延时操作Thread.sleep(10000)val ad = getAd()outputData = http://www.kingceram.com/post/Data.Builder().putString("key_ad", ad).build()Log.e("ad", "SUCCESS")return WorkerResult.SUCCESS} else {Log.e("ad", "FAILURE:")return WorkerResult.FAILURE}}private fun getAd(): String {return "我是广告君,没进刚哥知识星球的赶紧加入了啊~~" + System.currentTimeMillis()}}
.kt
任务的调度类 我们在此用的是ilder一次性调用 。
如果我们需要重复执行一项任务的话使用.
不过这个有个坑在等着大家 。使用的时候的里面的值是空的,上网查了很多资料都没有写出这个问题,但是ilder确实没有问题的,希望各位大神可以给解答一下这个问题 。
这里可以往进行,数据输入 。然后加入任务队列 。我们在此保存好任务ID,根据这个ID才可以找到这个任务 。
那么只有这一个方法才能找到任务吗?答案是否定的 。
我们可以标记任务 .("")
这个方法来获取任务 .().("")
约束:
定义约束条件以告诉合适安排任务执行,如果没有提供任何约束条件 , 那么该任务将立即运行 。
以下是仅在设备充电和设备是否为空闲才运行任务的约束
val myConstraints = Constraints.Builder().setRequiresDeviceIdle(true).setRequiresCharging(true).build()
image.png
class AdEngine {fun schedulAd() {val adReauest = OneTimeWorkRequestBuilder().setInputData(Data.Builder().putBoolean("is_open_ad", true).build()).setConstraints(myConstraints).addTag("tag_ad").build()WorkManager.getInstance().enqueue(adReauest)//保存任务IDval adRequestId = adReauest.idvar arid by Preference("adRequestId", "")arid = adRequestId.toString()}//这是约束条件@RequiresApi(Build.VERSION_CODES.M)val myConstraints = Constraints.Builder().setRequiresDeviceIdle(true).setRequiresCharging(true).build()}
.kt
在的工具类
class Preference(val name: String, private val default: T) {private val prefs: SharedPreferences by lazy { App.instance.applicationContext.getSharedPreferences(name, Context.MODE_PRIVATE) }operator fun getValue(thisRef: Any?, property: KProperty<*>): T {Log.i("info", "调用$this 的getValue()")return getSharePreferences(name, default)}operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {Log.i("info", "调用$this 的setValue() value参数值为:$value")putSharePreferences(name, value)}@SuppressLint("CommitPrefEdits")private fun putSharePreferences(name: String, value: T) = with(prefs.edit()) {when (value) {is Long -> putLong(name, value)is String -> putString(name, value)is Int -> putInt(name, value)is Boolean -> putBoolean(name, value)is Float -> putFloat(name, value)else -> throw IllegalArgumentException("This type of data cannot be saved!")}.apply()}@Suppress("UNCHECKED_CAST")private fun getSharePreferences(name: String, default: T): T = with(prefs) {val res: Any = when (default) {is Long -> getLong(name, default)is String -> getString(name, default)is Int -> getInt(name, default)is Boolean -> getBoolean(name, default)is Float -> getFloat(name, default)else -> throw IllegalArgumentException("This type of data cannot be saved!")}return res as T}}
app.kt
class App :Application(){companion object {// 伴生对象java里的静态属性lateinit var instance: Appprivate set}override fun onCreate() {super.onCreate()instance = this}}
.kt
调用.()方法,开启任务 , 然后通过 返回,添加到(本身就是一个),我们就可以根据state的改变,改变的内容 。显示我们的广告语 。结果我们会发现并不会直接调起来APP , 这是为什么呢?
因为我们用的是,当销毁后,处于未激活的状态,不回去接受数据的改变,只有从新获得生命周期后 , 才会接受数据的变化,从而受到的通知 , 所以想要做坏事的同学,这条路行不通的,谷歌只是为了保证一些任务的可靠性 , 而不是保证你的App的生命 。
当然任务可以执行就可以取消
.().(uuid);
链式调用
WorkManager.getInstance(). beginWith(workA1,workA2,workA3).then (workB).then(workC1 , workC2).enqueue();
image.png
这样就完了吗?还有更复杂的链式调用 大家可以自行学习下 。
class Main2Activity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main2)button.setOnClickListener(View.OnClickListener {val adEngine: AdEngine = AdEngine()adEngine.schedulAd()showAd(this, textad)})showAd(this, textad)}}fun showAd(lifeowner: LifecycleOwner, textad: TextView) {var arid by Preference("adRequestId", "")if (!arid.equals("")) {val uuid = UUID.fromString(arid)//public abstract LiveData getStatusById(@NonNull UUID id);WorkManager.getInstance().getStatusById(uuid).observe(lifeowner, android.arch.lifecycle.Observer { state ->if (state != null && state.state.isFinished) {val adResult = state.outputData.getString("key_ad", "无")textad.text = adResult}})}}
在国内应用的范围不广 , 重要是因为是的延伸,谷歌希望使用者能过快速到达App里面的某个特点功能 , 举一例子就是,你对 说你要回家,那么以前可能只会出现滴滴,Uber的选项,但是引进之后会显示更加详细的数据列表,比如滴滴item下会出现到家多少距离,多少钱,是否立即打车等等 。在国内不好用,但是谷歌有这个功能开源我们自己其实也可以去实现,可能小米会把这个功能给小艾同学吧 。
开始搭建我们的吧 。
注意注意:开发环境必须是3.2 以及以上,最低版本 4.4 (API level 19) ,我们可以从官网下载3.2 ,图标是黄色的,可以和我们之前的共存,相互没有干扰,讲实话3.2 真的处处是坑,特别和配合,那真的是一言难?。嗖豢把?。
no.1
image.png
如果没有这个选项的话

值得一说的是 依赖的版本真的很坑,注意是否依赖了正确的版本
implementation "androidx.slice:slice-core:1.0.0-alpha1"implementation "androidx.slice:slice-builders:1.0.0-alpha1"
no.2
class MySliceProvider : SliceProvider() {/*** Instantiate any required objects. Return true if the provider was successfully created,* false otherwise.*/override fun onCreateSliceProvider(): Boolean {return true}override fun onMapIntentToUri(intent: Intent?): Uri {// Note: implementing this is only required if you plan on catching URL requests.// This is an example solution.var uriBuilder: Uri.Builder = Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)if (intent == null) return uriBuilder.build()val data = http://www.kingceram.com/post/intent.dataif (data != null && data.path != null) {val path = data.path.replace("/", "")uriBuilder = uriBuilder.path(path)}val context = contextif (context != null) {uriBuilder = uriBuilder.authority(context.getPackageName())}return uriBuilder.build()}override fun onBindSlice(sliceUri: Uri): Slice? {val context = getContext() ?: return nullreturn if (sliceUri.path == "/") {// Path recognized. Customize the Slice using the androidx.slice.builders API.// Note: ANR and StrictMode are enforced here so don't do any heavy operations. // Only bind data that is currently available in memory.ListBuilder(context, sliceUri).addRow { it.setTitle("URI found.") }.build()} else {// Error: Path not found.ListBuilder(context, sliceUri).addRow { it.setTitle("URI not found.") }.build()}}override fun onSlicePinned(sliceUri: Uri?) {}override fun onSliceUnpinned(sliceUri: Uri?) {// Remove any observers if necessary to avoid memory leaks.}}
绑定Slice
override fun onBindSlice(sliceUri: Uri): Slice? {val activityAction = createActivityAction()return if (sliceUri.path == "/ssy") {ListBuilder(context, sliceUri, ListBuilder.INFINITY).addRow { it.setTitle("URI found. 我是标题")it.setSubtitle("我是子标题")//设置Actionit.setPrimaryAction(activityAction)}}.build()} else {ListBuilder(context, sliceUri, ListBuilder.INFINITY).addRow { it.setTitle("URI not found.") }.build()}}//创建Actionfun createActivityAction(): SliceAction {val intent = Intent(context, MainActivity::class.java)return SliceAction(PendingIntent.getActivity(context, 0, intent, 0),IconCompat.createWithResource(context, R.drawable.ic_launcher_background),"Open MainActivity.")}
将URL转变成 URI
override fun onMapIntentToUri(intent: Intent?): Uri {var uriBuilder: Uri.Builder = Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)if (intent == null) return uriBuilder.build()val data = http://www.kingceram.com/post/intent.dataif (data != null && data.path != null) {val path = data.path.replace("/ssy", "")uriBuilder = uriBuilder.path(path)}val context = contextif (context != null) {uriBuilder = uriBuilder.authority(context.getPackageName())}return uriBuilder.build()}
这样我们我们就完成了一个简单的Slice,什么?怎么用?下载这个slice-.apk充当 吧,然后我们需要做的是
adb shell am start -a ...VIEW -d slice-://com../ssy
蓝色的是我们自己的 Uri,这样就会在slice-.apk打开我们的slice了 。
image.png
点击会跳转到我们的app 。
什么?觉得不够丰富?来来来,阿秀同志请坐下,有好东西给你
创建一个带有和进度条 的Slice和,只需要替换我们上面的Slice和即可
fun createBrightnessSlice(sliceUri: Uri): Slice {val toggleAction =SliceAction(createToggleIntent(), "Toggle adaptive brightness", true)return ListBuilder(context, sliceUri, ListBuilder.INFINITY).addRow {it.apply {setTitle("Adaptive brightness")setSubtitle("Optimizes brightness for available light")//这是togglebuttonsetPrimaryAction(toggleAction)}}.addInputRange {it.apply {//这个是进度条setInputAction(brightnessPendingIntent)setMax(100)setValue(45)}}.build()}fun createToggleIntent(): PendingIntent {val intent = Intent(context, MyBroadcastReceiver::class.java)return PendingIntent.getBroadcast(context, 0, intent, 0)}class MyBroadcastReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {Toast.makeText(context, "Toggled:" + intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false),Toast.LENGTH_LONG).show()}}companion object {const val EXTRA_MESSAGE = "message"}
啥?就这些?你想点击Slice后,在Slice上面动态修改一些信息,这是你的应用吗?咋想这么多呢?好吧,怕你了,来来来(谷歌官网的代码有坑,真的有坑)
fun createDynamicSlice(sliceUri: Uri): Slice {return when (sliceUri.path) {"/ssy" -> {val toastAndIncrementAction = SliceAction(createToastAndIncrementIntent("Item clicked"),IconCompat.createWithResource(context, R.drawable.no1), "Increment.")ListBuilder(context, sliceUri, ListBuilder.INFINITY).addRow {it.apply {setPrimaryAction(toastAndIncrementAction)setTitle("Count: ${MyBroadcastReceiver.receivedCount}")setSubtitle("Click me")}}.build()}else -> {ListBuilder(context, sliceUri, ListBuilder.INFINITY).addRow { it.setTitle("URI not found.") }.build()}}}fun createToastAndIncrementIntent(s: String): PendingIntent {return PendingIntent.getBroadcast(context, 0,Intent(context, MyBroadcastReceiver::class.java).putExtra(android.app.slice.Slice.EXTRA_TOGGLE_STATE, s), 0)}class MyBroadcastReceiver : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {Toast.makeText(context, "Toggled:" + intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false),Toast.LENGTH_LONG).show()receivedCount++;context.contentResolver.notifyChange(sliceUri, null)}}companion object {var receivedCount = 0val sliceUri = Uri.parse("content://com.simple.slicesapplication/ssy")const val EXTRA_MESSAGE = "message"}}
这回行了吧,什么布局能不能改?啥你想加广告?真是 , 好吧好吧
Slice
这个大哥叫切片模板
定义你的切片模板
切片是通过使用构造的。允许您添加列表中显示的不同类型的行 。本节介绍每种行类型及其构造方式 。
Slice模板的最基本元素是。包含一个标签以及一个,并且是以下之一:
切片模板
由本节其余部分描述的模板构建器使用 。可以定义一个图像模式,用于确定如何为该动作呈现图像:
在大多数情况下,您应该使用为您的模板设置标题。标头可以支持以下内容:
下面显示了一些示例头部配置 。请注意,灰色框显示潜在的图标和填充位置:
image.png
标题在不同的表面上呈现
当需要切片时,显示表面决定如何渲染切片 。请注意 , 托管表面之间的渲染可能有所不同 。
在较小的格式中,通常只显示标题(如果存在) 。如果您为标题指定了摘要,则会显示摘要文本而不是字幕文本 。
如果您没有在模板中指定标题,则通常会显示添加到的第一行
image.png
那我们就试一下
fun createSliceWithHeader(sliceUri: Uri): Slice=ListBuilder(context, sliceUri, ListBuilder.INFINITY).setAccentColor(0xff0F9D) // Specify color for tinting icons.setHeader {it.apply {setTitle("Get a ride")setSubtitle("Ride in 4 min")setSummary("Work in 1 hour 45 min | Home in 12 min")}}.addRow {it.apply {setTitle("Home")setSubtitle("12 miles | 12 min | $9.00")addEndItem(IconCompat.createWithResource(context, R.drawable.ic_launcher_background), SliceHints.ICON_IMAGE)}}.build()}
image.png
感觉有点意思了,不过还是没有想象中该有的样子 。上下标题差不多有了 , 右边的icon可以多几个吗?
fun createSliceWithActionInHeader(sliceUri: Uri): Slice {// Construct our slice actions.val noteAction = SliceAction(takeNoteIntent,IconCompat.createWithResource(context, R.drawable.a),ICON_IMAGE, "Take note")val voiceNoteAction = SliceAction(voiceNoteIntent,IconCompat.createWithResource(context, R.drawable.b),ICON_IMAGE,"Take voice note")val cameraNoteAction = SliceAction(cameraNoteIntent,IconCompat.createWithResource(context, R.drawable.c),ICON_IMAGE,"Create photo note")// Construct the list.return ListBuilder(context, sliceUri, ListBuilder.INFINITY).setAccentColor(0xfff4b4) // Specify color for tinting icons.setHeader {it.apply {setTitle("Create new note")setSubtitle("Easily done with this note taking app")}}.addAction(noteAction).addAction(voiceNoteAction).addAction(cameraNoteAction).build()}
image.png
最后来个全家桶
image.png
fun createSliceWithGridRow(sliceUri: Uri): Slice {// Create the parent builder.val icon_a = IconCompat.createWithResource(context, R.mipmap.a)val icon_b = IconCompat.createWithResource(context, R.mipmap.b)val icon_c = IconCompat.createWithResource(context, R.mipmap.c)val icon_d = IconCompat.createWithResource(context, R.mipmap.d)val intent = Intent(context, MainActivity::class.java)val brightnessPendingIntent = PendingIntent.getActivity(context, 0, intent, 0)return ListBuilder(context, sliceUri, ListBuilder.INFINITY).setHeader {it.apply {setTitle("玩具")setPrimaryAction(SliceAction(brightnessPendingIntent, icon_c, "Famous restaurants"))}}.addRow {it.apply {setSubtitle("12 miles | 12 min | $9.00")addEndItem(IconCompat.createWithResource(context, R.mipmap.b), SliceHints.LARGE_IMAGE)}}.addGridRow {it.apply {addCell {it.apply {addImage(icon_a, LARGE_IMAGE)addTitleText("积木")addText("¥100")setContentIntent(brightnessPendingIntent)}}addCell {it.apply {addImage(icon_b, LARGE_IMAGE)addTitleText("摇杆")addText("¥200")setContentIntent(brightnessPendingIntent)}}addCell {it.apply {addImage(icon_c, LARGE_IMAGE)addTitleText("十合一卡")addText("¥300")setContentIntent(brightnessPendingIntent)}}addCell {it.apply {addImage(icon_d, LARGE_IMAGE)addTitleText("手柄t")addText("¥200")setContentIntent(brightnessPendingIntent)}}}}.build()}
本篇把、、的概念和简单的应用梳理了一遍,其中也发现了很多坑,网上好多文章,只是讲原理不写实例 , 或者有的人写了实例但是自己没有验证过,就是谷歌文档也有很多不清楚的地方,特别是的依赖的配置,所幸把大部分的问题解决 了,不过还有一些问题依然不清楚 , 查看了官方文档 , 谷歌了众多文章可是资料很少 , 希望有小伙伴可以给解惑 。一起学习 。
大家可以点个关注,告诉我大家想要深入探究哪些问题,希望看到哪方面的文章,我可以免费给你写专题文章 。。哈哈 。。。
【JetPack :Paging、WorkManager、Slices介绍和使用方】希望大家多多支持 。。你的一个关注,是我坚持的最大动力 。。