Android app版本迭代升级(适配Android 10)

Android 11 适配指南 Android11 适配 阅读详情
AI摘要

本文详细介绍App版本迭代过程中的版本对比、权限申请、下载及安装流程,包括AndroidManifest配置、权限动态申请、下载进度监听及7.0以上版本的安装兼容性处理。

在我们进行App开发的时候,免不了进行进行版本迭代,所以就将自己的版本迭代进行整理,以便大家使用,也对自己以后的开发更方便
首先是请求后台数据,获取当前后台版本号,然后和自己客户端的版本号进行比对,如果高于当前版本,就进行升级

在AndroidManifest中声明权限(读写权限需要动态申请,允许未知来源声明一下就可以了)

//写入文件夹权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//安卓8.0之后安装需要提示允许未知来源安装
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
   //versionCode 是当前App的版本号
if (versionCode < 服务器版本号) {
                //不强制更新(这是我们的判断,用来判断是否是强制更新)
                if (SPUtil.getFormKey(this, Contact.ISFORCEUPDATE).equals("0")) {
                    //这是弹出框,用来展示更新描述的
                    final UpdateDialog updateDialog = new UpdateDialog(MainActivity.this, "版本更新", updateInfo, "升级");
                    updateDialog.setDialogListener(new DialogListener() {
                        @Override
                        public void confirm(String tag) {

                            //这些弹出框可以自己写,重要的就是这一句,其他的都是弹出框
                            //这是用来请求存储权限的
                            //downloadUrl = “可以随便写,比如fileurl(我是这么写的)”
                            permissionRequest(updateDialog, downloadUrl, "0", Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE);//Permission请求实例
                       

 }
                    });
                    updateDialog.show();
                } else {
                    //强制更新(弹出框可以自定义,只需要在点击升级的时候做操作就行)
                    final UpdateDialog updateDialog = new UpdateDialog(MainActivity.this, "版本更新", updateInfo, "升级");
                    updateDialog.setCanceledOnTouchOutside(false);
                    updateDialog.setCancelable(false);
                    updateDialog.setDialogListener(new DialogListener() {
                        @Override
                        public void confirm(String tag) {
                            
                            //这些弹出框可以自己写,重要的就是这一句,其他的都是弹出框
                            permissionRequest(updateDialog, downloadUrl, "1", Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE);//Permission请求实例
                       
 }
                    });
                    updateDialog.show();
                }
            } else {
                //当前不更新
            }

这个Version我创建了一个工具类,用来获取当前App的版本号和版本名称,自行选择
 


/**
 * 获取 versioncode versionname 工具类
 */
public class APKVersionCodeUtils {

    /**
     * 获取当前本地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;
    }

    /**
     * 获取版本号名称
     *
     * @param context 上下文
     * @return
     */
    public static String getVerName(Context context) {
        String verName = "";
        try {
            verName = context.getPackageManager().
                    getPackageInfo(context.getPackageName(), 0).versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return verName;
    }
}

因为我们在从服务器下载完安装包之后,就要将安装包保存到本地文件夹,所以在点击升级的时候,我们要先去申请读写权限,这个权限要在AndroidManifest里面声明一下,就不在多说
 

//这是申请权限的方法   
 private void permissionRequest(final UpdateDialog updateDialog, final String fileUrl, final String isForce, String... permissions) {
        mSetting = new PermissionSetting(this);
        AndPermission.with(this)
                .permission(permissions)
                .onGranted(new Action() {
                    @Override
                    public void onAction(List<String> permissions) {
                        //提示用户正在下载
                        Toast.makeText(getApplicationContext(), "正在下载...", Toast.LENGTH_LONG).show();
                        //我将下载APK和安装APK并替换放在了工具类中
                        DownloadUtils downloadUtils = new DownloadUtils(MainActivity.this);
                        downloadUtils.downloadAPK(fileUrl, "你下载安装包的名称,自行定义,以.apk结尾");
                        //判断当前弹出框是否是强制升级,如果是就不让他隐藏
                        if (isForce.equals("0")) {
                            updateDialog.dismiss();
                        } else {

                        }

                    }
                })
                .onDenied(new Action() {
                    @Override
                    public void onAction(@NonNull List<String> permissions) {
                        //权限申请失败,不做任何操作
                        Log.e("Main_Activity", "权限申请失败");
                    }
                })
                .start();
    }

我这里的权限申请再次说明只是用来使用他的申请成功和申请失败的方法,你可以自己去找权限申请方法,只要你能申请下来,就都可以,没必要纠结我这个,因为我做了很多操作,代码有点麻烦,就不粘出来了
 

//下载APK并安装的工具类
public class DownloadUtils {
    //下载器
    private DownloadManager downloadManager;
    //上下文
    private Context mContext;
    //下载的ID
    private long downloadId;
    private Uri imageUri;

