浅谈移动端开发

一 移动平台主流都是哪些 ?
二 移动开发有哪些方式 ?
三 具体如何进行移动开发 ?

面向读者
1 如果你是移动开发后端,你知道你给的接口前端是怎么处理的么?
2 如果你是移动前端开发,你知道后台是接口是怎么实现的么?
3 如果你做的是跟移动开发相关的工作,产品设计,你知道我们是如何一步步实现你的设计个构想的么。
以上如果有你还不了解的,那么请接着往下阅读,如果都了解的,那你可以关闭本页面了.
简介:移动开发也称为手机开发或叫做移动互联网开发。是指以手机、PDA、UMPC等便携终端为基础,进行相应的开发工作,由于这些随身设备基本都采用无线上网的方式,因此,业内也称作为无线开发。

开篇之前我们先简略的讲一下移动手机的发展历史。
一. 1902年,一个叫做“内森·斯塔布菲尔德”的美国人在肯塔基州默里的乡下住宅内制成了第一个无线电话装置,这部可无线移动通讯的电话就是人类对“手机”技术最早的探索研究。

内森·斯塔布菲尔德

对手机最早的探索者——内森·斯塔布菲尔德

二 1938年,美国贝尔实验室为美国军方制成了世界上第一部“移动电话”手机。

第一部“移动电话

三.1983年,世界上第一台移动电话终于问世——摩托罗拉DynaTAC8000X(大哥大),是世界上首部获得美国联邦通讯委员会(FCC)认可并正式投入商用的蜂窝式移动电话。这部手机在1983年首次将贝尔实验室1947年提出的移动电话概念和70年代提出的蜂窝组网技术概念变为了现实。这个移动通讯业界的第一奠定了摩托罗拉手机部门在移动通讯业界20余年不可动摇的地位。

1世界上第一台移动电话

四 . 1999年,第一部智能手机 摩托罗拉天拓A6188

第一部智能手机
 这款A6188采用了摩托罗拉公司自主研发的龙珠(Dragon ball EZ)16MHzCPU,支持WAP1.1无线上网,采用了PPSM(Personal Portable Systems Manager)操作系统。A6188一经推出,便成为了高端商务人士的首选,至今我们还能偶尔看到这款开辟一个时代的传奇手机。同时,这款手机也是全球第一部具有触摸屏的手机,它同时也是第一部中文手写识别输入的手机。

五 2007年,iphone出世,触屏+应用引爆智能机新时代。

iphone诞生

六 . 2008年,首部Android手机,由HTC制造,型号为HTC G1。

android系统的手机诞生
一 移动平台主流都是哪些 ?
iOS

iOS是由苹果公司为iPhone、iPod touch以及iPad开发的闭源操作系统。就像其基于的Mac OS X操作系统一样,它也是以Darwin为基础的。原本这个系统名为iPhone OS,直到2010年6月7日WWDC大会上宣布改名为iOS。iOS的系统结构分为四个层次:核心操作系统(the Core OS layer),核心服务层(the Core Services layer),媒体层(the Media layer),Cocoa 触摸框架层(the Cocoa Touch layer)。

IOS系统架构图

支持生产商:苹果。

开发语言:objective-c,swift
主要开发工具:Xcode
设计规范: Human Interface Guidelines

中文翻译

开发方向:逆向开发,App应用开发
安卓

Android是Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统,该平台由操作系统、中间件、用户界面和应用软件组成 [18]

Android一词的本义指“机器人”。同时也Android的系统架构和其操作系统一样,采用了分层的架构。从架构图看,Android分为四个层,从高层到低层分别是应用程序层、应用程序框架层、系统运行库层和Linux内核层。

android系统架构层.jpeg

