安卓开发中的一点经验总结

架构(风格)

第一次接触安卓项目,一开始感觉无从下手。虽然已经学习了基本的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架构的根本目的在于将modelview解耦。

它充分利用了面向接口编程和依赖倒置的思想,使得负责界面展示的组件能够专注于展示界面,而无需关注究竟如何获得需要展示的内容,以及了解这些数据的状态;同时负责处理数据的组件只需要关注数据本身,而无需关注这些数据将会由那种界面元素使用以及如何使用。这种做法克服了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);
                    }
                });
    }

我们定义两个回调函数onNextonError来分别处理请求成功和失败的情况。

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容