    public DownloadUtils(Context context) {
        this.mContext = context;
    }

    //下载apk
    public void downloadAPK(String url, String name) {
        downloadManager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
        //创建下载任务
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        //移动网络情况下是否允许漫游
        request.setAllowedOverRoaming(false);
        request.setMimeType("application/vnd.android.packed-archive");
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"");
        //在通知栏中显示,默认就是显示的
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setTitle("通知栏的下载APP名称");
        request.setDescription("通知栏的下载提示");
        request.setVisibleInDownloadsUi(true);


        //设置下载的路径
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, name);
        File apkfile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "你之前下载的.apk结尾的安装包名称,以.apk结尾");
        if (apkfile.exists()) {
            apkfile.delete();
        }
        //获取DownloadManager
        //将下载请求加入下载队列,加入下载队列后会给该任务返回一个long型的id,通过该id可以取消任务,重启任务、获取下载的文件等等
        assert downloadManager != null;
        downloadId = downloadManager.enqueue(request);

        //注册广播接收者,监听下载状态
        mContext.registerReceiver(receiver,
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }

    //广播监听下载的各个状态
    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            checkStatus();
        }
    };


    //检查下载状态
    private void checkStatus() {
        DownloadManager.Query query = new DownloadManager.Query();
        //通过下载的id查找
        query.setFilterById(downloadId);
        Cursor c = downloadManager.query(query);
        if (c.moveToFirst()) {
            int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
            switch (status) {
                //下载暂停
                case DownloadManager.STATUS_PAUSED:
                    break;
                //下载延迟
                case DownloadManager.STATUS_PENDING:
                    break;
                //正在下载
                case DownloadManager.STATUS_RUNNING:
                    break;
                //下载完成
                case DownloadManager.STATUS_SUCCESSFUL:
                    //下载完成安装APK
                    installApk();
                    DeafultToast.show("下载完成");
                    break;
                //下载失败
                case DownloadManager.STATUS_FAILED:
                    Toast.makeText(mContext, "下载失败", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
        c.close();
    }

    //    /**
//     * 7.0兼容
//     */
//    private void installAPK() {
//        File apkFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "caizhidao.apk");
//        Intent intent = new Intent(Intent.ACTION_VIEW);
//        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//        if (Build.VERSION.SDK_INT >= 24) {
//            Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".provider", apkFile);
//            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
//            intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
//        } else {
//            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
//        }
//        mContext.startActivity(intent);
//    }

    //去调用你下载完成的apk进行安装
    private void installApk() {
        File apkfile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "你之前下载完成的.apk名称,以.apk结尾");
        if (!apkfile.exists()) {
            return;
        }

        //判断当前用户是否同意了允许安装未知应用来源权限
        boolean b = mContext.getPackageManager().canRequestPackageInstalls();
        if (b) {
            Intent i = new Intent(Intent.ACTION_VIEW);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 7.0+以上版本
                Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".provider", apkfile); //(重中之重,如果不一致,就会调用失败,他找不到你下载过的安装包,就会出现安装失败,再次提示,上线之前,一定要检查一下升级是否好使)与manifest中定义的provider中的authorities=""保持一致
                i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                i.setDataAndType(apkUri, "application/vnd.android.package-archive");
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            } else {
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                i.setDataAndType(Uri.parse("file://" + apkfile.toString()),
                        "application/vnd.android.package-archive");
            }
            mContext.startActivity(i);
        } else {
//跳转页面让用户选择
            Uri parse = Uri.parse("package:" + mContext.getApplicationContext().getPackageName());
            Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, parse);
            Activity activity = (Activity) mContext;
//Constants.UNKNOWN_INSTALL这是个静态属性,自己定义一个就行,我的等于2020
            activity.startActivityForResult(intent, Constants.UNKNOWN_INSTALL);
        }
    }
}

 在我们跳转到用户选择完未知来源权限之后,用户同意后需要直接调用安装方法,所以在调用的Activity中需要再判断一下:

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == Constants.UNKNOWN_INSTALL) {
            // 检查用户是否更改了权限
            if (Settings.Secure.getInt(getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS, 0) == 1) {
                // 权限被更改,可以安装应用
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    downloadUtils.installApk();
                }
            } else {
                // 权限未被更改,可以给出提示或者采取其他措施
                Toast.makeText(mContext, "请在设置中同意安装位置权限才可安装", Toast.LENGTH_SHORT).show();
            }
        }
    }