Android在正式发行之前,最开始拥有两个内部测试版本,并且以著名的机器人名称来对其进行命名。
它们分别是:阿童木(AndroidBeta),发条机器人(Android 1.0)。
后来由于涉及到版权问题,谷歌将其命名规则变更为用甜点作为它们系统版本的代号的命名方法。甜点命名法开始于Android 1.5发布的时候。
作为每个版本代表的甜点的尺寸越变越大,然后按照26个字母数序:纸杯蛋糕(Android 1.5),甜甜圈(Android 1.6),松饼(Android 2.0/2.1),冻酸奶(Android 2.2),姜饼(Android 2.3),蜂巢(Android 3.0、Android 3.1和Android 3.2),冰激凌三明治(Android 4.0),果冻豆(Jelly Bean,Android4.1、Android 4.2和Android 4.3,以及棒棒糖(Android5.0)。用户可通过ROOT获得更好的体验。
代表支持生产商:三星、小米、华为、魅族、中兴、摩托罗拉、HTC、LG、索尼。

开发语言:java,kotlin
主要开发工具:Android Studio
设计语言: MaterialsDesign
开发方向:底层驱动开发,嵌入式开发,App应用开发
二 移动开发有哪些方式 ?

1 Native App: 本地应用程序(原生App)
2 Web App:网页应用程序(移动web)
3 Hybrid App:混合应用程序(混合App)


1.Native APP

Native APP 指的是原生程序,一般依托于操作系统,有很强的交互,是一个完整的App,可拓展性强,需要用户下载安装使用。(简单来说,原生应用是特别为某种操作系统开发的,比如iOS、Android、黑莓等等,它们是在各自的移动设备上运行的)

该模式通常是由“云服务器数据+APP应用客户端”两部份构成,APP应用所有的UI元素、数据内容、逻辑框架均安装在手机终端上。

原生应用程序是某一个移动平台(比如iOS或安卓)所特有的,使用相应平台支持的开发工具和语言(比如iOS平台支持Xcode和Objective-C,安卓平台支持Android studio和Java)。原生应用程序看起来(外观)和运行起来(性能)是最佳的。

优点:

能够与移动硬件设备的底层功能,比如个人信息,摄像头以及重力加速器等等。
可访问手机所有功能(GPS、摄像头)。
速度更快、性能高、整体用户体验不错。
支持大量图形和动画
比移动Web App运行快
应用审核流程会保证让用户得到高质量以及安全的App
官方会发布很多开发工具或者人工支持来帮助你的开发
页面存放于本地

缺点:

开发成本高,尤其是当需要多种移动设备来测试时
因为是不同的开发语言,所以开发,维护成本也高
因为用户使用的App版本不同,所以你维护起来很困难
不夸平台
审核流程复杂且慢,会严重影响你的发布进程
上线时间不确定(App Store审核过程不一)
内容限制(App Store限制)
获得新版本时需重新下载应用更新(提示用户下载更新,用户体验差)

2.Web APP

Web App 指采用Html5语言写出的App,不需要下载安装。类似于现在所说的轻应用。生存在浏览器中的应用,基本上可以说是触屏版的网页应用。(Web应用本质上是为移动浏览器设计的基于Web的应用,它们是用普通Web开发语言开发的,可以在各种智能手机浏览器上运行)

Web App开发即是一种框架型APP开发模式(HTML5 APP 框架开发模式),该开发具有跨平台的优势,该模式通常由“HTML5云网站+APP应用客户端”两部份构成,APP应用客户端只需安装应用的框架部份,而应用的数据则是每次打开APP的时候,去云端取数据呈现给手机用户。

HTML5应用程序使用标准的Web技术,通常是HTML5、JavaScript和CSS。这种只编写一次、可到处运行的移动开发方法构建的跨平台移动应用程序可以在多个设备上运行。虽然开发人员单单使用HTML5和JavaScript就能构建功能复杂的应用程序,但仍然存在一些重大的局限性,具体包括会话管理、安全离线存储以及访问原生设备功能(摄像头、日历和地理位置等)。

优点:

支持设备广泛
较低的开发成本
可即时上线
无内容限制
任何时候都可以发布App,不需要官方的审核
纯H5 APP快速开发、低成本、多平台
用户可以直接使用最新版本(自动更新,不需用户手动更新)
跨平台开发
页面存放于web服务器(受限于UIwebview)(减少了内存,但是会增加服务器的压力)

缺点:

只能使用有限的移动硬件设备功能,无法使用很多移动硬件设备的独特功能
要同时支持多种移动设备的浏览器让开发维护的成本也不低(也要适配不同的浏览器),如果用户使用更多的新型浏览器,那问题就更不好处理了
对于用户来说,这种App很难被用户发现
这里的数据获取都是在资源页面上异步完成的,因为只有这样才能让这些资源页面完成预加载或者渲染。(异步的话都涉及到耗时的问题)
表现差(对联网的要求比较大)
用户体验没那么炫
图片和动画支持性不高
访问手机硬件功能比较费力

3.Hybrid APP

Hybrid APP指的是半原生半Web的混合类App。需要下载安装,看上去类似Native App,但只有很少的UI Web View,访问的内容是 Web 。

混合应用程序让开发人员可以把HTML5应用程序嵌入到一个细薄的原生容器里面,集原生应用程序和HTML5应用程序的优点(及缺点)于一体。

混合应用大家都知道是原生应用和Web应用的结合体,采用了原生应用的一部分、Web应用的一部分,所以必须在部分在设备上运行、部分在Web上运行。不过混合应用中比例很自由,比如Web 占90%,原生占10%;或者各占50%。

有些应用最开始就是包了个原生客户端的壳,其实里面是HTML5的网页,后来才推出真正的原生应用。比较知名的APP,比如手机百度和淘宝客户端 Android版,走的也是Hybrid App的路线,不过手机百度里面封装的不是WebView,而是自己的浏览内核,所以体验上更像客户端,更高效。
(1)第一种方案:Web架构为重

优点:
全Web开发,一定程度上有利于Web前端技术人员快速地构建页面样式
有利于在不同的平台上面展示同一个交互层
便于调试,开发的时候可以通过浏览器的
方式进行调试,工具丰富。
兼容多平台
顺利访问手机的多种功能
App Store中可下载(Wen应用套用原生应用的外壳)
可线下使用
页面存放于本地和服务器两种方式,部署应用程序(受限于UIwebview)
缺点:
不确定上线时间
虽然说你可以专注在界面以及交互开发上了,但是这页会成为一个缺点,比如说要仿造一个iOS的默认设置界面,就需要大量的html以及css代码了,而且效果不一定和iPhone上面的界面一样好
用户体验不如本地应用
性能稍慢(需要连接网络)
技术还不是很成熟(比如Facebook现在的应用属于混合应用它可以在许多App Store畅通无阻,但是掺杂了大量Web特性,所以它运行速度比较慢,而现在为了提高性能FB又决定采用原生应用)
(2)第二种方案:编译转换方式
优点:
利用自己熟悉的语言进行应用开发。
缺点:
严重依赖于其工具厂商提供的工具包,调试的时候就要有全套的工具。

怎样选择开发模式(视情况而定)

近年来随着移动设备类型的变多,操作系统的变多,用户需求的增加,对于每个项目启动前,大家都会考虑到的成本,团队成员,技术成熟度,时间,项目需求等一堆的因素。
因此,开发App的方案已经变得越来越多了。无数的人参与或者看到过一个讨论:原生开发还是混合开发,又或者是Web开发?要结实践和自身的情况。
1.比如,你的预算是多少?预算充足的话可以开发几个本地应用加一个Web应用
2.你的应用需要什么时候面市?Web应用可以很快地开发然后直接推出来
3.你的应用需要包含什么特点和功能?如果跟手机的某些功能深度整合了,比如摄像头,需要呈现大量图形和动画就选原生
应用好点
4.你的应用是否一定需要网络
5.你的应用的目标硬件设备是所有的移动设备还是仅仅只是一部分而已
6.你自己已经熟悉的开发语言,或者说现有资源
7.这个应用对于性能要求是否苛刻
等等。。。。要根据自己的业务需求,来选择合适的开发模式,一个应用中,不仅仅是单独的一种开发方式,也有可能是多种开发方式的组合

三 具体如何进行移动开发 ?

接下来我会针对于Android,与iOS两个平台,分别讲一下基本的开发打包流程,以及与后端交互流程,先来一张整体交互流程图如下:


前后端数据交互流程

数据封装过程
客户机发起一次请求的时候:客户机会将请求封装成http数据包-->封装成Tcp数据包-->封装成Ip数据包--->封装成数据帧--->硬件将帧数据转换成bit流(二进制数据)-->最后通过物理硬件(网卡芯片)发送到指定地点 服务器硬件首先收到bit流, 然后转换成ip数据包。于是通过ip协议解析Ip数据包,然后又发现里面是tcp数据包,就通过tcp协议解析Tcp数据包,接着发现是http数据包通过http协议再解析http数据包得到数据。

简介:app发起请求通过http协议到达后端,后端操作数据库,然后在原路返回结果,这是基本的前后端交互流程,接下来我们针对于每个环节来具体讲一下,主要内容有,后台搭建,iOS工程搭建及打包发布,Android工程搭建及打包发布

3.1 后台搭建

开发语言:python
开发工具:pycharm
数据库:mongoDB
web框架:flask
整体环境就是使用了这些东西,具体如何安装mongodb ,安装python环境,新建flask项目,这些网上有很多资料,具体我就不再赘述了,我们主要讲整体通信的流程,使用它就是给我们提供一些简单的接口,然我们了解整个前后端交互过程,当新建了一个flask项目后,我写了增删查改,四个接口,我们app也主要对接这四个接口。代码如下:

#coding=utf-8
#mongo.py
from flask import Flask,abort
from flask import jsonify
from flask import request


app = Flask(__name__)

import pymongo

client = pymongo.MongoClient(host='localhost', port=27017)
db = client.local

@app.route('/findUser', methods=['GET'])
def get_all_users():
  user_info = db.test.find()
  users = []
  for user in user_info:
      userInfo = {"uId": user['_id'], "sex": user['sex'], "aName": user['aName'], "age": user['age'], "address": user['address'], "phone": user['phone']}
      users.append(userInfo)

  return jsonify({'result': users})

@app.route('/addUser', methods=['POST'])
def add_user():
  _id = request.form['uId']
  sex = request.form['sex']
  aName = request.form['aName']
  age = request.form['age']
  address = request.form['address']
  phone = request.form['phone']


  user_id = db.test.insert({'_id': _id, 'sex': sex, 'aName': aName, 'age': age, 'address': address, 'phone': phone})
  return jsonify({'result': user_id})

@app.route('/modifyUser/', methods=['PUT'])
def update_user(uId):
    aName = request.form['aName']
    phone = request.form['phone']
    output = db.test.update({"_id": uId}, {'$set': {"aName": aName, "phone": phone}})
    return jsonify({'result': output['ok']})

@app.route('/deleteUser/', methods=['DELETE'])
def delete_user(uId):
    userInfo = db.test.find({"_id": uId})
    for user in userInfo:
        db.test.remove(user)

    return jsonify({'result': True})

if __name__ == '__main__':
    # app.run(host = '0.0.0.0', port = 80, debug = True)
    app.run()

项目截图

点击运行,我们的后台服务就起来了,访问地址是:http://127.0.0.1:5000/ ,到此我们后端服务就搭建起来了,下面再看一下我们的数据库设计,我安装了一个mongodb的桌面工具:Robot 3t,我在local数据库下,建了一个test表,主要建了一个用户信息的表,截图如下:
数据库表

字段信息

注释:数据库名称,表名称,跟写的接口文件里面的配置是对应的

client = pymongo.MongoClient(host='localhost', port=27017)
db = client.local //生成一个db实例
 user_info = db.test.find() //查询test表

至此我们后端环境算是搭建完了,我们提供了四个接口:findUser,addUser,modifyUser,deleteUser。
使用postman都已经全部测试通过:

postman接口请求测试

接下来我们开始写App,我们先写Android.

步骤1:新建Android工程
1 文件夹下面建一个androidDemo目录
2 打开Android studio
3 新建工程
4 配置项目工程,一直点Next直到配置完成
步骤2 :代码编写
我们写了一个简单的demo

下面我贴一下主要代码:
MainActivity.java

package com.xu.androiddemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Toast;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xu.androiddemo.adapter.RecyclerAdapter;
import com.xu.androiddemo.http.AsynNetUtils;
import com.xu.androiddemo.model.UserInfo;
import com.xu.androiddemo.model.UserInfoResult;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.edt_u_name)
    EditText edtUName;
    @BindView(R.id.edt_u_phone)
    EditText edtUPhone;
    @BindView(R.id.recyclerView)
    RecyclerView mRecyclerView;
    @BindView(R.id.btn_save)
    Button btnSave;

    RecyclerAdapter mAdapter;
    private List mlist = new ArrayList();
    private Boolean isEdit = false;
    private String uId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayout.VERTICAL,false));
        mAdapter = new RecyclerAdapter(mlist);
        mRecyclerView.setAdapter(mAdapter);
        mAdapter.setOnItemClickListener(new RecyclerAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                UserInfo info = mlist.get(position);
                edtUName.setText(info.getaName());
                edtUPhone.setText(info.getPhone());
                btnSave.setText("编辑");
                isEdit = true;
                uId = info.getuId();
            }
        });

        mAdapter.setmOnItemLongClickListener(new RecyclerAdapter.OnItemLongClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                UserInfo info = mlist.get(position);
                deleteUsers(info.getuId());
            }
        });
        findUsers();
    }

    /**
     * 增加用户
     */
    private void addUser() {
        Random ran = new Random();
        Map param = new HashMap();
        param.put("sex",ran.nextInt(1));
        param.put("aName",edtUName.getText().toString().trim());
        param.put("age",ran.nextInt(80)+18);
        param.put("address","上海市闵行区");
        param.put("phone",edtUPhone.getText().toString().trim());
        param.put("uId",ran.nextInt(999999)+1000000);
        AsynNetUtils.request("addUser", "POST", transMapToString(param), new AsynNetUtils.Callback() {
            @Override
            public void onResponse(String response) {
                if (response != null) {
                    findUsers();
                    Toast.makeText(MainActivity.this,"添加成功",Toast.LENGTH_SHORT);
                    Log.v("TAG",response);
                }
            }
        });
    }

    /**
     * 删除用户
     * @param uId
     */
    private void deleteUsers(String uId) {
        AsynNetUtils.request("deleteUser/"+uId, "DELETE", null, new AsynNetUtils.Callback() {
            @Override
            public void onResponse(String response) {

                if (response != null) {
                    findUsers();
                    Toast.makeText(MainActivity.this,"删除成功",Toast.LENGTH_LONG);
                    Log.v("TAG",response);
                }

            }
        });
    }

    /**
     * 查找用户
     */
    private void findUsers() {
        AsynNetUtils.request("findUser", "GET", null, new AsynNetUtils.Callback() {
            @Override
            public void onResponse(String response) {
                UserInfoResult result = JSONObject.parseObject(response, UserInfoResult.class);
                if (result != null) {
                    mlist.clear();
                    mlist.addAll(result.result);
                    mAdapter.notifyDataSetChanged();
                    Log.v("TAG",response);
                }

            }
        });
    }

    /**
     * 修改用户信息
     * @param uId
     */
    private void motifyUser(String uId) {
        Random ran = new Random();
        Map param = new HashMap();
        param.put("aName",edtUName.getText().toString().trim());
        param.put("phone",edtUPhone.getText().toString().trim());

        AsynNetUtils.request("modifyUser/"+uId, "PUT", transMapToString(param), new AsynNetUtils.Callback() {
            @Override
            public void onResponse(String response) {
                if (response != null) {
                    findUsers();
                    Toast.makeText(MainActivity.this,"修改成功",Toast.LENGTH_SHORT);
                    Log.v("TAG",response);
                    isEdit = false;
                    btnSave.setText("保存");
                    edtUName.setText("");
                    edtUPhone.setText("");
                }
            }
        });
    }

    @OnClick({R.id.btn_save})
    public void onViewClicked(View view) {
              if(isEdit) {
                  motifyUser(uId);
              } else {
                  addUser();
              }
    }

    public static String transMapToString(Map map){
        java.util.Map.Entry entry;
        StringBuffer sb = new StringBuffer();
        for(Iterator iterator = map.entrySet().iterator(); iterator.hasNext();)
        {
            entry = (java.util.Map.Entry)iterator.next();
            sb.append(entry.getKey().toString()+"=").append(null==entry.getValue()?"":
                    entry.getValue().toString()).append (iterator.hasNext() ? "&" : "");
        }
        return sb.toString();
    }
}

