前言

搭建android项目框架前,我们需要先定义要框架的结构,因为android框架本身的结构就很复杂,如果一开始没定义好结构,那么后续的使用就会事倍功半。

结构如下:

com.kiba.framework

——activity 存储所有的活动

  ——base 存储baseActivity

——fragment存储所有的Fragment

  ——base 存储baseFragment

——service存储所有的service

——utils存储所有的工具类

——dto存储所有的传入传出实体

——model存储所有的实体类

——model_db存储所有的数据库实体类(框架使用ormlit)

创建项目

我们先创建一个项目,File—New—New Project,选择BasicActivity。

然后创建一个utils文件夹。

添加LogUtils,DateUtils,DecimalUtil文件,就是简单的日志输出,日期,字符串工具。(写法很多,可以上网任意搜索)。

然后创建一个异常捕获文件——CrashExceptionHandler,用于输入未捕获异常日志(写法很多,可以上网任意搜索)。

然后打开app下的gradle,引入我们常用的包。

网络请求:okhttp。

json处理:gson和fastjson。

黄油刀注解:ButterKnife。

内置数据库管理:ormlite。

权限请求:rxpermissions。

图片处理:glide。

代码如下:

    //okhttp
implementation "com.squareup.okhttp3:okhttp:4.9.0"
//gson
implementation 'com.google.code.gson:gson:2.8.6'
//fastjson
implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.83'
//解决超过65546代码的问题
implementation 'com.android.support:multidex:1.0.2'
implementation "com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4"
//ButterKnife
implementation 'com.jakewharton:butterknife:10.2.3'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
// 数据库ormlite
implementation 'com.j256.ormlite:ormlite-android:5.0'
implementation 'com.j256.ormlite:ormlite-core:5.0'
//权限请求rxpermissions
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
//图片处理glide
implementation 'com.github.bumptech.glide:glide:4.14.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'//运行时 编译时 处理注解

然后在添加一些json和http的utils(写法很多,可以上网任意搜索)。

然后创建MyApplication的java文件,代码如下:

public class MyApplication extends Application {

    public static Context context;//全局上下文
public static List<Activity> activityList = new ArrayList<Activity>();//用于存放所有启动的Activity的集合
public static ApplicationInfo applicationInfo;

@Override
public void onCreate() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Log.d("项目启动", "项目启动: " + DateUtils.getTime());
super.onCreate();

context = getApplicationContext();

PackageManager packageManager = getApplicationContext().getPackageManager();
try {
packageManager = getApplicationContext().getPackageManager();
applicationInfo = packageManager.getApplicationInfo(getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
applicationInfo = null;
LogUtils.LogHelperError("获取applicationInfo报错", e);
}

CrashExceptionHandler.getInstance().init(this);

//解决4.x运行崩溃的问题
MultiDex.install(this);

}

private boolean isDebug() {
return BuildConfig.DEBUG;
}
public static String GetProperties(String propertyName) {
Properties props = new Properties();
String serviceUrl = null;
try {
InputStream in =context.getAssets().open("appConfig.properties");
props.load(in);
String vaule = props.getProperty(propertyName);
serviceUrl = new String(vaule.getBytes("ISO-8859-1"), "gbk");
} catch (IOException e) {
e.printStackTrace();
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle("错误");
dialog.setMessage("读取配置文件失败");
dialog.setCancelable(false);
removeALLActivity();
}
return serviceUrl;
}
/**
* 销毁所有的Activity
*/
public static void removeALLActivity() {
//通过循环,把集合中的所有Activity销毁
for (Activity activity : activityList) {
if (!activity.isFinishing()) {
activity.finish();
}
}
MyApplication.activityList.clear();
}

}

然后注册CrashException和MultiDex。

然后找到AndroidManifest.xml,注册application,并开启大堆内存,如下:

HTTP请求

http请求是我们最常用的工具,下面我们编写一个简单的请求工具。

先创建一个文件夹dto,然后在创建一个base,一个user文件夹。

编写简单的请求和返回实体如下:

然后编写HttpUtils代码如下:

