经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
从头开始,手写android应用框架(一)
来源:cnblogs  作者:kiba518  时间:2023/3/31 8:58:41  对本文有异议

前言

搭建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。

代码如下:

  1. //okhttp
  2. implementation "com.squareup.okhttp3:okhttp:4.9.0"
  3. //gson
  4. implementation 'com.google.code.gson:gson:2.8.6'
  5. //fastjson
  6. implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.83'
  7. //解决超过65546代码的问题
  8. implementation 'com.android.support:multidex:1.0.2'
  9. implementation "com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.4"
  10. //ButterKnife
  11. implementation 'com.jakewharton:butterknife:10.2.3'
  12. annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
  13. // 数据库ormlite
  14. implementation 'com.j256.ormlite:ormlite-android:5.0'
  15. implementation 'com.j256.ormlite:ormlite-core:5.0'
  16. //权限请求rxpermissions
  17. implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
  18. //图片处理glide
  19. implementation 'com.github.bumptech.glide:glide:4.14.2'
  20. annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'//运行时 编译时 处理注解

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

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

  1. public class MyApplication extends Application {
  2. public static Context context;//全局上下文
  3. public static List<Activity> activityList = new ArrayList<Activity>();//用于存放所有启动的Activity的集合
  4. public static ApplicationInfo applicationInfo;
  5. ?
  6. @Override
  7. public void onCreate() {
  8. SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  9. Log.d("项目启动", "项目启动: " + DateUtils.getTime());
  10. super.onCreate();
  11. ?
  12. context = getApplicationContext();
  13. ?
  14. PackageManager packageManager = getApplicationContext().getPackageManager();
  15. try {
  16. packageManager = getApplicationContext().getPackageManager();
  17. applicationInfo = packageManager.getApplicationInfo(getPackageName(), 0);
  18. } catch (PackageManager.NameNotFoundException e) {
  19. applicationInfo = null;
  20. LogUtils.LogHelperError("获取applicationInfo报错", e);
  21. }
  22. ?
  23. CrashExceptionHandler.getInstance().init(this);
  24. ?
  25. //解决4.x运行崩溃的问题
  26. MultiDex.install(this);
  27. ?
  28. }
  29. ?
  30. private boolean isDebug() {
  31. return BuildConfig.DEBUG;
  32. }
  33. public static String GetProperties(String propertyName) {
  34. Properties props = new Properties();
  35. String serviceUrl = null;
  36. try {
  37. InputStream in =context.getAssets().open("appConfig.properties");
  38. props.load(in);
  39. String vaule = props.getProperty(propertyName);
  40. serviceUrl = new String(vaule.getBytes("ISO-8859-1"), "gbk");
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. AlertDialog.Builder dialog = new AlertDialog.Builder(context);
  44. dialog.setTitle("错误");
  45. dialog.setMessage("读取配置文件失败");
  46. dialog.setCancelable(false);
  47. removeALLActivity();
  48. }
  49. return serviceUrl;
  50. }
  51. /**
  52. * 销毁所有的Activity
  53. */
  54. public static void removeALLActivity() {
  55. //通过循环,把集合中的所有Activity销毁
  56. for (Activity activity : activityList) {
  57. if (!activity.isFinishing()) {
  58. activity.finish();
  59. }
  60. }
  61. MyApplication.activityList.clear();
  62. }
  63. ?
  64. }

然后注册CrashException和MultiDex。

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

HTTP请求

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

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

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