主页布局文件:activity_main.xml



    
        
        
    

    
        
        
    
    

列表适配器:RecyclerAdapter.java

package com.xu.androiddemo.adapter;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.xu.androiddemo.R;
import com.xu.androiddemo.model.UserInfo;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;

public class RecyclerAdapter extends RecyclerView.Adapter {

    private List list;
    private OnItemClickListener mOnItemClickListener;//声明接口
    private OnItemLongClickListener mOnItemLongClickListener;
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    public interface OnItemLongClickListener {
        void onItemClick(View view, int position);
    }

    public RecyclerAdapter(List list) {
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_main,parent,false);
        RecyclerAdapter.ViewHolder viewHolder = new  RecyclerAdapter.ViewHolder(view);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        UserInfo info = list.get(position);
        holder.name.setText(info.getaName());
        holder.age.setText(info.getAge()+"岁");
        holder.sex.setText(info.getSex() == 1 ? "男" : "女");
        holder.address.setText(info.getAddress());
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                mOnItemClickListener.onItemClick(holder.itemView, position);
            }
        });
        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                mOnItemLongClickListener.onItemClick(holder.itemView, position);
                return false;
            }
        });
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
        mOnItemClickListener = onItemClickListener;
    }

    public void setmOnItemLongClickListener(OnItemLongClickListener mOnItemLongClickListener) {
        this.mOnItemLongClickListener = mOnItemLongClickListener;
    }

    class ViewHolder extends  RecyclerView.ViewHolder {

        @BindView(R.id.name)
        TextView name;
        @BindView(R.id.age)
        TextView age;
        @BindView(R.id.sex)
        TextView sex;
        @BindView(R.id.address)
        TextView address;

        ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this,itemView);
        }

    }
}