虽然我在代码中写了,但是还是要提醒一下大家,Uri apkUri = FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".provider", apkfile);(一定要和AndroidManifest中定义的provider中的authorities一致,不然找不到安装包啊!!!上线记得测试!测试!在测试!)

接下来就是我们AndroidManifest中的东西了,
 

    //就在application下设置就行,可以存在多个   
 <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="你的包名.provider"
            android:exported="false"
            android:grantUriPermissions="true">

            <!-- 元数据 -->
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path" />
        </provider>

记住啊,这个authorities和你代码中写的一定要一样啊,这个@xml/file_path是我们安装后安装的路径,没必要指定,就放在系统默认的地方就行


<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <paths>
        <external-path
            name="download"
            path="" />
    </paths>
</resources>

这个就是那个file_path,现在是结束了,但是大家一定要测试一下,希望这篇文章对大家能有所帮助~
 

想了想,还是将我使用的权限升级写上吧,就是这个

implementation 'com.yanzhenjie:permission:2.0.0-rc4'

这篇博客在最近使用的时候,发现在Android10上不兼容,在Debug运行的时候,发现

这里的apkfile.exists()为空,这是因为android10的读取发生了更改,需要在AndroidManifest里面添加上

android:requestLegacyExternalStorage="true"

大家注意,添加这个权限,需要你的targetSdkVersion为29,或者是最低的为26好像是。

新增app下载完apk之后,需要删除旧版apk,只保留一个apk存在,我的调用是在每次调用下载的时候,去文件中查询是否存在旧版APK,如果存在就删除:(我这里用的是适配Android10的文件读取方式和存储方式)

private void deleteApk() {
        File apkfile = new File(Environment.getExternalStoragePublicDirectory
                (Environment.DIRECTORY_DOWNLOADS),
                "你下载的apk名称.apk");
        if (apkfile.exists()) {
            apkfile.delete();
        }
    }

这样就只剩一个apk文件了,告辞!

uniapp - 安卓APP热更新与强制升级实战指南 本文提供了uniapp开发安卓APP时实现热更新与强制升级的完整实战指南。详细对比了借助蒲公英第三方平台与自建后台接口两种主流方案,涵盖核心代码实现、wgt包制作、高版本安卓权限适配及灰度发布等高级技巧,帮助开发者快速构建稳定高效的APP更新体系,提升版本覆盖率和用户体验。 阅读详情

相关推荐

Android 13.0 根据包名授予OP_REQUEST_INSTALL_PACKAGES权限

在系统13.0的定制化开发中,对于在app中调用安装第三方app的时候,会在这时弹出安装未知来源弹窗,需要默认授予REQUEST_INSTALL_PACKAGES 权限,来安装第三方app的安装未知来源权限,所以就是今天需要解决的这个问题

安卓兼职framework和app工程师的博客 2307

Google Play的QUERY_ALL_PACKAGES或REQUEST_INSTALL_PACKAGES权限问题

应用发布Google Play的QUERY_ALL_PACKAGES或REQUEST_INSTALL_PACKAGES权限问题

墨客 9218

Android12.0 根据包名授予OP_REQUEST_INSTALL_PACKAGES权限

