首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >自定义Androidk全量更新组件

自定义Androidk全量更新组件

作者头像
饮水思源为名
发布于 2018-09-06 05:01:02
发布于 2018-09-06 05:01:02
1.4K0
举报
文章被收录于专栏:Android小菜鸡Android小菜鸡

自动更新功能对于一个APP来说是必备的功能,特别是对于未投放市场下载的APP,每次都让用户删掉原来的,再下载新的版本,肯定是不合适的。

实现思路:

  1. 后台提供接口,返回服务端版本号serviceVersion以及APK下载地址
  2. 前端对接接口,用拿到的serviceVersion和APK配置的localVersion比较,如果serviceVersion>localVersion则提示可以更新,通过获取的APK下载地址下载,然后通过api打开安装完成更新。

注意:

  1. localVersion笔者使用的是versionCode,可以再AndroidManifest中配置,通过java代码获取。笔者与后台约定了Code的规则,采用更新时间编辑,例如2018年8月2号,则versionCode=“180802”
代码语言:javascript
AI代码解释
复制
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.mintu.dcdb"
    android:versionCode="180802"
    android:versionName="3.6">
代码语言:javascript
AI代码解释
复制
  /**
     * 获取当前本地apk的版本号
     * @param mContext
     * @return
     */
    public static int getVersionCode(Context mContext) {
        int versionCode = 0;
        try {
            //获取软件版本号,对应AndroidManifest.xml下android:versionCode
            versionCode = mContext.getPackageManager().
                    getPackageInfo(mContext.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return versionCode;
    }
  1. 对于Android7.0以上的手机,打开附件做了改变,无法使用以往的uri发布意图,详情可见笔者之前的一篇文章。Android7.0以上版本打开附件失败问题
  2. 本文的文件下载、附件打开方法使用的是笔者封装的OkHttp3工具类,使用者可以自己随意替换。只要将APK从url上下载下来,用API打开即可。

核心代码:

代码语言:javascript
AI代码解释
复制
     /***
     * 检查是否更新版本
     */
    private void checkVersion() {
        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }  
代码语言:javascript
AI代码解释
复制
    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }
代码语言:javascript
AI代码解释
复制
 /**
     * 打开附件的方法
     * @param f
     * @param context
     */
    public void openFile(File f, Context context) {
        Log.i(LOGTAG, "正在打开附件打--------" + f.getName()+"。附件大小为"+f.length());
        try {
            String end = f.getName().substring(f.getName().lastIndexOf(".")
                    + 1, f.getName().length()).toLowerCase();
            if(end.equals("amr")){
                AudioRecoderUtils.getInstance().playerStart(f.getAbsolutePath());
            }else {
                Intent intent = new Intent();
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.setAction(android.content.Intent.ACTION_VIEW);
                intent.addCategory("android.intent.category.DEFAULT");
      /* 调用getMIMEType()来取得MimeType */
                String type = getMIMEType(f);
      /* 设置intent的file与MimeType */
                if(Build.VERSION.SDK_INT>=24){
                    Uri contenturi=FileProvider.getUriForFile(context, "com.mintu.dcdb.fileprovider",f);
                    intent.setDataAndType(contenturi,type);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, contenturi);
                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }else{
                    intent.setDataAndType(Uri.fromFile(f), type);
                }
                context.startActivity(intent);
            }
        } catch (Exception e) {
//            Toast.makeText(context,"打开附件---"+f.getName()+",发生了错误",Toast.LENGTH_SHORT).show();
            Log.e(LOGTAG, "打开附件" + f.getName() + "报错了,错误是-----" + e.getMessage());
        }
    }

项目中全量更新源码:

代码语言:javascript
AI代码解释
复制
package com.mintu.dcdb.util.updateAppUtil;

import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.google.gson.Gson;
import com.mintu.dcdb.R;
import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.config.RequestUrl;
import com.mintu.dcdb.main.bean.UpdateBean;
import com.mintu.dcdb.main.view.UpdateDialog;
import com.mintu.dcdb.util.LogUtil;
import com.wusy.wusylibrary.base.BaseActivity;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.IOException;
import java.util.ArrayList;

import okhttp3.Call;

/**
 * Created by XIAO RONG on 2018/7/19.
 */