模型类:UserInfo.java

package com.xu.androiddemo.model;

public class UserInfo {

   private String uId;
   private int age;
   private int sex;
   private String phone;
   private String address;
   private String aName;

    public String getuId() {
        return uId;
    }

    public void setuId(String uId) {
        this.uId = uId;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getaName() {
        return aName;
    }

    public void setaName(String aName) {
        this.aName = aName;
    }
}

网络访问工具类:NetUtils.java

package com.xu.androiddemo.http;

import android.accounts.NetworkErrorException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

public class NetUtils {
    public static String request(String url,String requestMethod, String content) {
        HttpURLConnection conn = null;
        try {
            // 创建一个URL对象
            URL mURL = new URL(url);
            // 调用URL的openConnection()方法,获取HttpURLConnection对象
            conn = (HttpURLConnection) mURL.openConnection();

            conn.setRequestMethod(requestMethod);// 设置请求方法为post
            conn.setReadTimeout(5000);// 设置读取超时为5秒
            conn.setConnectTimeout(10000);// 设置连接网络超时为10秒
            if(content != null && content.length() > 0) {
                conn.setDoOutput(true);// 设置此方法,允许向服务器输出内容
                // post请求的参数
                String data = content;
                // 获得一个输出流,向服务器写数据,默认情况下,系统不允许向服务器输出内容
                OutputStream out = conn.getOutputStream();// 获得一个输出流,向服务器写数据
                out.write(data.getBytes());
                out.flush();
                out.close();
            }

            int responseCode = conn.getResponseCode();// 调用此方法就不必再使用conn.connect()方法
            if (responseCode == 200) {

                InputStream is = conn.getInputStream();
                String response = getStringFromInputStream(is);
                return response;
            } else {
                throw new NetworkErrorException("response status is "+responseCode);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect();// 关闭连接
            }
        }

        return null;
    }