在系统12.0的定制化开发中,对于在app中调用安装第三方app的时候,会在这时弹出安装未知来源弹窗,需要默认授予REQUEST_INSTALL_PACKAGES 权限,来安装第三方app的安装未知来源权限,所以就是今天需要解决的这个问题

安卓兼职framework和app工程师的博客 3038

apk安装流程

android系统启动之后调用PackageManagerService服务并解析系统特定目录,解析apk文件并安装的过程,这个安装过程实际上是没有图形界面的,底层调用的是我们平时比较熟悉的adb命令,那么我们平时安装apk文件的时候大部分是都过图形界面安装的,那么这种方式安装apk具体的流程是怎样的呢? 本文我们就来具体看一下apk的具体安装过程,通过本文的学习希望帮助大家大概的了解到Android系统安装Apk文件的基本流程。 相信大家都知道如果我们想在代码里执行apk的安装,那么一般都是这样: Int

weixin_42904469的博客 1万+

最全卷积总结篇(分组、深度可分离、空洞、转置等)

分组卷积(Group Convolution)是一种卷积操作方式,它最早在AlexNet中引入,并在深度学习中被广泛使用,尤其是在高效模型设计(如MobileNet和ResNeXt)中。分组卷积通过将输入特征通道分为若干组,并在每组内单独进行卷积操作,可以减少计算复杂度和参数量,同时有效利用模型的结构特性。分组卷积的工作原理通道分组:输入特征图的通道图为C_in,输出特征图的通道数为C_out,将每组的C_in和C_out分为G组,每组的通道数为C_in/G和C_out/G。组内卷积。

qq_44815135的博客 4219

ZYNQ Linux环境下PS端I2C驱动OV5640摄像头全流程解析

本文详细解析了在ZYNQ Linux环境下,通过PS端I2C驱动OV5640摄像头的完整流程。内容涵盖从Vivado硬件配置、PetaLinux系统构建、设备树节点添加,到用户空间I2C编程与寄存器配置实战,并提供了关键的调试技巧与常见问题排查方法,帮助开发者快速实现嵌入式视觉应用的传感器配置。

vulkan6gpu的博客 853

Android 10 完美适配

背景 2019 年 9 月 3 日,Google 发布了 Android 10 正式版。Android 10 聚焦移动创新、安全隐私和数字健康三大主题,全面打造最佳的用户体验。 在Android 10 版本中,官方的改动较大,相应的开发者适配成本还是很高的。基于前期调研,我们主要基于以下几方面进行Android 10的适配: Android X 分区存储 设备ID 明文HTTP限制 1. AndroidX AndroidX 对原始 Android Support库进行了重大改进,后者现在已不再维护。An

lixiong0713的博客 2万+

Nvidia Jetson CH340 驱动安装

最近调一个USB设备,Windows系统上是可以安装CH340 驱动使用的,移到Jetson AGX Orin 上发现没有 /dev/ttyUSB* 设备显示。是一个为盲人和视力障碍者提供的终端设备驱动程序,它会在系统启动时自动加载并占用串口设备,导致我们无法正常使用部分串口设备。安装完后,内核中已经有。

Vulcan_S的博客 1638

Android: 应用内升级APP

学习hyman的imooc《应用内升级App》的总结 Why do I need an in-app upgrade? apk上架市场周期慢。无法回退 可以小规模实验以及试错,新功能实验,稳定性检测 可以快速收敛版本,新功能覆盖,严重bug修复 App中存在的几种形式: 应用启动的时候静默检测,提示更新 用户手动在设置页面,点击检测更新 应用市场通过Notification 网络...

手写我对你的温柔 1176

联想ThinkServer SR588 重装raid5阵列信息(装完centos7后,找不到硬盘)

这里写自定义目录标题找不到硬盘选择光盘启动 找不到硬盘 装完centos7之后,提示重启,重启之后,就一直无法启动。 进入bios 看了,确实是有设置硬盘为第一启动选项。 选择光盘启动 本地标准硬盘也是找不到。 1、 重启进入bios在系统手设置里找到存储信息, 然后找到如图选项回车进入。 选择主目录进入。 进入Configuration Management 里面 先清除原来的设置信息 选择已启用在yes 提示清除成功。 返回上一目录选择,选择 Create Virtual Drive 再选择S

