- 强化布局学习,用不同的布局类型实现计算器页面,并且保证每个xml都可以直接使用,同时学习了解每种布局的特性和优劣势
- 强化编码规范,方法的抽取、变量定义、命名规则,尽快根据文档进行学习
- Activity改成Fragment,搞清楚两者之间的关系和使用方式
- 重新梳理各自代码后,学习使用UML工具,把计算器demo的实现思路和典型场景的时序图画清楚
- 强调产品和需求意识,对照手机计算器功能和逻辑完善demo
不需要这么复杂,glide就是先下载到本地,然后下一次显示,已经下载的就拿出来,如果内存缓存有了,就直接用。接口的话也简单,直接给okhttp做个缓存就行了。不需要自己一个一个保存。框架本来就提供这样功能,只是看你用不用
约束布局优点:极大程度减少布局层级,可以实现一些其他布局管理器不能实现的样式,适合复杂的大型布局
规则:
- 每个视图都必须至少有两个约束条件:一个水平约束条件,一个垂直约束条件
- 只能在共用同一平面的约束手柄与定位点之间创建约束条件。因此,视图的垂直平面(左侧和右侧)只能约束在另一个垂直平面上;而基准线则只能约束到其他基准线上。
- 每个约束句柄只能用于一个约束条件,但您可以在同一定位点上创建多个约束条件(从不同的视图)
约束布局使用margin必须注意的点:
- 控件必须在布局里约束一个相对位置;
- margin只能大于等于0;
// 常用属性
layout_constraintLeft_toLeftOf//目标view左边与另一个view左边对齐
layout_constraintLeft_toRightOf//目标view左边与另一个view右边对齐
layout_constraintRight_toLeftOf//目标view右边与另一个view左边对齐
layout_constraintRight_toRightOf//目标view右边与另一个view右边对齐
layout_constraintTop_toTopOf//目标view顶部与另一个view顶部对齐
layout_constraintTop_toBottomOf//目标view顶部与另一个view底部对齐
layout_constraintBottom_toTopOf//目标view底部与另一个view顶部对齐
layout_constraintBottom_toBottomOf//目标view底部与另一个view底部对齐
layout_constraintbaseline_tobaselineOf//基于baseline对齐
layout_constraintStart_toEndOf//目标view起始边缘与另一个view结束边缘对齐
layout_constraintStart_toStartOf//目标view起始边缘与另一个view起始边缘对齐
layout_constraintEnd_toStartOf//目标view结束边缘与另一个view起始边缘对齐
layout_constraintEnd_toEndOf//目标view结束边缘与另一个view结束边缘对齐
约束布局中权重的使用
重点:使用约束布局的权重,控件之间需要两两关联,不然就算设置了0dp还是没有效果。
TextView2里用到了app:layout_constraintLeft_toRightOf="@+id/TextView1"这个属性,他的意思是把TextView2的左边约束到TextView1的右边;
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"
RelativeLayout中的水平居中layout_centerHorizontal相当于在ConstraintLayout约束控件的左右为parent的左右;RelativeLayout中的垂直居中layout_centerVertical相当于在ConstraintLayout约束控件的上下为parent的上下;
log的等级- Verbose 详细的,推荐使用颜色白色
- Debug 调试信息,推荐使用绿色
- Info 通用信息,推荐使用蓝色
- Warning 警告信息,推荐使用黄色
- Error 错误信息,推荐使用红色
空指针的解决思路
首先我们要知道的是为什么会引发空指针一场,因为对象为空。那么问题就来了:为什么对象为空呢?
所以,同学们要明白的是对象的创建与使用时机,也就是说,发生空指针的时候
- 去找对象什么时候创建或者赋值;
- 去找空指针发生的地方;
- 如果对象为空时,不影响程序执行,可以加判空处理;
- 如果对象为空时,影响程序执行,则需要解决对象创建的时序问题。
豆瓣APP实现思路:
-
创建MovieBean类,里面的成员变量是api接口对应的字段;(用了一个AS的插件 GsonFormatPlus,直接将JSON字段转成对应的变量)
package com.example.douban.bean; import java.util.List; public class MovieBean { private Listsubjects; public List getSubjects() { return subjects; } public void setSubjects(List subjects) { this.subjects = subjects; } @Override public String toString() { return "MovieBean{" + "subjects=" + subjects + '}'; } public static class SubjectsDTO { private String episodesInfo; private String rate; private Integer coverX; private String title; private String url; private Boolean playable; private String cover; private String id; private Integer coverY; private Boolean isNew; public String getEpisodesInfo() { return episodesInfo; } public void setEpisodesInfo(String episodesInfo) { this.episodesInfo = episodesInfo; } public String getRate() { return rate; } public void setRate(String rate) { this.rate = rate; } public Integer getCoverX() { return coverX; } public void setCoverX(Integer coverX) { this.coverX = coverX; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public Boolean getPlayable() { return playable; } public void setPlayable(Boolean playable) { this.playable = playable; } public String getCover() { return cover; } public void setCover(String cover) { this.cover = cover; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Integer getCoverY() { return coverY; } public void setCoverY(Integer coverY) { this.coverY = coverY; } public Boolean getNew() { return isNew; } public void setNew(Boolean aNew) { isNew = aNew; } @Override public String toString() { return "SubjectsDTO{" + "episodesInfo='" + episodesInfo + ''' + ", rate='" + rate + ''' + ", coverX=" + coverX + ", title='" + title + ''' + ", url='" + url + ''' + ", playable=" + playable + ", cover='" + cover + ''' + ", id='" + id + ''' + ", coverY=" + coverY + ", isNew=" + isNew + '}'; } } } -
创建Fragment布局和对应的Fragment类加载布局,布局中添加recyclerview控件,recyclerview的条目布局;
package com.example.douban.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.Fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import com.example.douban.adapter.MovieAdapter; import com.example.douban.databinding.MovieFragmentBinding; public class MovieFragment extends Fragment { public MovieAdapter movieAdapter; MovieFragmentBinding mBinding; // Listlist; @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mBinding = MovieFragmentBinding.inflate(inflater,container,false); initMovieRecyclerView(); // list = SPsave.getHistory(getContext()); return mBinding.getRoot(); } private void initMovieRecyclerView() { mBinding.movieRecyclerview.setLayoutManager(new LinearLayoutManager(getContext())); movieAdapter = new MovieAdapter(); mBinding.movieRecyclerview.setAdapter(movieAdapter); } } -
为recyclerview 创建自定义适配器;
package com.example.douban.adapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.douban.R; import com.example.douban.bean.MovieBean; import com.squareup.picasso.Picasso; import java.util.ArrayList; import java.util.List; public class MovieAdapter extends RecyclerView.Adapter
{ private static final String TAG ="MovieAdapter"; List data = new ArrayList<>(); @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_movie, parent, false); ViewHolder holder = new ViewHolder(view); return holder; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { ImageView imgCover = holder.itemView.findViewById(R.id.cover); TextView titile = holder.itemView.findViewById(R.id.title); TextView score = holder.itemView.findViewById(R.id.score); MovieBean.SubjectsDTO movieBean = data.get(position); titile.setText(movieBean.getTitle()); score.setText(movieBean.getRate()); // Log.d(TAG, "onBindViewHolder: "+movieBean.getCover()); Picasso.with(imgCover.getContext()) .load(movieBean.getCover()) .into(imgCover); } @Override public int getItemCount() { return data.size(); } public void setData(MovieBean movieBean) { data.clear(); data.addAll(movieBean.getSubjects()); notifyDataSetChanged(); } public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(@NonNull View itemView) { super(itemView); } } } -
在MainActivity中进行视图绑定 使用ViewBinding;
package com.example.douban; import android.os.Bundle; import android.util.Log; import android.view.View; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.Fragment.app.Fragment; import androidx.Fragment.app.FragmentManager; import androidx.Fragment.app.FragmentTransaction; import com.example.douban.bean.MovieBean; import com.example.douban.bean.MusicBean; import com.example.douban.databinding.ActivityMainBinding; import com.example.douban.Fragment.BookFragment; import com.example.douban.Fragment.MovieFragment; import com.example.douban.Fragment.MusicFragment; import com.example.douban.util.RetrofitManager; import java.net.HttpURLConnection; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ ActivityMainBinding mBinding; private static final String TAG = "MainActivity"; MovieFragment movieFragment; MusicFragment musicFragment; BookFragment bookFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(mBinding.getRoot()); //隐藏标题栏 ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.hide(); } init(); initEvent(); repalceFragment(movieFragment); getMovie(); } private void init() { movieFragment = new MovieFragment(); musicFragment = new MusicFragment(); bookFragment = new BookFragment(); } private void initEvent() { mBinding.btnBook.setOnClickListener(this); mBinding.btnMovie.setOnClickListener(this); mBinding.btnMusic.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_movie: repalceFragment(movieFragment); getMovie(); break; case R.id.btn_music: repalceFragment(musicFragment); getMusic(); break; case R.id.btn_book: break; default: break; } } private void getMusic() { new Thread(new Runnable() { @Override public void run() { Retrofit retrofit = RetrofitManager.getRetrofit("https://api.uomg.com"); DouBanAPI api = retrofit.create(DouBanAPI.class); Calltask = api.getMusic(); task.enqueue(new Callback () { @Override public void onResponse(Call call, Response response) { Log.d(TAG, "onResponse: "+response.code()); if (response.code()== HttpURLConnection.HTTP_OK) { MusicBean result = response.body(); Log.d(TAG, "onResponse: "+result.toString()); // updateMusicList(result); } } @Override public void onFailure(Call call, Throwable t) { Log.d(TAG, "onFailure: "+t.toString()); } }); } }).start(); } private void repalceFragment(Fragment Fragment) { FragmentManager FragmentManager = getSupportFragmentManager(); FragmentTransaction transaction = FragmentManager.beginTransaction(); transaction.replace(R.id.content_Fragment,Fragment); transaction.addToBackStack(null); transaction.commit(); } private void getMovie() { new Thread(new Runnable() { @Override public void run() { Retrofit retrofit = RetrofitManager.getRetrofit("https://movie.douban.com"); DouBanAPI api = retrofit.create(DouBanAPI.class); Call task = api.getMovie(); task.enqueue(new Callback () { @Override public void onResponse(Call call, Response response) { Log.d(TAG, "onResponse: "+response.code()); if (response.code()== HttpURLConnection.HTTP_OK) { MovieBean result = response.body(); Log.d(TAG, "onResponse: "+result.toString()); updateMovieList(result); } } @Override public void onFailure(Call call, Throwable t) { Log.d(TAG, "onFailure: "+t.toString()); } }); } }).start(); } private void updateMovieList(MovieBean movieBean) { movieFragment.movieAdapter.setData(movieBean); } private void updateMusicList(MusicBean musicBean) { musicFragment.musicAdapter.setData(musicBean); } } 创建Retrofit实例的工具类
package com.example.douban.util; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitManager { public static Retrofit getRetrofit(String url){ return new Retrofit.Builder() .baseUrl(url) //对服务器返回的数据进行解析 .addConverterFactory(GsonConverterFactory.create()) .build(); } } -
创建Retrofit对象调用create()方法获得接口实例;
-
接口实例调用抽象方法形成一个任务对象;
-
任务对象异步执行;
-
判断response.code是200获得响应体;
-
依赖的.addConverterFactory(GsonConverterFactory.create())方法将json转成javaBean对象;
-
将对象数据传进recyclerview的适配器中;
-
适配器在onBindViewHolder中将数据绑定显示出来;
基本实现了放豆瓣APP,但是在APP断网情况下,不能对里面的内容进行缓存。
Android是使用栈来管理活动的,这个栈也被称为返回栈,先进后出。
活动状态-
运行状态
活动位于返回栈的栈顶,这时就是运行状态,系统最不愿意回收这种状态的活动。
-
暂停状态
活动不在栈顶,但仍然可见。例如:对话框
系统也不愿意回收这种状态的活动,但是在内存极低的情况下,才会去考虑回收这种状态下的活动。
-
停止状态
活动不在栈顶,完全不可见,系统仍然会为这种活动保存响应的状态和成员变量,但是在其他地方需要内存的时候,停止状态的活动可能会被系统吸收。
-
销毁状态
活动从返回栈中移除,那么就是销毁状态了。系统最倾向于回收这种状态的活动,从而保证手机的内存充足。
定义了7个回调方法
-
onCreate():活动第一次创建的时候被调用,在这个方法中完成活动初始化操作。
-
onStart():活动由不可见变为可见的时候调用,可见但是不可以和用户进行交互。
-
oResume():活动准备好和用户进行交互的时候调用,活动位于返回栈的栈顶,此时活动处于运行状态。
-
onPause():系统准备去启动或者恢复另一个活动的时候调用,在这个方法中将一些消耗的CPU资源释放掉,保存一些关键的数据,方法执行一定要快。在这里可以做一些存储的操作,因为onPause是进程被杀死唯一一个一定会被执行的操作,但是手机死机或者关机除外。存储的操作方法一定要靠谱,执行要快,否则会影响下一个活动的恢复或者启动,进而影响用户的体验。
这个时候活动还是可见的,只是处于停止状态。可见但是不可操作
-
onStop():活动完全不可见的时候调用,它和onPause()方法的主要区别在于启动的新活动是一个对话框的时候onPause()会执行 onStop()不会执行。
可见转为不可见的时候调用。
-
onDestroy():在活动被销毁之前调用,之后活动就是销毁状态了。
-
onRestart():活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
onCreate() 初始化操作onDestroy()释放内存
可见生存期onStart() 加载可见资源 onStop()对资源进行释放
前台生存期onResume() onPause()活动一直处在运行状态 可以和用户进行交互
生命周期解析:- 当Activity首次被创建时,会调用onCreate()方法,接着当显示给用户时,调用onStart(),如果要让Activity位于前台的话就需要调用onResume()方法,此时activity位于栈顶。
- 当有另一个activity覆盖当前的activity时,这个时候调用onPause()方法,将前一个activity的数据保存起来。
- 此时,如果你想让前一个activity不会再显示的话,调用onStop()方法停止该activity,但是如果你想让它回到前台的话,重新获得焦点的话,可以调用onResume()方法。
- onStop()后,你可以调用onDestroy()方法来销毁该activity,也是该activity最后一次被调用了,可以通过finish()关闭activity。
- 当内存资源不足的时候,就可能杀死处于onPause()的activity所在的进程,但是这种极端的情况很少会发生。
//从布局中获取Fragment的实例 Fragment Fragment = getSupportFragmentManager().findFragmentById(R.id.history_Fragment);
//从Fragment中获取activity实例 MainActivity activity = (MainActivity) requireActivity();Fragment生命周期
我们可以将Fragment看做是一个小的activity,又称activity片段。使用Fragment将屏幕划分成几块,进行分组,进行模块化管理,从而可以更加方便的在运行过程中动态的更新activity的用户界面。
Fragment不能单独使用,它需要嵌套在activity中使用,即使拥有自己的生命周期,但是还是会受到宿主activity的生命周期的影响。
Fragment的四种状态
-
运行状态
Fragment可见,且它所关联的活动也处于运行状态,碎片也处于运行状态。
-
暂停状态
活动进入暂停状态(由于一个未占满屏幕的活动被添加到了栈顶),和它相关联的可见碎片就会进入到暂停状态。
-
停止状态
活动进入停止状态,与他相关联的碎片进入停止状态
-
销毁状态
碎片总是依附于活动存在的,活动销毁,碎片也销毁
-
onAttach():Fragment添加到activity中,只调用一次
-
onCreate():创建Fragment时调用,只调用一次
-
onCreateView():每次创建Fragment的view组件时调用,会返回显示的view
-
onActivityCreate():Fragment所在的activity启动完成后回调
-
onStart():启动Fragment时回调
-
onResume():恢复Fragment时回调,onStart方法后一定回调onResume,onStart可见,onResume才能交互
-
onPause():暂停Fragment时被回调
-
onStop():停止Fragment时被回调
-
onDestroyView():销毁Fragment所包含view组件的时候
-
onDestroy():销毁Fragment时
-
onDetach():将Fragment从activity中删除/替换完成后
- activity加载Fragment的时候,依次调用下面的方法:onAttach onCreate onCreateVIew onActivityCreated onStart onResume
- 当我们弄出一个悬浮的对话框风格的activity,或者其他,就是让Fragment所在的activity可见,但是不获得焦点onPause
- 当对话框关闭,activity又获得焦点:onResume
- 替换Fragment,并调用addToBackStack()将他添加到back栈中 onPause onStop onDestroyView 注意此时的Fragment还没有被销毁
- 当我们按下键盘上的回退键。Fragment会再次显示出来:onCreateView onActivityCreated onStart onResume
- 如果我们替换后,在事务commit之前没有调用addToBackStack()方法Fragment添加到back栈中的话,又或者退出了activity的话,那么用户执行回退操作进行Fragment的恢复,该Fragment将重新启动,如果不向返回栈添加事务,则系统会移除或者替换Fragment时将其销毁。
addToBackStack()方法的作用:当移除或替换一个Fragment并向返回栈添加事务时,系统会停止(而非销毁)移除的Fragment。如果用户执行回退操作进行Fragment的恢复,该Fragment将重新启动,如果不像返回栈添加事务,则系统会在移除或替换Fragment时将其销毁。
静态加载Fragment必须添加ID属性
动态添加Fragment- 通过getFragmentManager() 获得FragmentManager对象
- 获得FragmentTransaction对象fm.beginTransaction();
- 调用add()方法或者replace()方法加载Fragment;add(要传入的容器,Fragment对象)
- 在前面的基础上还需调用commit()方法提交事务,当然还有其他的方法,如remove
- 组件获取:
- Fragment获取activity中的组件:getActivity().findViewById()
- activity中获取Fragment中的组件:getFragmentManager.findFragmentById()
- 数据传递:
- activity传递数据给Fragment:在activity中创建Bundle数据包,调用Fragment实例的setArguments(bundle)从而将Bundle数据包传给Fragment
- Fragment传递数据给activity:在Fragment中定义一个内部回调接口,在让包含该Fragment的activity实现该回调接口,Fragment就可以通过回调方法传数据了
AsyncTask
// jiangfudao
public void readMovieListFromFile(){
// AsyncTask asyncTask = new MyReadMovieListFromFileTask();
// asyncTask.execute();
new MyReadMovieListFromFileTask().execute();
}
public class MyReadMovieListFromFileTask extends AsyncTask{
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected Object doInBackground(Object[] objects) {
//在任务被线程池执行时调用,通常用来做一些准备操作
return null;
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
}
缓存思路
MovieBeanModify
package com.example.douban.bean;
import android.content.Context;
import android.content.SharedPreferences;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
// jiangfudao
public class MovieBeanCopy {
private List subjects = new ArrayList<>();
public List getSubjects() {
return subjects;
}
public void setSubjects(List subjects) {
this.subjects = subjects;
}
@Override
public String toString() {
return "MovieBean{" +
"subjects=" + subjects +
'}';
}
public void saveToFiles(Context context, int saveType){
JsonArray ja = toJson();
if(0 == saveType){
SharedPreferences sharedPreferences = context.getSharedPreferences("movie", 0);
sharedPreferences.edit().putString("movie", ja.toString()).commit();
}else{
try {
File file = context.getExternalFilesDir("movie.txt").getAbsoluteFile();
file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(ja.toString().getBytes("utf-8"));
fileOutputStream.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static MovieBeanCopy readFromFile(Context context, int saveType){
MovieBeanCopy movieBeanCopy = new MovieBeanCopy();
String json = "";
if(0 == saveType){
SharedPreferences sharedPreferences = context.getSharedPreferences("movie", 0);
json = sharedPreferences.getString("movie", "");
}else{
File file = context.getExternalFilesDir("movie.txt").getAbsoluteFile();
if(file.exists()){
try {
FileInputStream input = new FileInputStream(file);
byte[] b = new byte[input.available()];
input.read(b);
json = new String(b, "utf-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
JsonArray jsonArray = (JsonArray) JsonParser.parseString(json);
int n = jsonArray.size();
for(int i=0; i
最后调用show()方法显示出来。
活动生命周期
- onCreate():活动一开始创建的时候,在这里面进行一些初始化操作
- onStart():活动由不可见变为可见的时候调用
- onResume():活动准备好和用户进行交互的时候调用,这个时候活动位于返回栈的栈顶,处于运行状态。
- onPause:这个时候系统准备去启动或者恢复另一个活动的时候调用,这个时候活动还是可见的
- onStop:活动由可见转为不可见的时候调用,它和onPause方法的区别就在于。如果启动的活动是一个对话框的时候,那么onPause会执行,而onStop不会执行
- onDestroy:活动由停止状态转为销毁状态调用
- onRestart:活动由停止状态转为运行状态的时候调用,也就是活动被重新启动了,这个时候不会再去调用onCreate() 只会调用onStart() onResume()
完整的生命周期
onCreate——onDestroy
可见生存期
onStart(加载可见资源)——onStop(释放内存)
前台生存期
onResume——onPause活动一直处在运行状态,可以和用户进行交互
注意:
这些方法都是回调方法,我们不能够去调用,只能重写方法里面的内容,什么时候调用是Activity来决定的,我们能够手动调用的就只有finish()方法,该方法用于关掉某个Activity。
活动的启动模式
- standard:标准模式,无脑模式,就是新建
- singleTop:栈顶复用模式,如果新建的activity位于返回栈的栈顶,那么就直接用这个实例,不用新建,如果没有位于栈顶,那么还是得重建。
- singleTask:栈内复用模式,如果新建的activity位于返回栈中,那么会将这个activity之上的活动都进行出栈。
- singleInstance:单例模式,直接创建一个新的返回栈,将新建的活动放在这个新的返回栈中。
Handler
如果要让新启动的线程周期性的修改UI组件的属性值,怎么办?
Handler类
- UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
- Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象
- Message:Handler接收与处理的消息对象
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;
- Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!
void handleMessage(Message msg):处理消息的方法,通常是用于被重写!
sendEmptyMessage(int what):发送空消息
sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信
息 se
ndMessage(Message msg):立即发送信息
sendMessageDelayed(Message msg):指定延时多少毫秒后发送信息
final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息
如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为
指定对象的消息
UML时序图学习
作用:
-
展示对象之间的交互顺序
-
相比其他UML图,时序图更强调交互的时间顺序
-
可以直观的描述并发进程
-
角色:系统角色,可以是人或者其他系统
-
对象:对象的命名有三种:
- 对象名和类名;
- 只显示类名,不显示对象,即为一个匿名类;
- 只显示对象名,不显示类名。
-
生命线:时序图每个对象和底部中心都有一条垂直的虚线,这就是对象的生命线(对象的时间线)。以一条垂直的虚线表示。
-
控制焦点:控制焦点代表时序图中在对象时间线上某段时期执行的操作。以一个很窄的矩形表示。
-
消息:表示代表对象之间发送的消息。消息分为三种类型;
- 同步消息:消息发送者把控制传递给消息的接受者,然后停止活动,等待消息的接受者放弃或者返回控制。用来表示同步的意义。以一条实线+实心箭头表示。
- 异步消息:消息发送者把控制传递给消息的接受者,然后继续自己的活动,不等待接受者返回消息或者控制。异步消息的接受者和发送者是并发工作的。以一条实线+大于号表示。
- 返回消息:返回消息表示从过程调用返回,以小于号+虚线表示。
-
自关联消息:表示方法的自身调用或者一个对象内的一个方法调用另外一个方法。以一个半闭合的长方形+下方实心箭头表示。
变更仿豆瓣部分代码
- 有网络:
- 响应:缓存,再将数据添加到Adapter中;
- 未响应:主线程吐司提示;
- 无网络:读取缓存
- 缓存为空:吐司提示;
- 缓存非空:以set的形式将缓存数据传递给Adapter;
时序图绘制
常用控件属性学习
- Src指的内容,填入图片后并不会拉伸
- Background指的是背景,填入图片后会根据给定的宽高拉伸
ViewModel:以注重生命周期的方式管理界面相关的数据
使用ViewModel来保存数据
LiveData:在底层数据库更改时通知视图
DataBinding
使用好处:借助布局文件中的绑定组件,可以移除 Activity 中的许多界面框架调用,使其维护起来更简单、方便。还可以提高应用性能,并且有助于防止内存泄漏以及避免发生 Null 指针异常。
添加
buildFeatures {
dataBinding true
}
ViewModel存储方式
注意:
上下文:Context context ,比较可靠的方式是使用ApplicationContext:可以理解为指向app的顶级引用。
上下文传进去的是MainActivity可能会导致内存泄漏,因为MIanActivity频繁的创建和删除会被保存占用内存资源。
Java的四种修饰符
- public:全局可见;(在整个工程中可见)
- private:只在自己的类中可见;
- protected:在自己及子类中可见;
- 默认:在其所在包中可见;
存储持久化数据的应该写在onPause()中
因为onPause()是唯一一个保证进程被杀之前会调用的。
在onPause()中可以做一些资源回收和数据存储的工作,但是不能太耗时,否则会影响到新的Activity的显示。
意外情况:手机直接关机、死机,这种情况数据是不会保存的。
- onAttach 将fragment添加到activity中
- onCreate 创建fragment
- onCreateView 加载fragment中的view组件 返回view
- onActivityCreate fragment所在的activity启动完成时回调
- onStart fragment由不可见转为可见的时候
- onResume fragment可以跟用户进行交互的时候
- onPause fragment进入暂停状态,可见不可交互
- onStop fragment停止状态,由可见转为不可见
- onDestroyView 销毁fragment中的view组件
- onDestroy 销毁fragment
- onDetach 将fragment从activity中移除、替换(解除关联的时候调用)
Handler类
如果想让新创建的线程周期性的更新UI组件的属性值,怎么办?
Handler类
- UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
- Handler:作用就是发送和处理信息,如果希望Handler类正常工作,在当前线程中要有一个Looper对象
- Message:接收和处理的消息对象
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue
- Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理
apk发版
- 找到自己对应的模块
- 选择Build with Paramerters
Service学习
定义:存在后台为我们执行一些耗时或者需要长时间执行的一些操作。
Service两种启动模式,同样都有生命周期,启动模式不同对应的生命周期也不同。
生命周期函数解析:
- onCreate():当Service第一次被创建后立即回调该方法,该方法在整个生命周期中只会调用一次
- onDestroy():当Service被关闭时回调该方法,该方法只会调用一次
- onStartCommand(intent,flag,startId):当客户端调用startService(Intent)方法时会回调,多次调用StartService方法,但不会再创建新的Service对象,而是继续复用前面产生的Service对象,但会继续回调onStartCommand()方法。
- IBinder onOnbind(intent):该方法是Service都必须实现的方法,该方法会返回一个IBinder对象,APP通过该对象与Service组件进行通信
- onUnbind(intent):当该Service上绑定的所有客户端都断开时会回调该方法
启动方式:
- StartService()启动Service
- BindService()启动Service
- PS:还有一种,就是启动Service后,绑定Service
BroadcastReceiver:
-
标准广播
完全异步执行的广播,在广播发出之后,所有的广播接收器会在同一时间接收到这条广播,无法被截断
-
有序广播
同步执行的广播,在广播发出之后,优先级高的广播接收器可以优先接收到广播
注册广播
不要再广播里添加过多的逻辑或者进行任何耗时操作,因为在广播中是不允许开辟线程的,当onReceiver()方法运行较长时间(超过10秒)还没有结束的话,那么程序会报错(ANR),广播更多的时候扮演的是一个打开其他组件的角色,比如启动Service,Notification提示,Activity等。
注册时间和接触注册时间:
一般我们在onResume的时候进行注册,在onDestory的时候解除注册。
仿豆瓣APP时序图绘制