    private static String getStringFromInputStream(InputStream is)
            throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        // 模板代码 必须熟练
        byte[] buffer = new byte[1024];
        int len = -1;
        while ((len = is.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
        is.close();
        String state = os.toString();// 把流中的数据转换成字符串,采用的编码是utf-8(模拟器默认编码)
        os.close();
        return state;
    }
}

说明:我们主要书写了增删查改用户信息的操作,具体讲解下以下几点:
1 网络访问:NetUitls.java文件中我们可以看到,Android端读取和发送数据到后端,其实都是数据输入输出流的一些操作,前期根据一个URL先获取一个网络连接对象,然后设置一些请求方式,超时时间等,接着打开一个输入流,写入数据,再接着发起网络连接 int responseCode = conn.getResponseCode();// 调用此方法就不必再使用conn.connect()方法,最后获取相应结果,通过输出流对象读取相应数据。
2 数据解析:模型与json转换使用的是fastjson,模型字段,与后端返回数据字段是一一对应的,所以在移动端开发过程中,接口数据是要有后端和前端人员共同制定的,每种语言都有自己的风格,前后端用的语言不同,人员使用偏好上也会有些差别,有人喜欢用map来进行数据接收操作,有人喜欢用对象来接收数据,在可读性,可维护性上,对象好点,虽然会建立model类,写转换方法,会多操作几步。map接收处理数据很灵活,但是可读性太差,后期不易维护。

页面运行结果
步骤 3 打包流程
1 点击build

build->Generate Signed Apk


2 会弹出对话框,点击next

3 我们会进入到证书选择页面,如果我们之前生成过证书,直接点Choose existing,然后输入密码点击next,如果没有生成过点Create new...

4 根据字面意思进行填写,主要是:签名文件保存路径,密码,应用别名,组织,所在城市,国家代码,填写完成后点OK

5 跳到之前选证书的页面,这里已经自动帮我们填好了,然后点next

6 选择App存放路径,和签名版本,然后点finish

7 接着就会开始打包,完成后就会生成一个apk包,和一个签名文件

注意:签名文件要保存好,不要丢失了,以后每次这个应用打包,都要用到这个签名文件,一个应用如果使用不同签名文件,就无法进行覆盖升级,那样用户手机就会出现两个相同的应用程序。

到此Android的整个开发到打包流程我们就讲完了,下面讲iOS开发及发布流程。

步骤 1:新建iOS工程
1 创建一个iOSDemo目录

2 打开Xcode,点击新建一个xcode工程

3 选择single view app ,点击next,在这里可以看到xcode 给我们提供了很多工程模板

4 填写工程名称,选择开发语言,然后点next,保存项目到我们第一步建的目录
步骤2:代码编写
项目工程结构

主要代码:ViewController.swift

import UIKit
import Alamofire

class ViewController: UIViewController {