熠铭心时空的博客 2万+

【AWS】创建aws云服务器EC2实例

本文介绍了如何创建aws的EC2云服务器,并介绍了如何使用Xshell远程连接上云服务器

Marcos921的博客 2501

干货 | 携程Android 10适配踩坑指南

作者简介曙光,携程资深软件工程师,负责市场营销相关研发及管理工作。2019 年 9 月 3 日,Google 发布了 Android 10 正式版。Android 10 聚焦移动创新...

携程技术 7343

《open3D+pyqt》点云读取、显示与保存

《open3D+pyqt》第一章:点云读取与显示一、效果展示二、环境配置三、代码3.1 qt designer设置3.2 生成ui,qrc的py文件3.3 py文件代码 一、效果展示 二、环境配置 环境配置:Anconda+pycharm+pyqt+open3d+pyqtgraph 显示界面:这里选择pyqtgraph 参考网上教程 三、代码 3.1 qt designer设置 界面布局参照《PCL+QT》教程,界面设置:布局如下: 布局参数参考: 3.2 生成ui,qrc的py文件 3.3 py

阁楼 7113

Android10 适配

在Android 10 版本中,官方的改动较大,相应的开发者适配成本还是很高的。基于前期调研,我们主要基于以下几方面进行Android 10的适配: Android X 分区存储 设备ID 明文HTTP限制 一、AndroidX AndroidX 对原始 Android Support库进行了重大改进,后者现在已不再维护。AndroidX 软件包完全取代了支持库,不仅提供同等的功能,而且提供了新的库。 1.1 什么是AndroidX ...

xss66777学习记录 1205

【AI智能体】Langchain 主流大模型调用与对话API使用详解

【AI大模型】Langchain 模型调用与对话API使用详解

congge_study的博客 408

IT:银行类金融科技岗笔试习题集合—四个模块包括【综合知识+EPI+英语+个性测评】持续更新,建议收藏

IT:银行类金融科技岗笔试习题集合—四个模块包括【综合知识+EPI+英语+个性测评】 导读:行测、综合知识、英语和性格测试。综合知识,包括货币学、金融、会计、计算机等内容。  行政能力测试,内容大家可以参考公务员考试,包括逻辑推理、数字推理、图形推理、文字推理。英语测试,包括语法和词汇30题、完型填空20题、10篇阅读理解50题,阅读理解文章特别长,生词特别多。 (1)、英语可以Ctrl+F...