然后编写HttpUtils代码如下:

  1. public class HttpUtils {
  2. private static final OkHttpClient client = new OkHttpClient.Builder()
  3. .sslSocketFactory(SSLSocketClient.getSSLSocketFactory(), SSLSocketClient.getX509TrustManager())
  4. .hostnameVerifier(SSLSocketClient.getHostnameVerifier())
  5. .connectTimeout(10, TimeUnit.SECONDS)//设置连接超时时间
  6. .readTimeout(20, TimeUnit.SECONDS)//设置读取超时时间
  7. .build();
  8. ?
  9. ?
  10. private static void getRequest(String url, ICallback callback) throws IOException {
  11. new Thread() {
  12. @Override
  13. public void run() {
  14. Request request = new Request.Builder()
  15. .url(url)
  16. .build();
  17. ?
  18. try (Response response = client.newCall(request).execute()) {
  19. String result = response.body().string();
  20. callback.Call(result);
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. Log.d("http异常", e.getMessage());
  24. callback.Call(e.getMessage());
  25. ?
  26. }
  27. }
  28. }.start();
  29. ?
  30. }
  31. ?
  32. private static final MediaType mediaType = MediaType.get("application/json; charset=utf-8");
  33. ?
  34. private static void postRequest(String url, String param, ICallback callback) throws IOException {
  35. new Thread() {
  36. @Override
  37. public void run() {
  38. ?
  39. RequestBody body = RequestBody.create(mediaType, param);
  40. Request request = new Request.Builder()
  41. .url(url)
  42. .post(body)
  43. .build();
  44. ?
  45. try (Response response = client.newCall(request).execute()) {
  46. String result = response.body().string();
  47. callback.Call(result);
  48. } catch (IOException e) {
  49. e.printStackTrace();
  50. BaseResult baseResult=new BaseResult();
  51. baseResult.code=-1;
  52. callback.Call(JsonUtils.Serialize(baseResult));
  53. }
  54. }
  55. }.start();
  56. }
  57. ?
  58. private interface ICallback {
  59. void Call(String con);
  60. }
  61. public interface HttpData<T>{
  62. public void getData(T result);
  63. }
  64. public static <T> void post(String param, String urlAddress, HttpData<T> httpData) {
  65. ?
  66. try {
  67. HttpUtils.postRequest(urlAddress, param, con -> {
  68. runOnUiThread(() -> {
  69. ?
  70. BaseResult baseResult = JsonUtils.Deserialize(BaseResult.class, con);
  71. if (null != baseResult && baseResult.code == 1) {
  72. Class thisClass = httpData.getClass();
  73. ?
  74. Type[] superClassType = thisClass.getGenericInterfaces();
  75. ParameterizedType pt = (ParameterizedType) superClassType[0];
  76. ?
  77. Type[] genTypeArr = pt.getActualTypeArguments();
  78. Type genType = genTypeArr[0];
  79. Class c1= (Class) genTypeArr[0];
  80. ?
  81. T result = (T)JsonUtils.Deserialize(c1, con);
  82. httpData.getData(result);
  83. ?
  84. } else {
  85. if (null != baseResult) {
  86. ToastUtils.showToast("数据获取失败:" + baseResult.msg);
  87. } else {
  88. ToastUtils.showToast("数据获取失败");
  89. }
  90. }
  91. });
  92. });
  93. } catch (IOException e) {
  94. e.printStackTrace();
  95. }
  96. }
  97. BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(BaseResult.class, "con");
  98. public static <T> void get(String param, String urlAddress, HttpData<T> httpData) {
  99. ?
  100. try {
  101. ?
  102. HttpUtils.getRequest(urlAddress, con -> {
  103. runOnUiThread(() -> {
  104. BaseResult baseResult = (BaseResult)JsonUtils.Deserialize(Object.class, con);
  105. if (null != baseResult && baseResult.code == 1) {
  106. Class thisClass = httpData.getClass();
  107. ?
  108. Type[] superClassType = thisClass.getGenericInterfaces();
  109. ParameterizedType pt = (ParameterizedType) superClassType[0];
  110. ?
  111. Type[] genTypeArr = pt.getActualTypeArguments();
  112. Type genType = genTypeArr[0];
  113. Class c1= (Class) genTypeArr[0];
  114. ?
  115. T result = (T)JsonUtils.Deserialize(c1, con);
  116. httpData.getData(result);
  117. ?
  118. } else {
  119. if (null != baseResult) {
  120. ToastUtils.showToast("数据获取失败:" + baseResult.msg);
  121. } else {
  122. ToastUtils.showToast("数据获取失败");
  123. }
  124. }
  125. });
  126. });
  127. } catch (IOException e) {
  128. e.printStackTrace();
  129. }
  130. }
  131. }

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

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

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