public class HttpUtils {
private static final OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
.hostnameVerifier(SSLSocketClient.getHostnameVerifier())
.connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
.readTimeout(20, TimeUnit.SECONDS)//设置读取超时时间
.build();


private static void getRequest(String url, ICallback callback) throws IOException {
new Thread() {
@Override
public void run() {
Request request = new Request.Builder()
.url(url)
.build();

try (Response response = client.newCall(request).execute()) {
String result = response.body().string();
callback.Call(result);
} catch (IOException e) {
e.printStackTrace();
Log.d("http异常", e.getMessage());
callback.Call(e.getMessage());

}
}
}.start();

}

private static final MediaType mediaType = MediaType.get("application/json; charset=utf-8");

private static void postRequest(String url, String param, ICallback callback) throws IOException {
new Thread() {
@Override
public void run() {

RequestBody body = RequestBody.create(mediaType, param);
Request request = new Request.Builder()
.url(url)
.post(body)
.build();

try (Response response = client.newCall(request).execute()) {
String result = response.body().string();
callback.Call(result);
} catch (IOException e) {
e.printStackTrace();
BaseResult baseResult=new BaseResult();
baseResult.code=-1;
callback.Call(JsonUtils.Serialize(baseResult));
}
}
}.start();
}

private interface ICallback {
void Call(String con);
}
public interface HttpData<T>{
public void getData(T result);
}
public static <T> void post(String param, String urlAddress, HttpData<T> httpData) {

try {
HttpUtils.postRequest(urlAddress, param, con -> {
runOnUiThread(() -> {

BaseResult baseResult = JsonUtils.Deserialize(BaseResult.class, con);
if (null != baseResult && baseResult.code == 1) {
Class thisClass = httpData.getClass();

Type[] superClassType = thisClass.getGenericInterfaces();
ParameterizedType pt = (ParameterizedType) superClassType[0];

Type[] genTypeArr = pt.getActualTypeArguments();
Type genType = genTypeArr[0];
Class c1= (Class) genTypeArr[0];

T result = (T)JsonUtils.Deserialize(c1, con);
httpData.getData(result);

} else {
if (null != baseResult) {
ToastUtils.showToast("数据获取失败:" + baseResult.msg);
} else {
ToastUtils.showToast("数据获取失败");
}
}
});
});
} catch (IOException e) {
e.printStackTrace();
}
}
BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(BaseResult.class, "con");
public static <T> void get(String param, String urlAddress, HttpData<T> httpData) {

try {

HttpUtils.getRequest(urlAddress, con -> {
runOnUiThread(() -> {
BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(Object.class, con);
if (null != baseResult && baseResult.code == 1) {
Class thisClass = httpData.getClass();

Type[] superClassType = thisClass.getGenericInterfaces();
ParameterizedType pt = (ParameterizedType) superClassType[0];

Type[] genTypeArr = pt.getActualTypeArguments();
Type genType = genTypeArr[0];
Class c1= (Class) genTypeArr[0];

T result = (T)JsonUtils.Deserialize(c1, con);
httpData.getData(result);

} else {
if (null != baseResult) {
ToastUtils.showToast("数据获取失败:" + baseResult.msg);
} else {
ToastUtils.showToast("数据获取失败");
}
}
});
});
} catch (IOException e) {
e.printStackTrace();
}
}
}

这里通过泛型反射直接找到了要序列化的类型,减少了调用时的代码编写,调用代码如下:

HttpUtils.get("url","参数", new HttpHelper.HttpData<LoginCommandResult>() {
@Override
public void getData(LoginCommandResult result) {
int code = result.code;
}
});

简单的输入参数和url后,就可以在匿名类的重写函数中获得返回值。

编写Activity与Fragment

应用的页面切换是以Fragment的替换为主,以尽量少创建Activity为中心思想,框架实现返回按钮切换fragment。

Activity于Fragment的编写思路如下:

首先编写Base文件,Base文件这里采取二级模式,BaseActivity加KActivity、BaseFragment加KFragment。

