软件工程作业的一些说明

软件工程作业要求写一款APP,安卓的应用开发也一直是自己的兴趣,所以整个作业的完成过程中还是很兴奋的。我们的APP是基于gank.io网站提供的文章数据接口,和美女图片接口,完成的一款开发技术干货APP,下面简单说一下完成作业过程中值得说的几个点。

MVC架构

作业要求使用MVC架构,MVC是Model-View-Controller的简写,在android里面,MVC很好理解,

  • Model是一个个数据实体类,它承载了应用中的数据,我们的APP中包括:用户的数据实体类User,封装的网络请求类(net包下的),文章的数据实体类GankModel,收藏的实体类SaveModel以及一些其他的常量类,和一些View的Adapter类,应该都属于Model层,Model为视图界面(View)层提供了根本的东西:


  • View就是用户能直接看到的视图界面,就对应android里面的一个个View,界面的布局写在xml文件里面,我们的App里面自定义的View就只有一个EmptyRecyclerView,处理了一些没有数据的空页面的情况,剩下的View都是用的是Android SDK中的UI控件,能够顺利完成我们的需求了:



    下面是很多布局文件,我们所有的视图都依赖这些xml:



    简单看一个其中的例子:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".controller.activity.LoginActivity">


    <ScrollView
        android:id="@+id/login_form"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:padding="10dp"
            android:id="@+id/email_login_form"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:layout_gravity="center_horizontal"
                android:src="@drawable/icon_app_ironman"
                android:id="@+id/iv_login_icon"
                android:layout_width="150dp"
                android:layout_height="150dp" />

            <TextView
                android:layout_marginTop="10dp"
                android:layout_gravity="center_horizontal"
                android:id="@+id/tv_welcome"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="18sp"
                android:textStyle="italic|bold"
                android:text="欢迎来到干货营!"/>
            <android.support.design.widget.TextInputLayout
                android:layout_marginTop="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <AutoCompleteTextView
                    android:id="@+id/tv_account"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/prompt_email"
                    android:inputType="textEmailAddress"
                    android:maxLines="1"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_marginTop="10dp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/et_password"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/prompt_password"
                    android:imeActionId="6"
                    android:imeOptions="actionUnspecified"
                    android:inputType="textPassword"
                    android:maxLines="1"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <CheckBox
                    android:id="@+id/cb_remember"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:text="记住密码" />
            </RelativeLayout>
            <Button
                android:id="@+id/btn_sign_in"
                android:elevation="10dp"
                android:background="@drawable/shape_login_btn"
                style="?android:textAppearanceSmall"
                android:textColor="@color/white"
                android:textSize="16sp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="@string/action_sign_in"
                android:textStyle="bold" />

            <Button
                android:id="@+id/btn_register"
                android:elevation="10dp"
                android:background="@drawable/shape_login_btn"
                style="?android:textAppearanceSmall"
                android:textColor="@color/white"
                android:textSize="16sp"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="@string/action_register"
                android:textStyle="bold" />
        </LinearLayout>
    </ScrollView>

这就是View层

  • Controller层自然就是安卓中最重要的Activity或者Fragment



    Controller顾名思义,它负责控制,相当于View层和Model层的中间层,View层的数据来源于Model层,它需要Controller层把Model的数据交给View,而用户的一些点击操作滑动操作可能改变Model层的数据,那也需要Controller层来扮演这个角色,将用户的操作转化成对数据的改变。典型的例子就是Activity中各种Button的setOnclickListener里面做的事情,就十分鲜明的说明了Activity作为Controller的作用。比如这是登录界面的两个Button的Listener:

        btn_register.setOnClickListener {
            startActivity(Intent(this, RegisterActivity::class.java))
        }
        btn_sign_in.setOnClickListener { onLogin() }

点击第一个Button注册,就跳转到注册的Activity,点击第二个Button登录,就进入登录状态,onLogin里面处理的登录的检查和跳转等等。

但事实上在写的过程中很难避免Activity或者Fragment要做一些视图的操作,比如setVisibility这些,因为xml几乎不具备可编程性。Controller层和View层的耦合还是比较严重的,所以其实有更好的一些其他的架构。。MVC这种模式事实上还是有很大的劣势的,至少在Android中是这样。

大概我们作业的结构就介绍完了:


下面再谈谈其他的几个点:

关于Kotlin

不得不说Kotlin包装的一些语法糖,吃起来真甜,目前还没有接触到多少Kotlin的高级特性,但是一些简单的特性,就已经让人爱不释手了。举个最简单的例子在Model层会有大量的数据实体类,我们拿最重要的这个GankModel举例子,Java语言是这样的:

public class GankModel {
    private boolean error;
    private List<ResultsEntity> results;

    public void setError(boolean error) {
        this.error = error;
    }

    public void setResults(List<ResultsEntity> results) {
        this.results = results;
    }

    public boolean getError() {
        return error;
    }

    public List<ResultsEntity> getResults() {
        return results;
    }

    public static class ResultsEntity implements Serializable{
        public ResultsEntity() {
        }

        public ResultsEntity(String _id, String createdAt, String desc, String publishedAt, String source, String type, String url, boolean used, String who, List<String> images) {
            this._id = _id;
            this.createdAt = createdAt;
            this.desc = desc;
            this.publishedAt = publishedAt;
            this.source = source;
            this.type = type;
            this.url = url;
            this.used = used;
            this.who = who;
            this.images = images;
        }