    @IBOutlet weak var texUserName: UITextField!
    @IBOutlet weak var texUserPhone: UITextField!
    @IBOutlet weak var tableview: UITableView!
    @IBOutlet weak var save: UIButton!
    var uId:String = ""
    fileprivate var dataList = [UserInfo]()
    var isUserEdit: Bool  = false
    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableview.delegate = self;
        self.tableview.dataSource = self;
        self.tableview.register(UINib.init(nibName: "UserTableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
        self.save.addTarget(self, action: #selector(self.save(_:)), for: UIControlEvents.touchUpInside)
        fetchUserInfo()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
   
    @objc func save(_ sender:UIResponder) {
        if isUserEdit {
            editUser()
        } else {
           addUser()
        }
    }
    
    func addUser() {
        let userName = texUserName.text
        let userPhone = texUserPhone.text
        var param  = [String:String]()
        param["sex"] = "1"
        param["aName"] = userName
        param["age"] =  "\(Int(arc4random_uniform(100))+1)"
        param["address"] = "上海市闵行区"
        param["phone"] = userPhone
        param["uId"] = "\(Int(arc4random_uniform(10000))+100)"
        handleRequest(urlString: "/addUser", requestMethod: HTTPMethod.post, parameter: param) { (response) in
           print("result:\(String(describing: response.data))")
           self.fetchUserInfo()
        }
    }
   
    func deleteUser(uId:String) {
        handleRequest(urlString: "/deleteUser/\(uId)", requestMethod: HTTPMethod.delete, parameter: nil) { (response) in
            print("result:\(String(describing: response.data))")
            self.fetchUserInfo()
        }
    }
   
    func fetchUserInfo() {
        handleRequest(urlString: "/findUser", requestMethod: HTTPMethod.get, parameter: nil) { (response) in
            print("result:\(String(describing: response.data))")
            let data = StringUtil.dataToDictionary(data: response.data!)
            let userinfo:NSArray = data!["result"] as! NSArray
            self.dataList = JsonUtil.jsonArrayToModel(StringUtil.dicValueString(userinfo)!, UserInfo.self) as! [UserInfo]
            self.tableview.reloadData()
        }
    }
    
    func editUser() {
        let userName = texUserName.text
        let userPhone = texUserPhone.text
        var param  = [String:String]()
        param["aName"] = userName
        param["phone"] = userPhone
        handleRequest(urlString: "/modifyUser/\(uId)", requestMethod: HTTPMethod.put, parameter: param) { (response) in
            print("result:\(String(describing: response.data))")
            self.isUserEdit = false
            self.texUserName.text = ""
            self.texUserPhone.text = ""
            self.save.setTitle("保存", for: UIControlState.normal)
            self.fetchUserInfo()
        }
    }
    
    func handleRequest(urlString:String,requestMethod:HTTPMethod,parameter:[String : String]?,completetion: @escaping (DataResponse)->())  {
        Alamofire.request("http://127.0.0.1:5000"+urlString, method:requestMethod, parameters: parameter)
            .responseJSON(completionHandler: { response in
                completetion(response)
                debugPrint(response)
            })
    }
}

extension ViewController:UITableViewDelegate,UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! UserTableViewCell
        if dataList.count > 0 {
            let model = dataList[indexPath.row]
            cell.setData(model: model)
            cell.cellDeleteHandler = {model in
                self.deleteUser(uId: model.uId)
            }
        }
        return cell;
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let userInfo = dataList[indexPath.row]
        texUserName.text = userInfo.aName
        texUserPhone.text = userInfo.phone
        uId = userInfo.uId
        isUserEdit = true
        save.setTitle("编辑", for: UIControlState.normal)
    }
}

模型类:Userinfo.swift

import UIKit

class UserInfo: BaseModel {