KBase文件实现生命周期,Base文件实现通用函数。

KActivity代码简介:

 /**
* @return 获取布局的id
*/
protected int getLayoutId() {
return -1;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int layoutId = getLayoutId();
if (layoutId != -1) {
this.rootView = View.inflate(this, layoutId, null);
setContentView(this.rootView);
} else {
throw new MissingResourceException("未使用getLayoutId()函数初始化view",this.getClass().getName(),"未初始化view");
}
if (savedInstanceState != null) {
loadActivitySavedData(savedInstanceState);
}
}

Base文件里将设置布局文件给提取出来了,并设置从Bundle里恢复数据的操作。

继承Base文件的Activity实现如下:

public class MainActivity extends BaseActivity {

@Override
protected int getLayoutId() {
return R.layout.activity_main;
}

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

}
/**
* 菜单、返回键响应
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
//moveTaskToBack(true);
}
return true;
}
}

继承Base文件的Fragment实现如下:

public class MainFragment extends BaseFragment {
@Override
protected int getLayoutId() {
return R.layout.fragment_main;
}

@Override
protected void onCreate() { }
}

可以看到,在类里,使用getLayoutId来指定布局XML文件,这样即可清晰的知道布局文件名,又便于阅读。

PS:Android是支持多个Activity或Fragment使用同一个XML的,但本框架中,拒绝这个特性,要求布局文件与类文件是一对一的关系。

gradle配置

app.gradle

打开app的gradle,首先在defaultConfig下增加指定cpu。

 ndk {
abiFilters 'armeabi', 'armeabi-v7a', 'x86'
}

然后在android下面关闭lint检测。

//不在googlePlay上线,关闭lint检测
lintOptions {
checkReleaseBuilds false
abortOnError false
}

project.gradle

我用的新版本AS建的项目,所以默认的代码是这样的。

plugins {
id 'com.android.application' version '7.3.1' apply false
id 'com.android.library' version '7.3.1' apply false
}

这里我们直接将生成的配置删除,粘贴上我们比较熟悉的gradle配置模式,代码如下:

buildscript {
repositories {
maven { url 'https://jitpack.io' }
maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon

}
dependencies {

classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
classpath 'com.android.tools.build:gradle:7.1.2'

}
}

allprojects {
repositories {
maven { url 'https://jitpack.io' }
maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon

}
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
task clean(type: Delete) {
delete rootProject.buildDir
}

要注意的是,新版的settings.gradle也变化了,如果只修改build.gradle编译会抛异常。

生成的setting.gradle代码如下:

pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "framework"
include ':app'

修改代码如下:

rootProject.name = "framework"
include ':app'

首页布局

结构搭建好后,我们使用LinkageRecyclerView组件,实现一个简单的双列表布局,界面如下:

结语

最后我们看一下项目结构,如下图:

如上图,一个简单的,有序的,支持activity恢复数据,支持fragment返回的框架就搭建完成了。

----------------------------------------------------------------------------------------------------

到此,手写Android框架一就已经介绍完了。

代码已经传到Github上了,欢迎大家下载。

下篇文章介绍AspectJX实现AOP的几个实用注解。

Github地址:https://github.com/kiba518/AndroidFramework2.0/

----------------------------------------------------------------------------------------------------

注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!

https://www.cnblogs.com/kiba/p/17262561.html

从头开始,手写android应用框架(一)的更多相关文章

  1. 手写Spring事务框架

    Spring事务基于AOP环绕通知和异常通知 编程事务 声明事务 Spring事务底层使用编程事务+AOP进行包装的   = 声明事务 AOP应用场景:  事务 权限 参数验证 什么是AOP技术 AO ...

  2. 剖析手写Vue,你也可以手写一个MVVM框架

    剖析手写Vue,你也可以手写一个MVVM框架# 邮箱:563995050@qq.com github: https://github.com/xiaoqiuxiong 作者:肖秋雄(eddy) 温馨提 ...

  3. 手写spring事务框架-蚂蚁课堂

    1.视频参加C:\Users\Administrator\Desktop\蚂蚁3期\[www.zxit8.com] 0017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Sp ...

  4. 手写开源ORM框架介绍

    手写开源ORM框架介绍 简介 前段时间利用空闲时间,参照mybatis的基本思路手写了一个ORM框架.一直没有时间去补充相应的文档,现在正好抽时间去整理下.通过思路历程和代码注释,一方面重温下知识,另 ...

  5. 闭关修炼180天--手写持久层框架(mybatis简易版)

    闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...

  6. 自己手写一个SpringMVC 框架

    一.了解SpringMVC运行流程及九大组件 1.SpringMVC 的运行流程   · 用户发送请求至前端控制器DispatcherServlet · DispatcherServlet收到请求调用 ...

  7. 自己动手写Android数据库框架

    前言 相信不少开发人员跟我一样,每次都非常烦恼自己写数据库,并且那些数据库语句也经常记不住.当然网上也有非常多非常好的数据库框架,你能够直接拿来用,可是 非常多时候我们的项目.特别是一个小型的Andr ...

  8. 手写迷你SpringMVC框架

    前言 学习如何使用Spring,SpringMVC是很快的,但是在往后使用的过程中难免会想探究一下框架背后的原理是什么,本文将通过讲解如何手写一个简单版的springMVC框架,直接从代码上看框架中请 ...

  9. 看年薪50W的架构师如何手写一个SpringMVC框架

    前言 做 Java Web 开发的你,一定听说过SpringMVC的大名,作为现在运用最广泛的Java框架,它到目前为止依然保持着强大的活力和广泛的用户群. 本文介绍如何用eclipse一步一步搭建S ...

  10. 手写MyBatis ORM框架实践

    一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...

随机推荐

  1. kubectl工具安装指南

    kubectl是一个用于连接Service Mesh控制平面的工具,可以安装在办公电脑的Windows系统上,也可以安装在虚拟机的Linux系统上,只要网络能与控制平面的公网地址互通即可.下面分别介绍 ...

  2. 等级保护2.0 三级-Linux 测评指导书

    等级保护2.0 三级-Linux 测评指导书 1.1安全计算环境 1.1.1身份鉴别        1.1.2访问控制        1.1.4入侵防范       1.1.5恶意代码防范      ...

  3. 高并发解决方案之 redis 分布式锁

    背景:秒杀服务中要写一个定时任务:活动到期时给order微服务发送关闭订单的通知.这需要改变数据库表中的数据,而集群中服务是多节点的方式进行部署,会出现并发执行的情况,所以采用的redis的分布式锁的 ...

  4. ReSharp的安装和使用教程

    1.ReSharp的安装及破解: (1)不多说,直接上下载链接: 链接:https://pan.baidu.com/s/1cJmTQxDS-OHmHs46Q_hbMg 提取码:shiz (2)下载好解 ...

  5. 使用TLS/SSL传输层安全机制实现web项目的通信安全

    自己的web项目在内网ip访问时,浏览器会提示不安全 原因就是因为没有证书,而传输层的TLS/SSL协议,会告诉我们本地客户端的浏览器,我现在访问的web项目的ip地址可能存在安全风险 要解决这个通信 ...

  6. linux sync命令

    Linux sync命令用于数据同步,sync命令是在关闭Linux系统时使用的. Linux 系统中欲写入硬盘的资料有的时候为了效率起见,会写到 filesystem buffer 中,这个 buf ...

  7. K8SPod进阶资源限制以及探针

    一.Pod 进阶 1.资源限制 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量. 最常见的可设定资源是 CPU 和内存大小,以及其他类型的资源. 当为 Pod 中的容器指定了 reque ...

  8. win10启动和安装nacos服务

    https://blog.csdn.net/tbmingzhao/article/details/113276845

  9. 第二次python作业

    #3.1 print("今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问几何?\n") number = int(input("请输入你认为符合条件的数: & ...

  10. .Net Core 前后端分离 DevOps 系列

    前言: 目录: Linux 安装脚本之乌班图版本 Linux Nginx部署 DevOps安装步骤