前言

搭建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. pytest之conftest.py

    一.conftest.py的特点 1.可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture 2.conftest.py与运行的用 ...

  2. 微服务注册到Nacos上的Ip错误,是内网ip不是公网ip

    spring.cloud.nacos.discovery.ip = 本机公网IP spring.cloud.nacos.discovery.port = 服务端口

  3. iOS学习十一之步进控制器UIStepper

    步进控制器也就是进行离散式数据调节的常用视图控件. 在viewDidLoad()方法中加入下面的方法,即可完成基本功能. override func viewDidLoad() { super.vie ...

  4. Compose Modifier Clip 圆角

    Row( modifier = Modifier .fillMaxWidth() .padding(20.dp) // 圆角 .clip(RoundedCornerShape(15.dp)) .cli ...

  5. MySQL8.0使用GROUP BY的问题

    当使用group by的语句中,select后面跟的列,在group by后面没有时,会报以下错误: Expression #2 of SELECT list is not in GROUP BY c ...

  6. deepin 调整微信、百度网盘、迅雷等等软件字体的方法

    一.修改微信字体大小方法: 1.方法一:修改deepinwine桌面环境字体 env WINEPREFIX="$HOME/.deepinwine/Deepin-WeChat" wi ...

  7. JS笔记(四):面向对象、异常处理

    镇楼图 Pixiv:torino 六.JS中的面向对象 类(class) 博主视为你已拥有相关基础,这里不再赘述相关概念 类的语法如下,class在本质上是function,可以说class只是针对构 ...

  8. JS笔记(二):数据类型

    镇楼图 Pixiv:torino 三.数据类型 原始类型 原始类型像是string.symbol.number之类的都只能存储原子值,而不能像对象一样随意扩展.但是为了提供额外功能,采取了轻量的对象包 ...

  9. C#MSDN简体中文 最后一版本 2007年的

    我在学习C# 需要 先看 MSDN文档,英文看的很吃力,就找了好久 MSDN简体中文版本的: 因为微软已经关闭网站(MSDN中文的链接),我找到的就是  磁力链接的: 我分享出来 ,有需要的 自行下载 ...

  10. Alibaba Cloud Linux 3.2104 64位安装mydumper-0.9.1

    1 下载 wget https://launchpad.net/mydumper/0.9/0.9.1/+download/mydumper-0.9.1.tar.gz tar -zxvf mydumpe ...