public class UpdateAppUtil {
    private SharedPreferencesUtil sharedPreferencesUtil;
    private BaseActivity activity;
    private  ProgressDialog m_pDialog;
    private boolean isAutoUpdate=false;

    public UpdateAppUtil(BaseActivity activity,boolean isAutoUpdate){
        sharedPreferencesUtil=SharedPreferencesUtil.getInstance(activity);
        this.activity=activity;
        this.isAutoUpdate=isAutoUpdate;
    }

    public void updateApp(){
        String url = RequestUrl.getInstance().getUpdateUrl((String) SharedPreferencesUtil.getInstance(activity).getData(Constant.VERSION_CODE, ""));
        OkHttpUtil.getInstance().asynGet(url, new OkHttpUtil.ResultCallBack() {
            @Override
            public void successListener(Call call, String responseStr) {
                LogUtil.i("update result str--"+responseStr);
                Gson gson = new Gson();
                UpdateBean bean = gson.fromJson(responseStr, UpdateBean.class);
                sharedPreferencesUtil.saveData(Constant.VERSION_CODE,bean.getVersion());
                sharedPreferencesUtil.saveData(Constant.VERSION_URL,bean.getUrl());
                sharedPreferencesUtil.saveData(Constant.VERSION_CONTENT,bean.getContent());
                sharedPreferencesUtil.saveData(Constant.VERSION_PATCH_FILE_PATH,bean.getPatchFilePath());
                sharedPreferencesUtil.saveData(Constant.VERSION_IS_PATCH_FILE_PATH,bean.getIsPatchFilePath());
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        checkVersion();
                    }
                });
            }

            @Override
            public void failListener(Call call, IOException e) {

            }
        });
    }
    /***
     * 检查是否更新版本
     */

    private void checkVersion() {

        if (Integer.parseInt((String) sharedPreferencesUtil.getData(Constant.VERSION_CODE_LOCAL,"")) < Integer
                .parseInt(CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CODE,"")) ? "0"
                        : (String) sharedPreferencesUtil.getData(Constant.VERSION_CODE,""))) {
            showDialog(new DownLoadBroadCastReceiver());
        }else{
            if(!isAutoUpdate) Toast.makeText(activity, "未检查到新版本", Toast.LENGTH_SHORT).show();
        }
    }
    private void showDialog(final BroadcastReceiver receiver) {
        final Dialog alert = new UpdateDialog(activity, R.style.MyDialogStyle);
        alert.setContentView(R.layout.upgrade_dialog);
        TextView tvView = (TextView) alert.findViewById(R.id.upgradeText);
        if (!CommonUtil.getInstance().isNull(sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, ""))) {
            tvView.setText(Html.fromHtml((String) sharedPreferencesUtil.getData(Constant.VERSION_CONTENT, "")));
        }

        tvView.setWidth(activity.getWindowManager().getDefaultDisplay().getWidth() * 3 / 4);
        Button sureBtn = (Button) alert.findViewById(R.id.btn_sure);
        Button cancleBtn = (Button) alert.findViewById(R.id.btn_cancle);

        sureBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {


                ArrayList<String> actionList = new ArrayList<>();
                actionList.add(Constant.DOWNLOAD_ACTION);
                activity.addBroadcastAction(actionList, receiver);
                Intent updateIntent = new Intent(activity,
                        UpdateService.class);
                updateIntent.putExtra("app_name", activity.getResources()
                        .getString(R.string.app));

                activity.startService(updateIntent);
                alert.dismiss();
            }
        });
        cancleBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                alert.dismiss();
            }
        });
        alert.show();
    }
    class DownLoadBroadCastReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            switch (intent.getAction()) {
                case Constant.DOWNLOAD_ACTION:
                    int progress = intent.getIntExtra("download", 0);
                    showProgress(progress);
                    break;

                default:
                    break;
            }

        }
    }
    private void showProgress(Integer progress) {
        if (m_pDialog == null) {
            createProgressDialog("正在更新请稍后...");
        }
        m_pDialog.setProgress(progress);
        if (progress == 100) {
            m_pDialog.dismiss();
        }
    }
    private void createProgressDialog(String title) {

        // 创建ProgressDialog对象
        m_pDialog = new ProgressDialog(activity);

        // 设置进度条风格,风格为长形
        m_pDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);

        // 设置ProgressDialog 标题
        m_pDialog.setTitle(title);
        // 设置ProgressDialog 进度条进度
        m_pDialog.setProgress(100);

        // 设置ProgressDialog 的进度条是否不明确
        m_pDialog.setIndeterminate(false);

        // 设置ProgressDialog 是否可以按退回按键取消
        m_pDialog.setCancelable(true);
        // 设置点击进度对话框外的区域对话框不消失
        m_pDialog.setCanceledOnTouchOutside(false);
        // 让ProgressDialog显示
        m_pDialog.show();
    }
}
代码语言:javascript
AI代码解释
复制
package com.mintu.dcdb.util.updateAppUtil;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

