前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从头开始,手写android应用框架(一)

从头开始,手写android应用框架(一)

作者头像
Kiba518
发布2023-04-01 10:30:04
4500
发布2023-04-01 10:30:04
举报
文章被收录于专栏:Kiba518

前言

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

代码如下:

代码语言:javascript
复制
    //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文件,代码如下:

代码语言:javascript
复制
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代码如下:

代码语言:javascript
复制
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();
        }
    }
}

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

代码语言:javascript
复制
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代码简介:

代码语言:javascript
复制
 /**
     * @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实现如下:

代码语言:javascript
复制
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实现如下:

代码语言:javascript
复制
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。

代码语言:javascript
复制
 ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
        }

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

代码语言:javascript
复制
//不在googlePlay上线,关闭lint检测
    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
project.gradle

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

代码语言:javascript
复制
plugins {
    id 'com.android.application' version '7.3.1' apply false
    id 'com.android.library' version '7.3.1' apply false
}

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

代码语言:javascript
复制
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代码如下:

代码语言:javascript
复制
pluginManagement {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "framework"
include ':app'

修改代码如下:

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-03-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • app.gradle
  • project.gradle
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档