    var uId:String = ""
    var address:String = ""
    var aName:String = ""
    var phone:String = ""
    var age:Int = 0
    var sex:Int = 0
    
}
首页样式,在ios 开发中,搭建页面有三种方式,storyboard,xib,以及代码

使用的第三方库

说明:我们主要书写了增删查改用户信息的操作,具体讲解下以下几点:
1 网络访问:iOS网路操作框架NSURLConnection:ios9之前使用,之后弃用
NSURLSession ios7.0之后出来 ios9只能使用这个类,我使用的是Alamofire,AFNetworking的swift版本,基于NSURLSession进行的封装,
2 数据解析:HandyJSON,阿里的,类似fastjson的功能,模型转换的工具。关于这块其他要注意的内容,上面Android模块已经讲过了。

运行结果图
步骤 3 打包流程

1 要先准备好证书,关于如何生成证书,自行查阅。

点击product->archive ,注意目标设备选择要是Generic iOS Device

编译完成之后会弹出此页面,iOS有两种包模式,企业版,和个人版,个人版只能上传appstore,企业版只能打包自己发布,不能发布到appStore,我们以发布到appStore为例,点击Upload to appstore


选择描述文件证书

开始打包上传

上传成功后,登录itunsConnectm在活动页就能看到你构建的版本了

在此处添加新版本

新建一个版本号

选择构建版本
输入更新内容,点击提交审核,通过后就能在appStore看到你的发布的新版本了

至此打包流程完成。

hybirdapp开发

最后在讲一下hybirdapp的开发,大致流程跟原生开发差不多,就是使用的语言不一样,一般使用javascript,html,css,有纯js开发,也有用html,css开发的,
举个:react-native
开发工具:vscode
开发语言:javascript

项目结构:
项目结构

一般这种混合框架,都会生成两个原生框架,一个Android,一个iOS,打包发布的话,你可以打开它生成的原生项目进行打包,也可以使用官方提供的命令来打包。
调用接口:
import DeviceStorage from "./DeviceStorage";
import { USERINFO, TOKEN } from "./ConstantUtil";

