架构(风格)
第一次接触安卓项目,一开始感觉无从下手。虽然已经学习了基本的UI组件的用法,但是出于对项目负责的态度,仍然不想就胡乱开始写。于是就从整个项目的架构开始思考,了解了MVC
,MVP
,MVVM
这三种风格的架构,后两者差别其实不大,我在项目中实际采用的就是第二种风格。下面我结合实际的项目,介绍一下使用这种架构的特点:
这是我的项目的整体分包情况,
接下来我们以获取当前用户的课程为例子,看一看如何用mvp
风格实现。首先我们需要的是处理数据,我们创建course
实体类,其实是一个简单的pojo用于封装课程信息。
public class Course implements Serializable {
@SerializedName("id")
private int id ;
@SerializedName("name")
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后我们再创建一个用于展示课程信息的界面组件
public class CourseListFragment extends Fragment implements CourseContract.View{
@BindView(R.id.course_recycler_view)
RecyclerView recyclerView;
CourseContract.Presenter presenter;
public CourseListFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = PresenterFactory.getCoursePresenter(this);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view =inflater.inflate(R.layout.fragment_course_list, container, false);
ButterKnife.bind(this,view);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext());
String token = TokenBuilder.getToken(preferences);
String username = preferences.getString("username","liuqin");
Log.d("get Username", username);
presenter.getCoursesByUsername(token,username);
return view;
}
@Override
public void showCourses(List<Course> courses) {
for(Course course : courses){
System.out.println(course.getName());
}
CourseAdapter adapter = new CourseAdapter(courses, getContext());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this.getContext()));
recyclerView.addItemDecoration(new GridItemDividerDecoration
(getContext(), R.dimen.divider_height, R.color.divider));
}
}
看到这里你可能已经注意到了,CourseListFragment
实现了一个接口CourseContract.View
,没错这个接口实际上正是使用mvp
架构的精髓所在,我们先来看看这个接口吧:
public interface CourseContract {
interface View {
void showCourses(List<Course> courses);
}
interface Presenter{
void getCoursesByUsername(String token , String username);
}
}
接口采用了内部类的写法,名为contract,包含了两个子类一个是View
,另一个是Presenter
从接口中定义的方法名我们也不难看出,这两个接口一个是用于界面展示的,而另一个则是用于后台处理的。我们刚才定义的CourseListFragment
实际上就实现了View
部分的接口,我们再来看看实现Presenter
部分的类吧:
public class CoursePresenterImpl implements CourseContract.Presenter {
private CourseContract.View view;
public CoursePresenterImpl (CourseContract.View view){
this.view = view;
}
@Override
public void getCoursesByUsername(String token, String username) {
ApiManager.getInstance().getCommonApi().getCoursesByUsername(token,username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Course>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
String err = (e.getMessage()==null)?"Get Courses failed":e.getMessage();
Log.e("error:",err);
e.printStackTrace();
}
@Override
public void onNext(List<Course> courses) {
Log.d("courseSize",courses.size()+"");
if(courses.size()==0){
Course course = new Course();
course.setName("软件工程与计算1");
course.setId(1);
courses.add(course);
}
view.showCourses(courses);
}
});
}
}
接下来,我们仔细看一下这个类的成员变量,它包含了一个对CourseContract.View
的引用,并实现了CourseContract.Presenter
接口;我们再回头看CourseListFragment
它包含了一个对CourseContract.Presenter
的引用。说到这里,大家应该就明白了:
mvp
架构的根本目的在于将model
与view
解耦。
它充分利用了面向接口编程和依赖倒置的思想,使得负责界面展示的组件能够专注于展示界面,而无需关注究竟如何获得需要展示的内容,以及了解这些数据的状态;同时负责处理数据的组件只需要关注数据本身,而无需关注这些数据将会由那种界面元素使用以及如何使用。这种做法克服了android
开发中的常见的却又令很多开发人员无奈的问题:我们的activity
或者fragment
将变得越来越臃肿,我们的controller
将只能与当前的界面绑定,想要更换UI将变得十分繁杂,界面组件与逻辑组件难分难解。
框架
其实框架这种东西,对于真正想要学习和了解android
的人来说并不是十分必要。但是为了加快开发,适当地引用一些好的框架的确可以使得开发工作事半功倍。
Butterknife
在处理界面元素的绑定以及事件监听的处理时,我们会感到在做大量的重复劳动,于是我选择了ButterKnife
这个轻量级的框架来帮助我们用注解的方式轻松解决上述问题。举个例子:
public class QuestionAnalysisActivity extends AppCompatActivity implements QuestionAnalysisContract.View{
@BindView(R.id.analysis_title_text)
TextView titleText;
@BindView(R.id.analysis_git_text)
TextView gitText;
@BindView(R.id.analysis_scored_text)
TextView scoredText;
@BindView(R.id.analysis_score_text)
TextView scoreText;
@BindView(R.id.analysis_compile_text)
TextView compileText;
@BindView(R.id.analysis_testcase_sum_text)
TextView testcaseSumText;
@BindView(R.id.analysis_testcase_passed_text)
TextView testcasePassedText;
private QuestionAnalysisContract.Presenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_question_analysis);
ButterKnife.bind(this);
this.presenter = new QuestionAnalysisPresenterImpl(this);
}
@Override
public void showAnalysis(QuestionResult analysis) {
}
}
值得注意的地方是这两行代码,帮助ButterKnife进行绑定
setContentView(R.layout.activity_question_analysis);
ButterKnife.bind(this);
Retrofit
由于本次项目是实现一个客户端,会有大量的网络请求代码,所以我采用了Retrofit
一款针对Android
的网络请求框架,底层实现依赖于okHttp
。整体来说使用起来还是相当简单的,举个例子:
public interface CommonApi {
@POST("user/auth")
Observable<Response<User>> login (@Body LoginUser user);
@GET("user/{username}/course")
Observable<List<Course>> getCoursesByUsername
(@Header("Authorization") String token, @Path("username") String username);
@GET("course/{courseId}/homework")
Observable<List<Homework>> getHomeworkByCourseId
(@Header("Authorization") String token, @Path("courseId") int courseId);
@GET("course/{courseId}/exercise")
Observable<List<Exercise>> getExerciseByCourseId
(@Header("Authorization") String token, @Path("courseId") int courseId);
@GET("course/{courseId}/exam")
Observable<List<Exam>> getExamByCourseId
(@Header("Authorization") String token, @Path("courseId") int courseId);
}
retorfit
帮助我们用简单的语法来定义http
请求接口,实际使用时我还配合了Rxjava
来实现异步的请求:
@Override
public void getCoursesByUsername(String token, String username) {
ApiManager.getInstance().getCommonApi().getCoursesByUsername(token,username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Course>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
String err = (e.getMessage()==null)?"Get Courses failed":e.getMessage();
Log.e("error:",err);
e.printStackTrace();
}
@Override
public void onNext(List<Course> courses) {
Log.d("courseSize",courses.size()+"");
view.showCourses(courses);
}
});
}
我们定义两个回调函数onNext
和onError
来分别处理请求成功和失败的情况。
参考链接