• Android应用程序MVC框架实例分析


    问题提出:如何优雅地分离出应用程序的状态、用户交互和数据表现?如何通过框架体现工程的高性能、高灵活性、高响应性?

    MVC定义:model、view、controller三者的有机组合,分别表示:模型、视图和控制。

    这个模式认为:程序不论简单还是复杂,从结构上来看,都可以分为三个层次。

    下图展示了MVC程序框架在Android应用程序中的使用,以及各个层次使用哪些组件担当:

    1)最上面一层,是直接面向于最终用户的“视图层”(View)。它是提供给用户的操作界面,是程序的外壳。界面就是各种UI组件(XML布局或者Java自定义控件对象)。只负责展示数据,同时接收控制器传过来的结果。

    2)最底下的一层,是核心的“数据层”(Model),也就是程序需要操作的数据或信息(系统中的业务逻辑部分)。通常是数据库SQLite、网络请求的JSON、本地XML或者Java对象数据。它代表了一些实体类,用来描述业务逻辑怎么组合,同时也为数据定义业务规则;

    3)中间的一层,就是“控制层”(controller),负责根据用户从“视图层”输入的指令,选取“数据层”中的数据,然后对其进行相应的操作,产生最终的结果(可以分派用户的请求并选择恰当的视图以用于显示,同时也可以解释用户的数据并将它们映射为模型层可执行的操作)。控制器是与应用程序相关联的动作集合,负责处理待响应的请求。通过界面响应用户输入,通过模型层处理数据,最后返回结果给界面。控制器扮演着模型和界面的粘合剂角色。

    抽象一点,上述模型可以抽象为下述结果:

    展示了从Activity接收用于点击输入,控制器响应用户输入并发起Internet请求数据(网络请求),响应结果经过模型层转换,最后控制器取到模型层数据并通知界面进行刷新。

    更加简化的MVC模型如下:

    或者是下述结果:

    在Android中,View和Model也是有关联的,从而抽象为下述图:

    上述的三个层次是紧密联系,且是相互独立的,每一层的变化不影响其他层次。每一层都对外提供接口,供上面一层调用。软件因而实现模块化,修改外观或者变更数据都不用修改其他层次,大大方便了维护和升级。

    一个逻辑模型可以对于多种视图模型,比如一批统计数据可以分别用柱状图、饼状图来显示结果;一种视图模型也可以对应多种逻辑模型。使用MVC的目的就是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式,而Controller存在的目的则是确保M改变,V应该同步更新。

    使用计算器这个例子作为实例进行分析:外部的按钮和上面的显示条,就是“视图层”;需要运算的数字就是“数据层”;执行加减乘除的内部运算步骤就是“控制层”。每一层执行不同的功能,整个程序的结构很清晰。

    MVC的好处在于:从用户的角度出发,用户可以根据自己的需求,选择自己合适浏览数据的方式。比如说,对于一篇在线文档,用户可以选择以HTML网页的方式阅读,也可以选择以pdf的方式阅读。从开发者的角度来看,MVC把应用程序的逻辑层与界面完全分开。

    Android应用程序中,MVC框架是如何实现的?都充当什么角色?

    1. View接受用户的交互请求;

    2. View将请求转交给Controller;

    3. Controller(用户做的动作比如:update数据,删除指定名字的学生等等)操作Model进行数据更新(根据用户指示,执行底层的数据动作等等);

    4. 数据更新之后,Model通知View数据变化;

    5. View显示更新之后的数据;

    M层适合做一些业务逻辑处理,比如数据库存取操作、网络操作、复杂的算法等耗时操作;

    V层显示数据部分,XML布局可以视为是V层,显示Model层的数据结果;

    C层适合使用Activity担当,Android中Activity用于处理用户交互问题(发起业务请求),读取用户输入(等待业务处理结果),响应用户点击等等事件。

    MainActivity.java代码如下:

    package com.demo.controller;
    	
    	import static com.demo.controller.ControllerProtocol.C_DATA;
    	import static com.demo.controller.ControllerProtocol.C_QUIT;
    	import static com.demo.controller.ControllerProtocol.C_UPDATE_FINISHED;
    	import static com.demo.controller.ControllerProtocol.C_UPDATE_STARTED;
    	import static com.demo.controller.ControllerProtocol.V_REQUEST_DATA;
    	import static com.demo.controller.ControllerProtocol.V_REQUEST_QUIT;
    	import static com.demo.controller.ControllerProtocol.V_REQUEST_UPDATE;
    	
    	import android.app.Activity;
    	import android.os.Bundle;
    	import android.os.Handler;
    	import android.os.Handler.Callback;
    	import android.os.Message;
    	import android.util.Log;
    	import android.view.View;
    	import android.widget.ProgressBar;
    	import android.widget.TextView;
    	
    	import com.demo.R;
    	import com.demo.model.Model;
    	import com.demo.model.ModelData;
    	import com.demo.utils.LogUtil;
    	
    	public class MvcActivity extends Activity implements Callback,
    	        View.OnClickListener {
    	    private static final String TAG = MvcActivity.class.getSimpleName();
    	
    	    private Controller controller;
    	
    	    @Override
    	    public void onCreate(Bundle savedInstanceState) {
    	        super.onCreate(savedInstanceState);
    	        setContentView(R.layout.main);
    	
    	        findViewById(R.id.update).setOnClickListener(this);
    	        findViewById(R.id.quit).setOnClickListener(this);
    	
    	        // 初始化层次结构中的交互
    	        controller = new Controller(new Model());
    	        // 使用MvcActivity所在的主线程初始化outBox中的Handler
    	        controller.addOutboxHandler(new Handler(MvcActivity.this));
    	        // Controller层发送请求数据的Message
    	        controller.getInboxHandler().sendEmptyMessage(V_REQUEST_DATA);
    	    }
    	
    	    @Override
    	    public void onClick(View v) {
    	        // 接收用户输入
    	        switch (v.getId()) {
    	            case R.id.update:
    	                LogUtil.d(TAG, "User click input: R.id.update");
    	                // 接收用户输入并得到Controller层响应,Controller层发送请求数据的Message
    	                controller.getInboxHandler().sendEmptyMessage(V_REQUEST_UPDATE);
    	                break;
    	            case R.id.quit:
    	                LogUtil.d(TAG, "User click input: R.id.quit");
    	                // 接收用户输入并得到Controller层响应,Controller层发送请求数据的Message
    	                controller.getInboxHandler().sendEmptyMessage(V_REQUEST_QUIT);
    	                break;
    	        }
    	    }
    	
    	    @Override
    	    public boolean handleMessage(Message msg) {
    	        Log.d(TAG, "Received message: " + msg);
    	
    	        switch (msg.what) {
    	            case C_QUIT:
    	                // 接收Controller层输入,准备退出应用
    	                onQuit();
    	                return true;
    	            case C_DATA:
    	                // 将数据显示到View层
    	                onData((ModelData) msg.obj);
    	                return true;
    	            case C_UPDATE_STARTED:
    	                // (状态)正在后台请求数据
    	                onUpdateStarted();
    	                return true;
    	            case C_UPDATE_FINISHED:
    	                // (状态)后台请求数据结束,准备显示
    	                onUpdateFinished();
    	                return true;
    	        }
    	        return false;
    	    }
    	
    	    @Override
    	    protected void onDestroy() {
    	        try {
    	            controller.dispose();
    	        } catch (Throwable t) {
    	            LogUtil.d(TAG, "Failed to destroy the controller");
    	        }
    	
    	        super.onDestroy();
    	    }
    	
    	    /**
    	     * <功能描述> Controller层做出update响应,更新View层
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void onQuit() {
    	        LogUtil.d(TAG, "Activity quitting");
    	        finish();
    	    }
    	
    	    /**
    	     * <功能描述> 从Controller层传递过来的Message,表明获取到返回的数据,更新View
    	     * 
    	     * @param data [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    private void onData(ModelData data) {
    	        LogUtil.d(TAG, "onData::running...");
    	        TextView dataView = (TextView) findViewById(R.id.data_view);
    	        dataView.setText("The answer is " + data.getAnswer());
    	    }
    	
    	    /**
    	     * <功能描述> 开始Update数据显示ProgressBar
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void onUpdateStarted() {
    	        LogUtil.d(TAG, "onUpdateStarted::running...");
    	        ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
    	        progressBar.setVisibility(View.VISIBLE);
    	    }
    	
    	    /**
    	     * <功能描述> Update数据过程结束后,ProgressBar消失,显示数据
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void onUpdateFinished() {
    	        LogUtil.d(TAG, "onUpdateFinished::running...");
    	        ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar);
    	        progressBar.setVisibility(View.GONE);
    	        controller.getInboxHandler().sendEmptyMessage(V_REQUEST_DATA);
    	    }
    	
    	}

    Controller.java代码如下:

    package com.demo.controller;
    	
    	import static com.demo.controller.ControllerProtocol.C_QUIT;
    	
    	import android.os.Handler;
    	import android.os.HandlerThread;
    	import android.os.Message;
    	
    	import com.demo.model.Model;
    	import com.demo.utils.LogUtil;
    	
    	import java.util.ArrayList;
    	import java.util.List;
    	
    	public class Controller {
    	    private static final String TAG = Controller.class.getSimpleName();
    	
    	    private final Model model;
    	
    	    // 使用HandlerThread、Handler、Message机制实现不同层的消息通信
    	    // inBox:用于接收从View层发送的Message
    	    private final HandlerThread inboxHandlerThread;
    	    private final Handler inboxHandler;
    	    // outBox:用于Controller给View发送Message
    	    private final List<Handler> outboxHandlers = new ArrayList<Handler>();
    	
    	    // 将消息处理委托给ControllerState,不在Controller中处理Message
    	    private ControllerState state;
    	
    	    /**
    	     * <默认构造函数> Controller层初始化
    	     */
    	    public Controller(Model model) {
    	        this.model = model;
    	
    	        // Handler就存在于该Thread子线程(也就是工作线程)中
    	        inboxHandlerThread = new HandlerThread("Controller Inbox");
    	        inboxHandlerThread.start();
    	        // HandlerThread的getLooper()获取到Handler实例
    	        inboxHandler = new Handler(inboxHandlerThread.getLooper()) {
    	
    	            @Override
    	            public void handleMessage(Message msg) {
    	                Controller.this.handleMessage(msg);
    	            }
    	        };
    	
    	        // 初始化ControllerState,准备接受Message
    	        this.state = new ReadyState(this);
    	    }
    	
    	    // 处理从View层传递过来的Message
    	    private void handleMessage(Message msg) {
    	        LogUtil.d(TAG, "handleMessage::Received message...msg.what=" + msg.what);
    	
    	        // 把消息处理委托给它的 ControllerState
    	        if (!state.handleMessage(msg)) {
    	            LogUtil.d(TAG, "Unknown message: " + msg);
    	        }
    	    }
    	
    	    /**
    	     * <功能描述> inboxHandlerThread退出
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    public final void dispose() {
    	        inboxHandlerThread.getLooper().quit();
    	    }
    	
    	    /**
    	     * <功能描述>获取inBox的Handler实例,并返回该实例
    	     * 
    	     * @return [参数说明]
    	     * @return Handler [返回类型说明]
    	     */
    	    public final Handler getInboxHandler() {
    	        return inboxHandler;
    	    }
    	
    	    /**
    	     * <功能描述> 增加Handler到outBox中;outBox用于Controller传送Message到View层
    	     * 
    	     * @param handler [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    public final void addOutboxHandler(Handler handler) {
    	        outboxHandlers.add(handler);
    	    }
    	
    	    /**
    	     * <功能描述> 从outBox移除Handler
    	     * 
    	     * @param handler [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    public final void removeOutboxHandler(Handler handler) {
    	        outboxHandlers.remove(handler);
    	    }
    	
    	    /**
    	     * <功能描述> 更新Controller的状态,ReadyState和UpdatingState之间的切换
    	     * 
    	     * @param newState [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    final void changeState(ControllerState newState) {
    	        LogUtil.d(TAG,
    	                String.format("Changing state from %s to %s", state, newState));
    	        state = newState;
    	    }
    	
    	    /**
    	     * <功能描述> Controller层向View层发送Message
    	     * 
    	     * @param what
    	     * @param arg1
    	     * @param arg2
    	     * @param obj [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    final void notifyOutboxHandlers(int what, int arg1, int arg2, Object obj) {
    	        LogUtil.d(TAG, "notifyOutboxHandlers::running...what=" + what);
    	
    	        if (outboxHandlers.isEmpty()) {
    	            LogUtil.d(TAG, String.format(
    	                    "No outbox handler to handle outgoing message (%d)", what));
    	        } else {
    	            for (Handler handler : outboxHandlers) {
    	                Message msg = Message.obtain(handler, what, arg1, arg2, obj);
    	                msg.sendToTarget();
    	            }
    	        }
    	    }
    	
    	    /**
    	     * <功能描述> 获取到Model层
    	     * 
    	     * @return [参数说明]
    	     * @return Model [返回类型说明]
    	     */
    	    final Model getModel() {
    	        return model;
    	    }
    	
    	    /**
    	     * <功能描述> 接收用户输入,准备退出应用
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    final void quit() {
    	        notifyOutboxHandlers(C_QUIT, 0, 0, null);
    	    }
    	}

    ControllerState.java代码如下:

    	package com.demo.controller;
    	
    	import android.os.Message;
    	
    	/**
    	 * <功能描述> 将Controller层的消息处理逻辑转交给ControllerState处理;ControllerState有两个实现类:
    	 * ReadyState和UpdatingState
    	 * 
    	 * @author Administrator
    	 */
    	public interface ControllerState {
    	    boolean handleMessage(Message msg);
    	}

    ReadyState.java代码如下:

    package com.demo.controller;
    	
    	import android.os.Message;
    	
    	import com.demo.utils.LogUtil;
    	
    	import static com.demo.controller.ControllerProtocol.*;
    	
    	/**
    	 * <功能描述> 数据更新的状态之一:准备阶段,最前面的状态
    	 * 
    	 * @author Administrator
    	 */
    	final class ReadyState implements ControllerState {
    	    private static final String TAG = ReadyState.class.getSimpleName();
    	
    	    private final Controller controller;
    	
    	    public ReadyState(Controller controller) {
    	        this.controller = controller;
    	    }
    	
    	    @Override
    	    public final boolean handleMessage(Message msg) {
    	        LogUtil.d(TAG, "handleMessage::running...msg.what=" + msg.what);
    	
    	        switch (msg.what) {
    	            case V_REQUEST_QUIT:
    	                // 从View层获取到的Quite消息
    	                onRequestQuit();
    	                return true;
    	            case V_REQUEST_UPDATE:
    	                // 从View层获取到的Update的消息
    	                onRequestUpdate();
    	                return true;
    	            case V_REQUEST_DATA:
    	                // 从View层获取到的更新数据的消息
    	                onRequestData();
    	                return true;
    	        }
    	        return false;
    	    }
    	
    	    /**
    	     * <功能描述> 请求数据
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void onRequestData() {
    	        LogUtil.d(TAG, "onRequestData::running...");
    	        controller.notifyOutboxHandlers(C_DATA, 0, 0, controller.getModel()
    	                .getData());
    	    }
    	
    	    /**
    	     * <功能描述> 请求更新数据,并更改Controller的状态
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void onRequestUpdate() {
    	        LogUtil.d(TAG, "onRequestUpdate::running...");
    	        // 状态返回给Controller层,并将状态改变为UpdatingState
    	        controller.changeState(new UpdatingState(controller));
    	    }
    	
    	    private void onRequestQuit() {
    	        LogUtil.d(TAG, "onRequestQuit::running...");
    	        controller.quit();
    	    }
    	}

    UpdatingState.java代码如下:

    package com.demo.controller;
    	
    	import static com.demo.controller.ControllerProtocol.C_UPDATE_FINISHED;
    	import static com.demo.controller.ControllerProtocol.C_UPDATE_STARTED;
    	import static com.demo.controller.ControllerProtocol.V_REQUEST_QUIT;
    	
    	import android.os.Message;
    	
    	import com.demo.utils.LogUtil;
    	
    	/**
    	 * <功能描述> 数据更新的状态之一:正在更新阶段
    	 * 
    	 * @author Administrator
    	 */
    	final class UpdatingState implements ControllerState {
    	    private static final String TAG = UpdatingState.class.getSimpleName();
    	
    	    private final Controller controller;
    	    private final Thread updateThread;
    	
    	    public UpdatingState(Controller controller) {
    	        this.controller = controller;
    	
    	        // 新建一个Thread工作线程
    	        updateThread = new Thread("Model Update") {
    	
    	            @Override
    	            public void run() {
    	                Controller controller = UpdatingState.this.controller;
    	                try {
    	                    // 在工作线程中执行数据请求(抽象为相对耗时的操作),可为其他:数据库操作
    	                    controller.getModel().updateData();
    	                } catch (Throwable t) {
    	                    LogUtil.d(TAG, "Error in the update thread");
    	                } finally {
    	                    // controller.getModel().updateData()执行完后,反馈数据给Controller层
    	                    notifyControllerOfCompletion();
    	                }
    	            }
    	        };
    	        updateThread.start();
    	        // Controller层向View层发送Message,开始更新数据
    	        controller.notifyOutboxHandlers(C_UPDATE_STARTED, 0, 0, null);
    	    }
    	
    	    @Override
    	    public boolean handleMessage(Message msg) {
    	        LogUtil.d(TAG, "handleMessage::msg.what=" + msg.what);
    	
    	        switch (msg.what) {
    	            case V_REQUEST_QUIT:
    	                onRequestQuit();
    	                return true;
    	        }
    	        return false;
    	    }
    	
    	    /**
    	     * <功能描述> 数据获取到后,最终会反馈给Controller层
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    private void notifyControllerOfCompletion() {
    	        LogUtil.d(TAG, "notifyControllerOfCompletion::starting...");
    	
    	        controller.getInboxHandler().post(new Runnable() {
    	
    	            @Override
    	            public void run() {
    	                // 将状态改变为ReadyState
    	                controller.changeState(new ReadyState(controller));
    	                // 并通知结束更新
    	                controller.notifyOutboxHandlers(C_UPDATE_FINISHED, 0, 0, null);
    	            }
    	        });
    	    }
    	
    	    private void onRequestQuit() {
    	        LogUtil.d(TAG, "onRequestQuit::starting...");
    	
    	        updateThread.interrupt();
    	        controller.quit();
    	    }
    	}
    

    ControllerProtocal.java代码如下:

    package com.demo.controller;
    	
    	public interface ControllerProtocol {
    	    // 从View层传递的Message
    	    int V_REQUEST_QUIT = 101;
    	    int V_REQUEST_UPDATE = 102;
    	    int V_REQUEST_DATA = 103;
    	
    	    // 从Controller层传递的Message
    	    int C_QUIT = 201;
    	    int C_UPDATE_STARTED = 202;
    	    int C_UPDATE_FINISHED = 203;
    	    int C_DATA = 204; // obj = (ModelData) data
    	}

    Model.java代码如下:

    package com.demo.model;
    	
    	import java.util.ArrayList;
    	import java.util.List;
    	import java.util.Random;
    	
    	import android.os.SystemClock;
    	
    	import com.demo.utils.LogUtil;
    	
    	/**
    	 * <功能描述> 专指Model层;使用ThreadSafe指示是线程安全的
    	 * 
    	 * @author Administrator
    	 */
    	public class Model {
    	    private static final String TAG = Model.class.getSimpleName();
    	
    	    public interface Listener {
    	        void onModelStateUpdated(Model model);
    	    }
    	
    	    // Model模型中包含有ModelData
    	    private ModelData data = new ModelData(0);
    	
    	    private final List<Listener> listeners = new ArrayList<Listener>();
    	
    	    public Model() {
    	        LogUtil.d(TAG, "Model constructer..");
    	    }
    	
    	    public final ModelData getData() {
    	        synchronized (this) {
    	            return data;
    	        }
    	    }
    	
    	    /**
    	     * <功能描述> 更新数据
    	     * 
    	     * @return void [返回类型说明]
    	     */
    	    public final void updateData() {
    	        // 可能是请求服务器数据,执行繁重的计算
    	        SystemClock.sleep(3000);
    	        ModelData newData = new ModelData(new Random().nextInt(10) + 1);
    	
    	        synchronized (this) {
    	            data = newData;
    	        }
    	
    	        synchronized (listeners) {
    	            for (Listener listener : listeners) {
    	                listener.onModelStateUpdated(this);
    	            }
    	        }
    	    }
    	
    	    /**
    	     * <功能描述>增加监听器
    	     * 
    	     * @param listener [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    public final void addListener(Listener listener) {
    	        synchronized (listeners) {
    	            listeners.add(listener);
    	        }
    	    }
    	
    	    /**
    	     * <功能描述>移除监听器
    	     * 
    	     * @param listener [参数说明]
    	     * @return void [返回类型说明]
    	     */
    	    public final void removeListener(Listener listener) {
    	        synchronized (listeners) {
    	            listeners.remove(listener);
    	        }
    	    }
    	}

    ModelData.java代码如下:

    package com.demo.model;
    	
    	import com.demo.utils.LogUtil;
    	
    	import java.io.Serializable;
    	
    	/**
    	 * <功能描述> Model层中的数据,指示数据层的状态;Immutable指示是不变的
    	 * 
    	 * @author Administrator
    	 */
    	public final class ModelData implements Serializable {
    	    private static final String TAG = ModelData.class.getSimpleName();
    	    private static final long serialVersionUID = 1L;
    	
    	    private final int answer;
    	
    	    public ModelData(int answer) {
    	        LogUtil.d(TAG, "ModelData::answer=" + answer);
    	        this.answer = answer;
    	    }
    	
    	    public final int getAnswer() {
    	        LogUtil.d(TAG, "getAnswer::answer=" + answer);
    	
    	        return answer;
    	    }
    	}

    View代码如下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    	    xmlns:tools="http://schemas.android.com/tools"
    	    android:layout_width="match_parent"
    	    android:layout_height="match_parent"
    	    android:orientation="vertical"
    	    tools:context="com.demo.controller.MvcActivity" >
    	
    	    <TextView
    	        android:id="@+id/data_view"
    	        style="@android:style/TextAppearance.Large"
    	        android:layout_width="fill_parent"
    	        android:layout_height="wrap_content"
    	        android:layout_margin="20dp"
    	        android:gravity="center"
    	        android:text="" />
    	
    	    <LinearLayout
    	        android:layout_width="fill_parent"
    	        android:layout_height="wrap_content"
    	        android:orientation="horizontal"
    	        android:weightSum="2.0" >
    	
    	        <Button
    	            android:id="@+id/update"
    	            android:layout_width="0px"
    	            android:layout_height="wrap_content"
    	            android:layout_weight="1.0"
    	            android:text="Update" />
    	
    	        <Button
    	            android:id="@+id/quit"
    	            android:layout_width="0px"
    	            android:layout_height="wrap_content"
    	            android:layout_weight="1.0"
    	            android:text="Quit" />
    	    </LinearLayout>
    	
    	    <ProgressBar
    	        android:id="@+id/progress_bar"
    	        android:layout_width="match_parent"
    	        android:layout_height="wrap_content"
    	        android:layout_gravity="center_horizontal"
    	        android:indeterminate="true"
    	        android:visibility="gone" />
    	
    	</LinearLayout>

    更加详细的MPV模型解析:

  • 相关阅读:
    PHP探针
    服务器fsockopen函数和pfsockopen函数开启及作用
    WP SMTP插件为啥我一直设置的不对?
    用WP SMTP插件实现邮件发送功能
    openssl基本原理 + 生成证书 + 使用实例
    解决wordpress无法发送邮件的问题|配置好WP-Mail-SMTP的前提
    使用 openssl 生成证书
    如何使用OpenSSL工具生成根证书与应用证书
    OpenSSL 给自己颁发根证书,由根证书签发下级证书的步骤。
    ARMCC和GCC编译ARM代码的软浮点和硬浮点问题【转】
  • 原文地址:https://www.cnblogs.com/CVstyle/p/6389990.html
Copyright © 2020-2023  润新知