头部AI社区如有邀博主AI主题演讲请私信—心比天高,仗剑走天涯,保持热爱,奔赴向梦想!低调,专注,谦虚,自律,反思,成长,还算比较正能量的博主,公益免费传播…内心特别想在AI界做出一些可以推进历史进程影响力的技术(兴趣使然,有点小情怀,也有点使命感呀 2万+

适配Android高低版本自动更新apk安装工具:助力开发者实现流畅升级体验

适配Android高低版本自动更新apk安装工具:助力开发者实现流畅升级体验 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在移动应用开发中,确保用户能够及时更新到最新版本的App,对于提升用户体验和维护应用安全至关重要。今天,我将向您推荐一款功能强大的开源工具——适配Android高低版本自动更新apk安装工具。它专注于解决Android设备在不同系统版本下的自动更...

gitblog_06709的博客 1056

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Android版本兼容(存储、http,相机、SparseArray、升级)

版本兼容处理(后续我会一直迭代下去的,只要遇到了就更新,希望能帮到大家) Android 11 存储规范 Android 11 中的存储机制更新 Android 11(API 29),在Android 10设备操作文件无效,临时解决方案为在AndroidManifest.xml中application标签添加以下属性: <application android:allowBackup="true" android:icon="@drawable/logo" andro

lovelixue的博客 1697

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

全面适配 Android12

以上内容为流利说各 APP 适配 Android12 改动范围。Android12 本身改变内容还有很多,比如定位精度、应用休眠、前台服务、蓝牙权限等等。2022年8月 Android13 正式版发布,更细化的权限和更多后台运行限制, 总体上感觉 Google 花了很大精力在提升 Android 安全性(当然还有稳定性),作为开发者很高兴看到 Android 生态越来越健康, 流利说作为保护用户隐私典范企业一直积极响应政策走在规范行业前沿,为我们的用户提供安全可靠的服务。

Androiddddd的博客 2110

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

honor 9x android 10,【荣耀9X升级EMUI10指南】终于等到你,9XPro+EMUI10再战三年!

本帖最后由 不吃糖 于 2020-1-11 16:31 编辑普大喜奔,在荣耀9X系列小伙伴们的万众期待中,2020年1月10日,我们终于等来了【荣耀9X系列EMUI10.0+安卓10公测的开启】,注意:这次公测是不限名额哦!只要简单报名,就可以OTA在线推送最新的基于安卓10的EMUI10升级大包,期待的小伙伴们跟本糖一起来做吧!!!IMG_20200110_204210.jpg (2.08 MB...

weixin_34219634的博客 1万+

最新android版本叫什么,Android版本今年将大更新,迈入10.0时代,新特性曝光

原标题:Android版本今年将大更新,迈入10.0时代,新特性曝光时代不曾为谁停留,谷歌将在今年推出Android的重大版本更新,Android将迈入10.0时代,代号是Android Q。 谷歌是在2018年8月7日正式发布了Android 9.0,并命名为Android P(Pie)。目前已经有很多安卓手机,包括华为、小米最新的机型都已经搭载了Android9.0上市了。不过也还有更多的安卓...

weixin_33303537的博客 4002

Android各版本迭代信息集合

今天分享的面试题是:Android在版本迭代中,总会进行很多改动,那么你熟知各版本都改动了什么内容?又要怎么适配呢?

u010345983的博客 335

学习安卓APP开发,10年磨一剑,b4a/Android Studio

摘要:本文回顾了作者时隔十年重启Android开发的历程。2016年开发时,项目编译耗时300秒,升级i5-8600K电脑后缩短至10秒,却意外搁置了开发计划。如今重新学习遭遇SDK未下载的编译错误,最终在清晨6点完成首个FTP客户端APP。通过对比今昔开发环境的变化,展现了技术进步带来的效率提升,也道出了开发者面对技术迭代的挑战与坚持。

483

团结引擎 1.5.0 版本发布:Android App View 功能详解

团结引擎1.5.0推出AndroidAppView功能,支持原生安卓应用以2D/3D形式嵌入3D场景,实现多应用并行展示。开发者可通过编辑器配置应用参数,灵活调整分辨率、缩放比等设置,并支持自定义材质。该功能目前支持OpenGLES,Vulkan版本即将推出,适用于智能座舱等场景,提供快速入门指南和开发文档支持。

YF云飞的博客 1630

大型app Gradle升级踩坑经历

2、升级GradlePlugin之后,我们还发现插件都不能用了,如aspectj,以及自定义的一些插件,出现编译失败,或者编译成功但无法运行的情况,所以插件还有一个一个进行适配;由于在7.0以后,编译流程改变过于巨大,对插件的修改成本极高,且要求JDK11,所以我们折中选择了4.1.0+的版本进行适配升级,即能满足我们的要求,又能降低改造带来的风险;而以上几个不只是改版本号那么简单,由于Gradle和kotlin升级并不是100%向下兼容,可能很多业务上需要对语法进行适配,一些之前写的工具都要进行适配;..

ice3897315的博客 4723

从权限适配到API迁移:App Inventor对Android 34的深度技术适配解析

你是否在Android 34设备上遇到过App Inventor应用崩溃、权限失效或功能异常?随着Google对Android系统安全性和隐私保护的持续强化,API变更速度逐年加快,开发者面临着前所未有的适配挑战。本文将系统剖析MIT App Inventor项目如何应对Android 34(Android 14)带来的技术变革,从权限系统重构到组件兼容性优化,提供一份完整的技术迁移指南。读完本文

gitblog_07136的博客 647
上一篇: Android 将Base64转成Bitmap
下一篇: Android 如何适配平板
四块五毛六
博客等级 码龄9年 43粉丝 26原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值