任务:
- 强化布局学习,用不同的布局类型实现计算器页面,并且保证每个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 List<SubjectsDTO> subjects;
public List<SubjectsDTO> getSubjects() {
return subjects;
}
public void setSubjects(List<SubjectsDTO> 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的条目布局; <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/movie_recyclerview"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<ImageView
android:id="@+id/cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher_round"
/>
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_toRightOf="@id/cover"
android:layout_marginLeft="10dp"
android:text="title"
android:textSize="20dp" />
<TextView
android:id="@+id/score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_below="@+id/title"
android:text="0.0"
android:layout_alignParentEnd="true"
android:layout_marginTop="20dp"
android:textSize="20dp" />
</RelativeLayout>
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;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mBinding = MovieFragmentBinding.inflate(inflater,container,false);
initMovieRecyclerView();
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<MovieAdapter.ViewHolder> {
private static final String TAG ="MovieAdapter";
List<MovieBean.SubjectsDTO> 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());
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);
Call<MusicBean> task = api.getMusic();
task.enqueue(new Callback<MusicBean>() {
@Override
public void onResponse(Call<MusicBean> call, Response<MusicBean> response) {
Log.d(TAG, "onResponse: "+response.code());
if (response.code()== HttpURLConnection.HTTP_OK) {
MusicBean result = response.body();
Log.d(TAG, "onResponse: "+result.toString());
}
}
@Override
public void onFailure(Call<MusicBean> 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<MovieBean> task = api.getMovie();
task.enqueue(new Callback<MovieBean>() {
@Override
public void onResponse(Call<MovieBean> call, Response<MovieBean> 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<MovieBean> 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和Activity之间的通信
Fragment Fragment = getSupportFragmentManager().findFragmentById(R.id.history_Fragment);
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中删除/替换完成后
Fragment生命周期解析
- 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的交互
- 组件获取:
- Fragment获取activity中的组件:getActivity().findViewById()
- activity中获取Fragment中的组件:getFragmentManager.findFragmentById()
- 数据传递:
- activity传递数据给Fragment:在activity中创建Bundle数据包,调用Fragment实例的setArguments(bundle)从而将Bundle数据包传给Fragment
- Fragment传递数据给activity:在Fragment中定义一个内部回调接口,在让包含该Fragment的activity实现该回调接口,Fragment就可以通过回调方法传数据了
缓存的设置
AsyncTask
public void readMovieListFromFile(){
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;
public class MovieBeanCopy {
private List<SubjectsDTO> subjects = new ArrayList<>();
public List<SubjectsDTO> getSubjects() {
return subjects;
}
public void setSubjects(List<SubjectsDTO> 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<n; i++){
JsonObject jsonObject = (JsonObject) jsonArray.get(i);
movieBeanCopy.subjects.add(SubjectsDTO.fromJson(jsonObject));
}
return movieBeanCopy;
}
public JsonArray toJson(){
JsonArray jsonArray = new JsonArray();
for(SubjectsDTO dto : subjects){
jsonArray.add(dto.toJson());
}
return jsonArray;
}
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 SubjectsDTO(String title, String cover, String rate){
this.title = title;
this.cover = cover;
this.rate = rate;
}
public JsonObject toJson(){
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("title", title);
jsonObject.addProperty("cover", cover);
jsonObject.addProperty("rate", rate);
return jsonObject;
}
public static SubjectsDTO fromJson(JsonObject jsonObject){
SubjectsDTO s = new SubjectsDTO(
jsonObject.get("title").toString(),
jsonObject.get("cover").toString(),
jsonObject.get("rate").toString()
);
return s;
}
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 +
'}';
}
}
}
开启小窗口
- adb install -t ----apk
- adb push -----json/sdcard
- adb root
- adb remount
- adb shell am startservice -n com.gwm.app.tool/com.gwm.app.tool.ToolService
Activity的启动模式
- standard 标准模式
- singleTop 栈顶复用模式
- singleTask 栈内复用模式
- singleInstance 单例模式
standard :标准模式
默认启动模式 就是新建
singleTop:栈顶复用模式
若需新建的activity位于任务栈栈顶 那么此activity实例就不会重建 而是重用栈顶的实例
singleTask: 栈内复用模式
若需要创建的activity位于栈内 如果不是栈顶 就需要将该活动的上面的活动都进行出栈
singleintance: 单例模式
创建新的任务栈
AlertDialog
- setTitle:设置对话框的标题,比如“提示”、“警告”等;
- setMessage:设置对话框要传达的具体信息;
- setIcon: 设置对话框的图标;
- setCancelable: 点击对话框以外的区域是否让对话框消失,默认为true;
- setPositiveButton:设置正面按钮,表示“积极”、“确认”的意思,第一个参数为按钮上显示的文字,下同;
- setNegativeButton:设置反面按钮,表示“消极”、“否认”、“取消”的意思;
- setNeutralButton:设置中立按钮;
- setOnShowListener:对话框显示时触发的事件;
- setOnCancelListener:对话框消失时触发的事件。
创建方式:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示:")
.setIcon(R.drawable.ic_launcher_foreground)
.setMessage("这是一个错误")
.setIcon(R.mipmap.ic_launcher)
.setCancelable(true);
最后调用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 指针异常。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewmodel"
type="com.myapp.data.ViewModel" />
</data>
<ConstraintLayout... />
</layout>
添加
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时序图绘制
|