 export default class NetRequest {
     /*
     *  post请求
     *  url:请求地址
     *  params:参数,这里的参数格式是:{param1: 'value1',param2: 'value2'}
     *  method:请求方法 post get pust update delete
     *  callback:回调函数
     * */

    static handleRequest(url,params,method,callback){
        //fetch请求
        console.log("url:"+url);
        console.log("params:"+JSON.stringify(params));

        var headers = {'Content-Type': 'application/json'}
        DeviceStorage.get(TOKEN).then((token)=>{
            
            if (token != null)
            {
                let tk = token.tk
                console.log(tk)
                headers['Authorization'] = 'Bearer '+tk
            }

            let requestInit = {method:method,headers:headers}
            if (method == 'POST'||method == 'PUT')
            {
                requestInit['body'] = JSON.stringify(params)
            }

            fetch(url,requestInit)
                .then((response) => response.json())
                
                .then((responseJSON) => {
                    console.log(responseJSON);
                    callback(responseJSON)
                })
                .catch((error) => {
                    console.log("error = " + error)
                });

        })
    }
}

使用fetch,基于Promise设计一个网络访问框架,常用的还有Ajax.
混合框架大致都是这个原理流程,见过一个特殊的CrossApp


C++开发的一个跨平台的移动开发框架,本人表示比学原生开发还难。
。。。。。。。
好了大致就讲这些吧。

总结:本文内容虽然很简单,但是很全面,主要是普及下移动开发的方方面面,平时开发中,后端人员知道他写的接口是给我们前端调用,但是我们前端怎么调用,如何获取的数据,他可能不清楚,还有前端人员,我们平时知道根据后台给的接口文档,进行接口对接,但是对于后台接口数据是怎么给我们的,后台是怎么操作逻辑的还是不清楚的,本文全面的讲解了移动开发的各个主要流程环节,万变不离其中,目前绝大部分移动端也好,web开发也好,基本流程都是这样的。

最后本文所有demo:https://gitee.com/mlxu/file.git

你可能感兴趣的:(浅谈移动端开发)