paging组件的用法和意义
一.Paging组件的意义
分页加载是在应用程序开发过程中十分常见的需求,我们经常需要以列表的方式加载大量的数据,这些数据通常来自网络或本地数据库。然而,如果一次性将所有数据全部加载出来,必然会消耗大量的时间和数据流量,而且用户可能只是需要一部分数据就行。因此,Google便推出了paging组件,来实现分页加载;分页加载就是对数据进行按需加载,在不影响用户体验的同时,还能节省数据流量,提升应用的性能。
二.Paging支持的架构类型
Paging支持3种架构类型,分别是:网络,数据库,网络+数据库
网络:也就是通过网络请求的方式去获得服务器返回的数据,然后分页加载出来
数据库:掌握了从网络上获取数据并加载出来,从数据库加载就变得很简单,只需替换数据源即可
网络+数据库:出于用户的体验,通常我们会对网络数据进行缓存,以便用户下次打开应用程序时,应用程序可以先展示缓存数据,我们通常会利用数据库对网络数据进行缓存,但这也意味着我们需要同时处理好网络和数据库这两个数据源。但是,多个数据源会让业务逻辑变得更为复杂,所以我们通常采用单一数据源作为解决方案,即从网络获取的数据,直接缓存进数据库,列表仅从数据库这个唯一的数据源获取数据。
三.三种分页机制的适用场景
PositionalDataSource:

适用于从任意位置加载任意数量的数据,且目标数据源中数据固定的情况。
PageKeyedDataSource:

适用于数据源以页的方式进行请求的情况,比如请求第二页的5条数据。
ItemKeyedDataSource:

适用于当目标数据的下一页需要依赖于上一页数据中最后一个对象中的某个字段作为key的情况,例如我请求key=9001后的5条数据作为下一页的数据。
下面会以PositionalDataSource为例进行讲解,其他方式极其相似。
四.分页机制的实现
我们以从豆瓣网上获取热度最高的250部电影为例进行讲解,这里我们使用Retrofit+OkHttp进行网络数据的获取,如果对这两个网络请求工具不熟悉的话,可以看这篇博客:https://www.cnblogs.com/luqman/p/okhttp_retrofit.html
这里我们请求的api接口是https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=0&limit=250,接口返回的前五条数据如下所示:
[{"rating":["9.7","50"],"rank":1,"cover_url":"https://img2.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p480747492.webp","is_playable":true,"id":"1292052","types":["犯罪","剧情"],"regions":["美国"],"title":"肖申克的救赎","url":"https:\/\/movie.douban.com\/subject\/1292052\/","release_date":"1994-09-10","actor_count":25,"vote_count":2912430,"score":"9.7","actors":["蒂姆·罗宾斯","摩根·弗里曼","鲍勃·冈顿","威廉姆·赛德勒","克兰西·布朗","吉尔·贝罗斯","马克·罗斯顿","詹姆斯·惠特摩","杰弗里·德曼","拉里·布兰登伯格","尼尔·吉恩托利","布赖恩·利比","大卫·普罗瓦尔","约瑟夫·劳格诺","祖德·塞克利拉","保罗·麦克兰尼","芮妮·布莱恩","阿方索·弗里曼","V·J·福斯特","弗兰克·梅德拉诺","马克·迈尔斯","尼尔·萨默斯","耐德·巴拉米","布赖恩·戴拉特","唐·麦克马纳斯"],"is_watched":false},{"rating":["9.6","50"],"rank":2,"cover_url":"https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2561716440.webp","is_playable":true,"id":"1291546","types":["剧情","爱情","同性"],"regions":["中国大陆","中国香港"],"title":"霸王别姬","url":"https:\/\/movie.douban.com\/subject\/1291546\/","release_date":"1993-07-26","actor_count":28,"vote_count":2150114,"score":"9.6","actors":["张国荣","张丰毅","巩俐","葛优","英达","蒋雯丽","吴大维","吕齐","雷汉","尹治","马明威","费振翔","智一桐","李春","赵海龙","李丹","童弟","沈慧芬","黄斐","徐杰","黄磊","冯远征","杨立新","方征","周璞","隋永清","宋小川","杜广沛"],"is_watched":false},{"rating":["9.6","50"],"rank":3,"cover_url":"https://img2.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p2578474613.webp","is_playable":true,"id":"1292063","types":["剧情","喜剧","爱情","战争"],"regions":["意大利"],"title":"美丽人生","url":"https:\/\/movie.douban.com\/subject\/1292063\/","release_date":"2020-01-03","actor_count":29,"vote_count":1332240,"score":"9.6","actors":["罗伯托·贝尼尼","尼可莱塔·布拉斯基","乔治·坎塔里尼","朱斯蒂诺·杜拉诺","赛尔乔·比尼·布斯特里克","玛丽萨·帕雷德斯","霍斯特·布赫霍尔茨","利迪娅·阿方西","朱利亚娜·洛约迪切","亚美利哥·丰塔尼","彼得·德·席尔瓦","弗朗西斯·古佐","拉法埃拉·莱博罗尼","克劳迪奥·阿方西","吉尔·巴罗尼","马西莫·比安奇","恩尼奥·孔萨尔维","吉安卡尔洛·科森蒂诺","阿伦·克雷格","汉尼斯·赫尔曼","弗兰科·梅斯科利尼","安东尼奥·普雷斯特","吉娜·诺维勒","理查德·塞梅尔","安德烈提多娜","迪尔克·范登贝格","奥梅罗·安东努蒂","沈晓谦","张欣"],"is_watched":false},{"rating":["9.6","50"],"rank":4,"cover_url":"https://img2.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p492406163.webp","is_playable":true,"id":"1295124","types":["剧情","历史","战争"],"regions":["美国"],"title":"辛德勒的名单","url":"https:\/\/movie.douban.com\/subject\/1295124\/","release_date":"1993-11-30","actor_count":49,"vote_count":1110744,"score":"9.6","actors":["连姆·尼森","本·金斯利","拉尔夫·费因斯","卡罗琳·古多尔","乔纳森·萨加尔","艾伯丝·戴维兹","马尔戈萨·格贝尔","马克·伊瓦涅","碧翠斯·马科拉","安德烈·瑟韦林","弗里德里希·冯·图恩","克齐斯茨托夫·拉夫特","诺伯特·魏塞尔","维斯瓦夫·科马萨","布拉德·雅各布维茨","Maciej Orlos","皮奥特·赛尔沃斯","Tadeusz Huk","马丁·塞梅洛格","托马斯·德德克","奥拉夫·卢巴申科","马瑞安·格林卡","约亨·尼克尔","阿格涅兹卡·克鲁科沃娜","阿格尼兹卡·旺格","托马斯·莫里斯","佐久间玲","吴俊全","约阿希姆·保罗·阿斯波克","彭河","戈兹·奥托","玛雅·奥丝塔泽斯卡","Maciej Kozlowski","艾尔文·莱德","Eugeniusz Priwieziencew","Marta Bizon","埃兹拉·达甘","吉恩·莱赫纳","Razia Israeli","拉米·希尔伯格","布兰科·拉斯蒂格","路德格·皮斯特","埃琳娜·勒文松","胡契克·卡勒塔","塔德乌什·布拉德茨基","亨里克·比斯塔","帕维·德朗柯","耶日·诺瓦克","安娜·穆查"],"is_watched":false},{"rating":["9.6","50"],"rank":5,"cover_url":"https://img1.doubanio.com\/view\/photo\/s_ratio_poster\/public\/p1505392928.webp","is_playable":true,"id":"1296141","types":["剧情","犯罪","悬疑"],"regions":["美国"],"title":"控方证人","url":"https:\/\/movie.douban.com\/subject\/1296141\/","release_date":"1957-12-17","actor_count":51,"vote_count":564999,"score":"9.6","actors":["泰隆·鲍华","玛琳·黛德丽","查尔斯·劳顿","埃尔莎·兰彻斯特","约翰·威廉姆斯","亨利·丹尼尔","伊安·沃尔夫","托林·撒切尔","诺玛·威登","尤娜·奥康纳","茹塔·李","贝丝·弗劳尔斯","比尔·厄尔文","J·帕特·奥马利","本·怀特","Paul Kruger","Jack Raine","Paul Power","乔治·佩林","威廉·H·奥布莱恩","奥托拉内史密斯","Frank McClure","Colin Kenny","Jeanne Lafayette","Wilbur Mack","Fred Rapport","利奥达·理查德斯","Glen Walters","Arthur Tovey","伯特史蒂文斯","Cap Somers","Lucille Sewall","斯考特西顿","Norbert Schiller","杰弗里·塞尔","John Roy","Al Roberts","Art Howard","Stuart Hall","Francis Compton","Philip Tonge","帕特里克·艾亨","富兰克林·法纳姆","玛乔丽·伊顿","史蒂夫·卡鲁瑟斯","George Calliga","乔治布鲁格曼","丹尼·鲍沙其","Brandon Beach","埃迪·贝克","沃尔特·培根"],"is_watched":false}]
有了这些json数据后,我们就可以编写对应的javaBean了,下面我们开始编写代码。
a.添加相关依赖:
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'androidx.paging:paging-runtime:2.1.2'
b.添加网络权限
c.构建网络请求框架:
public interface Api {
/**
* 获取电影院当前上映的电影
* https://movie.douban.com/j/chart/top_list?type=11&interval_id=100%3A90&action=&start=40&limit=20
*/
@GET("j/chart/top_list")
Call<List<Movie>> getMovies(@Query("start") int start, @Query("limit") int limit);
}
public class RetrofitClient {
private static final String BASE_URL="https://movie.douban.com";
private static RetrofitClient retrofitClient;
private final Retrofit retrofit;
private RetrofitClient(){
retrofit=new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()) //添加转换器
.client(getClient()) //添加OkHttp的拦截器
.build();
}
public static synchronized RetrofitClient getInstance(){
if(retrofitClient==null){
retrofitClient=new RetrofitClient();
}
return retrofitClient;
}
public Api getApi(){
return retrofit.create(Api.class);
}
private OkHttpClient getClient(){
OkHttpClient client=new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException { //拦截器用于添加请求参数,会在源url上进行修改
Request original = chain.request();
HttpUrl url = original.url();
HttpUrl currentUrl = url.newBuilder()
.addQueryParameter("interval_id", "100:90")
.addQueryParameter("type", "11")
.addQueryParameter("action", "")
.build();
Request newRequest = original.newBuilder()
.url(currentUrl)
.build();
return chain.proceed(newRequest);
}
})
.build();
return client;
}
}
d.创建Model类
public class Movie{
private List<String> rating;
private Integer rank;
private String cover_url;
private String is_playable;
private String id;
private List<String> types;
private List<String> regions;
private String title;
private String url;
private String release_date;
private Integer actor_count;
private Integer vote_count;
private String score;
private List<String> actors;
private String is_watched;
public List<String> getRating() {
return this.rating;
}
public void setRating(List<String> rating) {
this.rating = rating;
}
public Integer getRank() {
return this.rank;
}
public void setRank(Integer rank) {
this.rank = rank;
}
public String getCover_url() {
return this.cover_url;
}
public void setCover_url(String cover_url) {
this.cover_url = cover_url;
}
public String getIs_playable() {
return this.is_playable;
}
public void setIs_playable(String is_playable) {
this.is_playable = is_playable;
}
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getTypes() {
return this.types;
}
public void setTypes(List<String> types) {
this.types = types;
}
public List<String> getRegions() {
return this.regions;
}
public void setRegions(List<String> regions) {
this.regions = regions;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRelease_date() {
return this.release_date;
}
public void setRelease_date(String release_date) {
this.release_date = release_date;
}
public Integer getActor_count() {
return this.actor_count;
}
public void setActor_count(Integer actor_count) {
this.actor_count = actor_count;
}
public Integer getVote_count() {
return this.vote_count;
}
public void setVote_count(Integer vote_count) {
this.vote_count = vote_count;
}
public String getScore() {
return this.score;
}
public void setScore(String score) {
this.score = score;
}
public List<String> getActors() {
return this.actors;
}
public void setActors(List<String> actors) {
this.actors = actors;
}
public String getIs_watched() {
return this.is_watched;
}
public void setIs_watched(String is_watched) {
this.is_watched = is_watched;
}
@Override
public String toString() {
return "Movie{" +
"rating=" + rating +
", rank=" + rank +
", cover_url='" + cover_url + '\'' +
", is_playable='" + is_playable + '\'' +
", id='" + id + '\'' +
", types=" + types +
", regions=" + regions +
", title='" + title + '\'' +
", url='" + url + '\'' +
", release_date='" + release_date + '\'' +
", actor_count=" + actor_count +
", vote_count=" + vote_count +
", score='" + score + '\'' +
", actors=" + actors +
", is_watched='" + is_watched + '\'' +
'}';
}
}
e.编写一个类继承PositionalDataSource,并在此类中进行网络请求,获取服务器返回的数据。
public class MovieDataSource extends PositionalDataSource<Movie> {
public static final int pageSize=5;
@Override
public void loadInitial(@NonNull LoadInitialParams loadInitialParams, @NonNull LoadInitialCallback<Movie> loadInitialCallback) {//负责第一页数据的加载
int startPosition=0;
RetrofitClient.getInstance()
.getApi()
.getMovies(startPosition,pageSize)
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if(response.body()!=null){
loadInitialCallback.onResult(response.body(),0,250);//将数据返回给PagedList,250是指数据源中的数据总数,当调用了setEnablePlaceHolder(true)方法时,必须传入此参数,以便预留位置
}
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
@Override
public void loadRange(@NonNull LoadRangeParams loadRangeParams, @NonNull LoadRangeCallback<Movie> loadRangeCallback) {//负责第一页之后数据的加载
RetrofitClient.getInstance()
.getApi()
.getMovies(loadRangeParams.startPosition,pageSize) //在你滑动手机屏幕到底部请求下一页的数据时,loadRangeParams.startPosition会自动维护,不需要你手动修改
.enqueue(new Callback<List<Movie>>() {
@Override
public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) {
if(response.body()!=null){
loadRangeCallback.onResult(response.body());
}
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
}
});
}
}
f.创建MovieDataSourceFactory类,负责MovieDataSource的创建。
public class MovieDataSourceFactory extends DataSource.Factory<Integer, Movie>{
@NonNull
@Override
public DataSource<Integer, Movie> create() {
return new MovieDataSource();
}
}
g.有了Factory类之后,接下来需要创建ViewModel类,在这个类中通过LivePagedListBuilder类创建和配置PagedList,并使用LiveData包装PagedList,然后在MainActivity中监测PagedList中数据的变化,并更新页面。
public class MovieViewModel extends ViewModel {
public LiveData<PagedList<Movie>> moviePagedList;
public MovieViewModel(){
PagedList.Config config=new PagedList.Config.Builder()
.setEnablePlaceholders(true) //设置是否为那些数量已知,但尚未加载出来的数据预留位置
.setPageSize(5)
.setPrefetchDistance(3) //设置当距离底部还有多少数据时加载下一页数据
.setInitialLoadSizeHint(20) //设置首次加载数据的数量
.setMaxSize(65536*5)
.build();
moviePagedList=new LivePagedListBuilder<>(new MovieDataSourceFactory(),config).build(); //MovieDataSource中的onResult方法会把服务器返回的数据传递到PagedList当中
}
}
h.编写RecyclerView的适配器类,此类需要继承自PagedListAdapter。
public class MoviePagedListAdapter extends PagedListAdapter<Movie, MoviePagedListAdapter.ViewHolder> {
private final Context context;
public MoviePagedListAdapter(Context context){
super(diffCallback);
this.context=context;
}
private static final DiffUtil.ItemCallback<Movie> diffCallback=new DiffUtil.ItemCallback<Movie>() {
@Override
public boolean areItemsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {//根据id来判断两条数据是否是同一条数据
return oldItem.getId().equals(newItem.getId());
}
@SuppressLint("DiffUtilEquals")
@Override
public boolean areContentsTheSame(@NonNull Movie oldItem, @NonNull Movie newItem) {//比较两条数据的内容是否一样
return oldItem.equals(newItem);
}
};
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater=LayoutInflater.from(context);
MovieItemBinding movieItemBinding= DataBindingUtil.inflate(inflater,R.layout.movie_item,parent,false);
return new ViewHolder(movieItemBinding);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Movie movie = getItem(position);//从PagedList中获取数据,如果没有的话,PagedList会通知DataSource获取下一页的数据
if(movie!=null){
holder.movieItemBinding.setMovie(movie);
}
}
public static class ViewHolder extends RecyclerView.ViewHolder{
public MovieItemBinding movieItemBinding;
public ViewHolder(MovieItemBinding movieItemBinding) {
super(movieItemBinding.getRoot());
this.movieItemBinding=movieItemBinding;
}
}
}
i.MainActivity实现:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main);
activityMainBinding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
MoviePagedListAdapter moviePagedListAdapter=new MoviePagedListAdapter(this);
activityMainBinding.recyclerView.setAdapter(moviePagedListAdapter);
MovieViewModel movieViewModel = new ViewModelProvider(this).get(MovieViewModel.class);
movieViewModel.moviePagedList.observe(this, new Observer<PagedList<Movie>>() {
@Override
public void onChanged(PagedList<Movie> movies) {
moviePagedListAdapter.submitList(movies);//当PagedList数据发生变化时,通知适配器更新数据,然后用getItem()方法获取数据
}
});
}
}
j.布局文件:
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/recyclerView"/>
</LinearLayout>
</layout>
movie_item.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="movie"
type="com.example.paging.model.Movie" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/movieName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{movie.title}"/>
</LinearLayout>
</layout>
到这里,就可以运行程序,查看效果了!
不过,我在写代码的时候,遇到了一个bug调了很久,就是当我在MainActivity中将activityMainBinding.recyclerView.setHasFixedSize(true);这句代码加上时,加载不出来任何的数据;但是如果我将RecyclerView组件的布局高度改成match_parent后,即使设置了setHasFixedSize(true)也能加载出来数据,也不知道咋回事。
paging组件的用法和意义的更多相关文章
- Jetpack系列:Paging组件帮你解决分页加载实现的痛苦
相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...
- Ogre 1.8 terrain 和 paging 组件
以下转自:http://hi.baidu.com/xocoder/item/e8d87cf53d87612b753c4cfd OGRE地形生成 OGRE可以通过两个接口来生成地形,分别是void Te ...
- java Future用法和意义一句话击破
在并发编程时,一般使用runnable,然后扔给线程池完事,这种情况下不需要线程的结果. 所以run的返回值是void类型. 如果是一个多线程协作程序,比如菲波拉切数列,1,1,2,3,5,8...使 ...
- TCPClient组件和TCPServer组件的主要方法和属性
IdTCPClient属性1 : IOHandler 如果有相应的输入/输出操作,那么IOHandler相对应的组件或接口将提供一个虚拟/抽象的输入/输出接口给相应的网络连接2 : Intercept ...
- Svn中的tag标签的用法和意义
使用场景: 假如你的项目的某个版本已经完成测试开发.测试并已经上线,接下来街道新的需求,新项目开发需要修改多个文件的代码,当需求已经开发一段时间的时候,突然接到用户和测试人员的反馈,项目中某个重大的b ...
- vue组件最佳实践
看了老外的一篇关于组件开发的建议(强烈建议阅读英文原版),感觉不错翻译一下加深理解. 这篇文章制定一个统一的规则来开发你的vue程序,以至于达到一下目的. 1.让开发者和开发团队更容易发现一些事情. ...
- react - 解刨组件的多种写法
一,原始的createClass写法 对于写react组件,很多人第一印象往往是createClass,这是因为createClass是react组件最原始的写法,基本每个学react的人都是接触这种 ...
- ionic3+angular4开发混合app 之自定义组件
这里主要是记录ionic3+angular4开发混合app时自定义组件,我想自定义组件的方法和angular4应该类似,具体在纯angular4中自定义组件,暂时没有实践,个人觉得差别不大,之后实践了 ...
- Android之ASD组件(一)
Google在android5.0之后推出新设计标准Material Design,为了能在低版本上使用Material Design,google发布了Android Support Design支 ...
- Vue.js 组件编码规范
本规范提供了一种统一的编码规范来编写 Vue.js 代码.这使得代码具有如下的特性: 其它开发者或是团队成员更容易阅读和理解. IDEs 更容易理解代码,从而提供高亮.格式化等辅助功能 更容易使用现有 ...
随机推荐
- 【GiraKoo】Android Studio控制台乱码
[GiraKoo]Android Studio控制台乱码 启动Android Studio进行编译时,可能会遇到控制台出现异常的乱码. 本文介绍该情况的解决方案. ����: δ������쳣���� ...
- 【Java】Eclipse常用快捷键整理
前言 还是最近在上Java课,由于疫情原因,看的网课,那里的老师比较实战派,很多时候不知道按了什么快捷键就立马出现了很骚的操作.网上查询后发现了一些快捷键对于我这个eclipse小白还是挺常用的,整理 ...
- 用Linux命令操作mysql数据库
操作mysql数据库,相信大家最熟悉的应该是用navicat工具来新建数据库,建表,查询数据,查看表结构等. 但是如果数据库与本操作机器不在同一个局域网内,并且对方环境也不支持vpn的情况下,如何查询 ...
- Pycharm激活码,Pycharm稳定专属激活码(持续更新)
分享一下 PyCharm 2023.1.2 最新激活注册码,破解教程如下,可免费永久激活,亲测有效,下面是详细文档哦~ 申明:本教程 PyCharm 激活码收集于网络,请勿商用,仅供个人学习使用,如有 ...
- 【python基础】复杂数据类型-列表类型(元组)
1.初识元组 列表非常适合用于存储在程序运行期间可能变化的数据集.列表是可以修改的. 然而,有时候需要创建一系列不可修改的元素,元组可以满足这种需求 python将不能修改的值称为不可变的,而不可变的 ...
- GO通道:无缓冲通道与缓冲通道
转载请注明出处: 1.通道定义 在多个协程之间进行通信和管理,可以使用 Go 语言提供的通道(Channel)类型.通道是一种特殊的数据结构,可以在协程之间进行传递数据,从而实现协程之间的通信和同步. ...
- 全志G2D实现屏幕旋转,开机logo实现手动旋转。
产品设计出来之后啊,大家使用的时候觉得反过来使用更加便捷.但是屏幕显示是反的.那怎么办那????? 修改硬件费时费工,那能否软件实现那????? 如果纯软件使用那就太费系统资源了.于是就想到了使用全志 ...
- 记一次 .NET 某企业采购平台 崩溃分析
一:背景 1. 讲故事 前段时间有个朋友找到我,说他们的程序有偶发崩溃的情况,让我帮忙看下怎么回事,针对这种 crash 的程序,用 AEDebug 的方式抓取一个便知,有了 dump 之后接下来就可 ...
- Python运维开发之路《数据类型》
一. python数据类型 python的五大基本数据类型,数字.字符串.列表.元组.字典;其他数据类型,类型type.Null.文件.集合.函数/方法.类.模块. 1.数字 1 ①整型 2 十进制转 ...
- 【调制解调】PM 调相
说明 学习数字信号处理算法时整理的学习笔记.同系列文章目录可见 <DSP 学习之路>目录,代码已上传到 Github - ModulationAndDemodulation.本篇介绍 PM ...