编写Activity与Fragment

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

Activity于Fragment的编写思路如下:

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

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

KActivity代码简介:

  1. /**
  2. * @return 获取布局的id
  3. */
  4. protected int getLayoutId() {
  5. return -1;
  6. }
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. int layoutId = getLayoutId();
  11. if (layoutId != -1) {
  12. this.rootView = View.inflate(this, layoutId, null);
  13. setContentView(this.rootView);
  14. } else {
  15. throw new MissingResourceException("未使用getLayoutId()函数初始化view",this.getClass().getName(),"未初始化view");
  16. }
  17. if (savedInstanceState != null) {
  18. loadActivitySavedData(savedInstanceState);
  19. }
  20. }

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

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

  1. public class MainActivity extends BaseActivity {
  2. ?
  3. @Override
  4. protected int getLayoutId() {
  5. return R.layout.activity_main;
  6. }
  7. ?
  8. @Override
  9. protected void onCreate(Bundle savedInstanceState) {
  10. ?
  11. super.onCreate(savedInstanceState);
  12. ?
  13. }
  14. /**
  15. * 菜单、返回键响应
  16. */
  17. @Override
  18. public boolean onKeyDown(int keyCode, KeyEvent event) {
  19. if (keyCode == KeyEvent.KEYCODE_BACK) {
  20. //moveTaskToBack(true);
  21. }
  22. return true;
  23. }
  24. }

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

  1. public class MainFragment extends BaseFragment {
  2. @Override
  3. protected int getLayoutId() {
  4. return R.layout.fragment_main;
  5. }
  6. ?
  7. @Override
  8. protected void onCreate() {
  9. }
  10. }

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

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

gradle配置

app.gradle

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

  1. ndk {
  2. abiFilters 'armeabi', 'armeabi-v7a', 'x86'
  3. }

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

  1. //不在googlePlay上线,关闭lint检测
  2. lintOptions {
  3. checkReleaseBuilds false
  4. abortOnError false
  5. }

project.gradle

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

  1. plugins {
  2. id 'com.android.application' version '7.3.1' apply false
  3. id 'com.android.library' version '7.3.1' apply false
  4. }

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

  1. buildscript {
  2. repositories {
  3. maven { url 'https://jitpack.io' }
  4. maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava
  5. google()
  6. mavenCentral()
  7. jcenter() // Warning: this repository is going to shut down soon
  8. ?
  9. }
  10. dependencies {
  11. ?
  12. classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
  13. classpath 'com.android.tools.build:gradle:7.1.2'
  14. ?
  15. }
  16. }
  17. ?
  18. allprojects {
  19. repositories {
  20. maven { url 'https://jitpack.io' }
  21. maven { url "https://oss.jfrog.org/libs-snapshot" }//rxjava
  22. google()
  23. mavenCentral()
  24. jcenter() // Warning: this repository is going to shut down soon
  25. ?
  26. }
  27. }
  28. tasks.withType(JavaCompile) {
  29. options.encoding = "UTF-8"
  30. }
  31. task clean(type: Delete) {
  32. delete rootProject.buildDir
  33. }

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

生成的setting.gradle代码如下:

  1. pluginManagement {
  2. repositories {
  3. gradlePluginPortal()
  4. google()
  5. mavenCentral()
  6. }
  7. }
  8. dependencyResolutionManagement {
  9. repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  10. repositories {
  11. google()
  12. mavenCentral()
  13. }
  14. }
  15. rootProject.name = "framework"
  16. include ':app'

修改代码如下:

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

首页布局

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

结语

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

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

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

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

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

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

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

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

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

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

 

 

原文链接:https://www.cnblogs.com/kiba/p/17262561.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号