- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
前言 。
搭建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,首先在defaultConfig下增加指定cpu.
ndk { abiFilters 'armeabi', 'armeabi-v7a', 'x86' }
然后在android下面关闭lint检测.
// 不在googlePlay上线,关闭lint检测 lintOptions { checkReleaseBuilds false abortOnError false }
我用的新版本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应用框架(一)的文章就讲到这里了,如果你想了解更多关于从头开始,手写android应用框架(一)的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
目录 1、背景 2、样本均值和样本方差矩阵 3、PCA 3.1 最大投影方差 3.2 最小重构距离 4、Py
android中获取屏幕的长于宽,参考了网上有很多代码,但结果与实际不符,如我的手机是i9000,屏幕大小是480*800px,得到的结果却为320*533 结果很不靠谱,于是自己写了几行代码,亲测
我写了一个 vector 类来学习 move 语义。 我使用 move 构造函数来 move T(注释行)。 我的问题是为什么不像在 C 中一样复制临时对象的所有字节并将临时对象的所有字节设置为零?
我需要解析一种类似于 Java 的最小化版本的语言。由于效率是最重要的因素,所以我选择手写解析器而不是像 GOLD、bison 和 yacc 这样的 LRAR 解析器生成器。 但是我找不到优秀的手写解
我正在尝试向 perlin 单纯形噪声函数添加 asm.js 注释: "use strict"; // Ported from Stefan Gustavson's java implementati
之前在bind和apply以及call函数使用中详解总结过bind和apply以及call函数的使用,下面手写一下三个函数。 一、首先call函数 Function.prototype.MyCall
我正在 asm.js 中编写优先级队列和八叉树Javascript 的子集,以便从它们中挤出最后可能的性能。 但是,您如何在 asm.js 函数的 heap 中存储对 Javascript 对象的引用
我是一名优秀的程序员,十分优秀!