import com.mintu.dcdb.config.Constant;
import com.mintu.dcdb.util.LogUtil;
import com.mintu.dcdb.util.SystemUtils;
import com.wusy.wusylibrary.util.CommonUtil;
import com.wusy.wusylibrary.util.OkHttpUtil;
import com.wusy.wusylibrary.util.SharedPreferencesUtil;

import java.io.File;

/**
 *
 * Filename: UpdateService.java Description: 今天修改了当增量包合成失败的时候,重新下载整个最新的apk
 * Company: minto
 *
 * @author: chjr
 * @version: 2.8.0 Create at: 2016年5月20日 下午3:25:52
 *
 */
public class UpdateService extends Service {
    private final int TIMEOUT = 10 * 1000;// 超时
    private final int DOWN_OK = 1;
    private final int DOWN_ERROR = 0;
    private String app_name;
    private String file_name;

    private NotificationManager notificationManager;
    private Notification notification;

    private Intent updateIntent;
    private PendingIntent pendingIntent;

    private int notification_id = 0;
    ProgressDialog m_pDialog;
    private File updateDir = new File(Constant.FILEDIR);
    File updateFile;
    SharedPreferencesUtil vsPreference;
    String packageName = "com.minto.workhi";
    String patchUrl = Constant.FILEDIR;
    String newApkUrl = Constant.FILEDIR;
    private String downUrl = "";
    private int flag = 2;
    private Intent broadCast;
    @Override
    public IBinder onBind(Intent arg0) {
        stopSelf();
        return null;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        vsPreference = SharedPreferencesUtil.getInstance(getApplicationContext());
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (CommonUtil.getInstance().isNull(intent)) {
            stopSelf();
            Log.d("UpService", intent + "--------------------");
        } else {

            app_name = intent.getStringExtra("app_name");
            file_name = app_name + ".apk";


            patchUrl = patchUrl + app_name + ".patch";
            // 创建文件
            CommonUtil.getInstance().createFile(app_name + ".patch");
            CommonUtil.getInstance().createFile(file_name);
            updateFile = new File(updateDir + "/" + app_name + ".patch");
            if(flag==1)flag=2;
            broadCast = new Intent();
            broadCast.setAction(Constant.DOWNLOAD_ACTION);
            broadCast.putExtra("download", 0);
            sendBroadcast(broadCast);
            downFile();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    /***
     * 开线程下载
     */
    public void createThread(final String downUrl) {
        final Message message = new Message();
        if (SystemUtils.isNetworkAvailable(getApplicationContext())) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        OkHttpUtil.getInstance().download(downUrl, newApkUrl, file_name, new OkHttpUtil.OnDownloadListener() {
                            @Override
                            public void onDownloadSuccess(File downfile, File file) {
                                hand();
                                stopSelf();
                            }
                            @Override
                            public void onDownloading(int progress, File file) {
                                LogUtil.i("update progress---"+progress);
                                broadCast.putExtra("download", progress);
                                sendBroadcast(broadCast);
                            }
                            @Override
                            public void onDownloadFailed(String error) {
                            }
                        });
                    } catch (Exception e) {
                    }
                }
            }).start();
        } else {
            Toast.makeText(getApplicationContext(), "网络无连接,请稍后下载!",
                    Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 根据类型下载不同的文件
     *
     * @exception
     * @since 1.0.0
     */
    protected void downFile() {
        // 下载增量包路径
        if (1 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_PATCH_FILE_PATH,"");
        } else if (2 == flag) {
            downUrl = (String) vsPreference.getData(Constant.VERSION_URL,"");
        }
        createThread(downUrl);
    }

    /**
     * 根据不同的包安装
     *
     * @exception
     * @since 1.0.0
     */
    protected void hand() {
        // 增量合成方法
        if (1 == flag) {
//          ApkUpdate update = new ApkUpdate(getApplicationContext(),
//                  packageName, patchUrl, newApkUrl);
//          // 检查低版本的apk 是否存在
//          if (update.isOldApkExist()) {
//              // 判断是否成功合成新的apk
//              if (update.newApkGet()) {
//                  update.installApk();
//                  delData();
//              } else {
//                  // Toast.makeText(getApplicationContext(),
//                  // "下载失败!:" + newApkUrl + "增量包地址:" + patchUrl,
//                  // Toast.LENGTH_SHORT).show();
//                  flag = 2;
//                  downFile();
//              }
//          }
        }
        // 整包安装方法
        else if (2 == flag) {
            File apkFile = new File(newApkUrl, file_name);
            OkHttpUtil.getInstance().openFile(apkFile,this);
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.08.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
划重点 | Android Jetpack 三大重要更新!
Android Jetpack 集合了一系列的开发库,旨在帮助开发者更容易地创作高质量的应用,同时也更好地兼容老旧版本的 Android 系统。在正式发布 Jetpack 两年后的今天,我们已经看到大量的应用开发开始采用 Jetpack 中的开发库,这其中既包括大型开发团队的产品,也有那些刚起步的应用。而这一切仅仅是开始,因为近期我们发布了一系列新的开发库,以及过去一年我们对于现有开发库的重要更新。
音视频开发进阶
2020/07/24
2K0
划重点 | Android Jetpack 三大重要更新!
Android Jetpack架构组件(一)与AndroidX
自2008年9月22日谷歌发布Android 1.0版本到前不久Android 12版本到发布,Android已经陪伴我们走过了12个年头。可以说,经过12年的打磨和沉淀,Android的技术体系已经非常的成熟了。比如说,一开始时框架很少,也没有什么规范,所有的代码都是要自己写,但是现在,我们很少会关系这种基础代码,因为一些框架和工具的出现正在帮助开发者完成这方面的工作。
xiangzhihong
2020/12/21
2.5K0
Android Jetpack 学习笔记(1) - 概述
Android Jetpack 套件是最近比较流行的组件库,它包含了一系列的优秀实践,本文是先介绍 Jetpack 的概貌。
张云飞Vir
2021/12/06
2.6K0
Android:手把手带你了解实用的Android Jetpack
提供了最基础的底层功能,如向后兼容性、测试、开发语言Kotlin支持等。包含的组件库:
Carson.Ho
2020/02/18
1.3K0
Android Jetpack系列——Android Jetpack介绍
早在2008年,Google 推出了Android ,但那个时候 Android 刚刚问世,经过这将近11年的发展和不断优化,Android 可以说逐渐变得成熟,方便和应用越来越广。 随着 Android 手机的市场保有量越来越多,APP的研发朝着愈发的简单化,易上手的方向发展。而 Google 也是站在这一个角度出发,如何快速得让一个研发人员开发出一款APP,也可以快速的添加新的开发人员。 现在在 Google 应用市场当中,大部分 APP 已经开始使用 Android Jetpack。通过 Android Jetpack 可以让我们的 BUG 减少,让我们把更多的精力放在打造应用本身。 为了这种模板式的开发,Google 在2018年推出了 Android Jetpack。接下来,我将会通过一系列的文章来介绍Android Jetpack,旨在希望了解、学习、应用Android Jetpack的小伙伴一个参考资料。
Demo_Yang
2019/05/15
2K0
Jetpack 重磅更新!
Android Jetpack 是一套帮助你轻松构建高质量应用,兼容旧版本系统的类库套件。在 Jetpack 发布两年之后的现在,我们已经看到了很多 app 的广泛采用,并且更多的开发者开始使用了。这只是一个开始:今天,我们将发布过去一年的工作成果,一些新的类库以及现有类库的重大更新。
路遥TM
2021/08/31
1.5K0
聚焦 Android 11: Jetpack
在往期 #11WeeksOfAndroid 系列文章中我们介绍了 联系人和身份 、隐私和安全 、 Android 11 兼容性 、 开发语言 ,本期将聚焦  Jetpack 。我们将为大家陆续带来 #11WeeksOfAndroid 内容,深入探讨 Android 的各个关键技术点,您不会错过任何重要内容。
Android 开发者
2020/10/16
1K0
JetPack最新库的简单介绍
这个其实没啥可说的,其实就是简化了一部分用法,比如把构造器放到activity上去。参考链接 How AndroidX changes the way we work with Activities and Fragments A first look at AndroidX Activity Result APIs
提莫队长
2021/07/19
1K0
Android Jetpack 更新一览
作者 / Florina Muntenescu, Android Developer Advocate
Android 开发者
2022/03/09
1.9K0
Android Jetpack 更新一览
现代 Android 开发的三大亮点
今年的 Google I/O 大会推出了有关现代 Android 开发的大量更新。您最需要了解的三大亮点可以 点击这里 查看相关视频了解。
Android 开发者
2022/03/09
6960
深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析
Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了
程序员徐公
2021/04/28
1.6K0
深入探索Android 启动优化(七) - JetPack App Startup 使用及源码浅析
推荐开发者使用 Material Design 组件
为了保证您的应用与用户设备中安装的其他应用在视觉和行为上保持一致,我们 推荐 您遵循 Material Design 规范,因为用户从一个应用中学习的操作模式可以无缝衔接地在另一个应用中使用。
Android 开发者
2022/03/09
1.3K0
推荐开发者使用 Material Design 组件
Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
链接:https://juejin.im/post/5ee4bbe4f265da76b559bdfe
陈宇明
2020/12/16
9380
Jetpack 最新成员 AndroidX App Startup 实践以及原理分析
【Jetpack】Jetpack 简介 ( 官方架构设计标准 | Jetpack 组成套件 | Jetpack架构 | Jetpack 的存在意义 | AndroidX 与 Jetpack 的关系 )
Android 架构发展 : Android 架构的发展 途径了 MVC -> MVP -> MVVM 等方案 , 这些架构都 不是 Google 官方提出的 , 都是各个团队 根据自己的需求推出的适合自己的架构方案 ;
韩曙亮
2023/03/30
2.7K0
【Jetpack】Jetpack 简介 ( 官方架构设计标准 | Jetpack 组成套件 | Jetpack架构 | Jetpack 的存在意义 | AndroidX 与 Jetpack 的关系 )
使用 Jetpack App Startup 库减少应用启动时间
应用启动时间是应用性能的关键衡量指标。应用启动后,用户期望能够得到快速响应并加载内容,当不符合预期时用户会感到失望。这种糟糕的体验可能会导致用户在 Play 商店上对您的应用给予低分数的评价,甚至不会再次使用。
Android 开发者
2022/03/09
5200
Android Sunflower 带您玩转 Jetpack
△ 插图作者:Virginia Poltrack 在 Google I/O 2018 开发者大会上,我们推出了 Android Jetpack,其中包含的 Android 开发架构组件能够帮助您简化开发流程,从而轻松打造出优质应用。开发者能够利用 Jetpack 组件学习最佳实践,减少样板代码,简化复杂任务,进而将精力集中在关键代码上。
Android 开发者
2018/08/30
1.6K0
Android Sunflower 带您玩转 Jetpack
聚焦 Android 11: 大功告成
这是 #11WeeksOfAndroid 系列的最后一篇文章。感谢您在过去的时间里和我们一起深入探索 Android 开发的关键领域。下面来和我们一起回顾这些精彩内容吧:
Android 开发者
2022/09/23
2.8K0
浅谈2022Android端技术趋势,什么[值得]学?
回头去看 2021,过的似乎那么快,不敢相信我已经从事 Android 开发两年了,不免生出一些感叹。
Petterp
2022/01/18
9580
浅谈2022Android端技术趋势,什么[值得]学?
7. JetpackNote---基于Jetpack的学习笔记APP
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
Hankkin
2019/09/18
1.1K0
7. JetpackNote---基于Jetpack的学习笔记APP
Kotlin 和 Jetpack 视频合集 | MAD Skills
在近期完成的一系列 Modern Android Development (简称 MAD Skills) 的视频和文章中,我们重点关注了 Kotlin 和 Jetpack。我们介绍了多种不同的方法,让 Android 代码更具表现力、更简洁、更安全以及更易于使用 Kotlin 运行异步代码。
Android 开发者
2022/03/09
8620
推荐阅读
相关推荐
划重点 | Android Jetpack 三大重要更新!
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档