        private String _id;
        private String createdAt;
        private String desc;
        private String publishedAt;
        private String source;
        private String type;
        private String url;
        private boolean used;
        private String who;
        private List<String> images;

        public void set_id(String _id) {
            this._id = _id;
        }

        public void setCreatedAt(String createdAt) {
            this.createdAt = createdAt;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }

        public void setPublishedAt(String publishedAt) {
            this.publishedAt = publishedAt;
        }

        public void setSource(String source) {
            this.source = source;
        }

        public void setType(String type) {
            this.type = type;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public void setUsed(boolean used) {
            this.used = used;
        }

        public void setWho(String who) {
            this.who = who;
        }

        public void setImages(List<String> images) {
            this.images = images;
        }

        public String get_id() {
            return _id;
        }

        public String getCreatedAt() {
            return createdAt;
        }

        public String getDesc() {
            return desc;
        }

        public String getPublishedAt() {
            return publishedAt;
        }

        public String getSource() {
            return source;
        }

        public String getType() {
            return type;
        }

        public String getUrl() {
            return url;
        }

        public boolean getUsed() {
            return used;
        }

        public String getWho() {
            return who;
        }

        public List<String> getImages() {
            return images;
        }
    }
}

嗯 它太长了。。kotlin呢,就这么几行

class GankModel {
   
    var error: Boolean = false
    var results: List<ResultsEntity>? = null
    class ResultsEntity(_id: String, createdAt: String, desc: String, publishedAt: String, source: String, type: String, url: String, var used: Boolean, who: String, images: List<String>) : Serializable {
        var _id: String? = _id
        var createdAt: String? = createdAt
        var desc: String? = desc
        var publishedAt: String? = publishedAt
        var source: String? = source
        var type: String? = type
        var url: String? = url
        var who: String? = who
        var images: List<String>? = images
    }
}

他免去了各种繁琐而重复的get set,让人能把精力放在有意义的事情上,另外还有一个很舒服的就是kotlin的可空类型(就是上图中的那个带问号的类型),它的使用免去了大量的Java中为了避免空指针异常的判空If语句,也让人倍感清新。大量的语法糖也就不再举例子了,Kotlin的学习还在路上!(比较尴尬的是,kotlin在和Java交互中有一些坑,在这次作业App的开发中也遇到了一个导致崩溃的可空类型和Java的交互,需要特别注意!)

项目第一次接触的的一些第三方的好用的库

登录模块借助Bmob后端云bmob后端云
,收藏模块为了避免大量数据库的没有意义的增删改查代码,也是使用了Android中比较常用的realm数据库框架realm
,都在Application中做了初始化。


这是我们的Bmobuser实体类

open class User : BmobUser() {
    var age: Int = 0
    //true男、false女
    var gender: Boolean = false
    var intro: String? = null
    var nickname: String? = null
}

下面是为realm封装的一个工具类:

public class DBManager {

    /**
     * 收藏数据,保存到数据库中
     *
     * @param entity
     */
    public static void save(final SaveModel entity) {
        Realm realm = Realm.getDefaultInstance();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                realm.copyToRealm(entity);
            }
        });
    }

    /**
     * 取消收藏,从数据库中删除
     *
     * @param entity
     */
    public static void cancelSave(final SaveModel entity) {
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        SaveModel resultsEntity = realm.where(SaveModel.class).equalTo("_id", entity.get_id()).findFirst();
        resultsEntity.deleteFromRealm();
        realm.commitTransaction();
    }

    /**
     * 获取数据中所搜收藏记录
     *
     * @return
     */
    public static List<SaveModel> getAllData() {
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        RealmResults<SaveModel> results = realm.where(SaveModel.class).findAll();
        realm.commitTransaction();
        return results;
    }

    /**
     * 根据主键查询某条记录
     *
     * @return
     */
    public static SaveModel queryModel(String _id) {
        Realm realm = Realm.getDefaultInstance();
        realm.beginTransaction();
        SaveModel result = realm.where(SaveModel.class).equalTo("_id", _id).findFirst();
        realm.commitTransaction();
        return result;
    }
}

项目的细节

都是Android中的一些常见的View的使用,一些简单的网络请求的封装,Handler Recyclerview Fragment Activity等等都可以看我之前发的博客的知识总结,这感觉是很难说完的。
项目的一个难点是美女图片的加载,在那么多图片的情况下,有可能出现out of memory以及加载的卡顿,这方面一个是借助Glide的缓存策略去避免反复加载,另外是RecyclerView的缓存和回收去避免过多图片驻留内存。

由于平时还有各种学业压力,所以完成项目过程中有一段时间是很赶进度的,难免有些代码功能是实现了,但是在代码的整洁性,结构的清晰性上都有一定可以提升的空间(就是有些代码写的比较丑。。),日后会继续努力去完善的,但是最终的作品是完成了,希望能有一个好的成绩~感谢大家的齐心协力!
最后还要感谢github上各个大神的分享,从中汲取了很多知识和开发技巧,这次遇到的一些问题的难点,就借鉴了许多大神的做法,开源真是伟大!

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