【Android】app应用内版本更新升级(DownloadManager下载,适配Android6.0以上所有版本)

这篇具有很好参考价值的文章主要介绍了【Android】app应用内版本更新升级(DownloadManager下载,适配Android6.0以上所有版本)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

版本的升级和更新是一个线上App所必备的功能,App的升级安装包主要通过 应用商店 或者 应用内下载 两种方式获得,大部分app这两种方式都会具备,应用商店只需要上传对应平台审核通过即可,而应用内更新一般是通过以下几种方式:

1.集成第三方库如 appupdateX、bugly 的更新功能
2.手动实现

这里自己从网上找了一些资料,使用 Kotlin 结合自己的想法,完整地实现了一个应用内在线更新的功能,该功能使用 DownloadManager 下载安装包,适配 Android6.0 以上所有版本,现也已经成功应用到自己公司平台上了。如果这不能满足大家的高级需求,也能提供思路和方向,万变不离其宗,清晰的思路永远胜过简单的搬运,下面是具体实现:


一、实现思路

1、通过接口获取版本号和安装包下载地址:完美一点的是应该是解析出安装包里面的版本号
2、比较线上的版本和本地版本,弹出升级弹窗:可在这里设置强制更新,不更新退出
3、下载 APK 安装包:显示进度条,通过 DownloadManager 下载,同时会在手机顶部通知栏显示下载进度,也可通过三方框架(比如 Volley、OkHttp、IntentService )的文件下载功能
4、安装升级包:获取权限和不同版本适配

UI效果:

android app更新,Android,android,okhttp,kotlin,android jetpack,java

二、服务端接口

服务端需要提供一个接口,返回下载安装包地址、版本号等信息,Json字符串:

{
  "result": {
    "id": 1,
    "publishTime": "发布时间",
    "name": "app名称",
    "version": "版本号",
    "updateMessage": "更新内容:1.xxx \n 2.xxx",
    "downloadUrl": "下载地址(https://www...com/app名称v4.0.0.apk)"
  },
  "success": true,
  "error": null,
}

对应bean数据类:UpgradeResponse.kt

import androidx.annotation.Keep
/**
 * version: 4.0.0
 * publishTime: 当前时间
 * updateMessage: 1....\n 2....
 */
@Keep
data class UpgradeResponse(
    val id:Int,
    //更新日期
    val publishTime: String?,
    // app名字
    val name: String,

    //服务器版本
    val version: String,

    //app最新版本地址
    val downloadUrl: String,

    //升级信息
    val updateMessage: String?,
)

三、UI页面

主要添加版本号、发布时间、更新内容、进度条、操作按钮等内容,进度条是 Android 自带的控件,默认隐藏,在点击更新后,隐藏按钮,显示进度条,并动态更新进度。好看的样式都可以自己 DIY,例如找一些火箭发射的专用背景图。

弹窗:dialog_upgrade.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="280dp"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_dialog"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:orientation="vertical"
        android:background="@drawable/bg_dialog_top"
        android:paddingLeft="20dp"
        android:paddingTop="8dp"
        android:paddingRight="20dp"
        android:paddingBottom="8dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="发现新版本"
            android:textColor="@color/white"
            android:textSize="20sp" />


        <TextView
            android:id="@+id/tv_version"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:lineSpacingMultiplier="1.2"
            android:text="版本号:"
            android:textColor="@color/white"
            android:textSize="15sp" />
        <TextView
            android:id="@+id/tv_date"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"
            android:lineSpacingMultiplier="1.2"
            android:text="发布时间:"
            android:textColor="@color/white"
            android:textSize="15sp" />

    </LinearLayout>

    <TextView
        android:id="@+id/tv_feature"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxHeight="350dp"
        android:padding="20dp"
        android:text="版本特性:"
        android:textSize="15sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="0.6dp"
        android:background="@color/api_date_text_color_1"/>
    <LinearLayout
        android:id="@+id/fl_progress"
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:padding="10dp"
        android:visibility="gone"
        tools:visibility="visible">

        <!--android:max="100"-->
        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="210dp"
            android:layout_height="wrap_content"
            android:padding="@dimen/dp_10"
            android:value="0" />

        <TextView
            android:id="@+id/tv_progress"
            android:layout_width="45dp"
            android:gravity="center"
            android:layout_height="wrap_content"
            android:layout_marginStart="5dp"
            android:text="0%" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_actions"
        android:layout_width="280dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_cancel"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="20dp"
            android:gravity="center"
            android:text="下次再说"
            android:textSize="16dp" />

        <View
            android:layout_width="0.6dp"
            android:layout_height="match_parent"
            android:background="@color/api_date_text_color_1"/>

        <TextView
            android:id="@+id/tv_upgrade"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:padding="20dp"
            android:gravity="center"
            android:text="立即更新"
            android:textColor="#42cba6"
            android:textSize="16dp" />

    </LinearLayout>
</LinearLayout>

整体白色圆角背景:bg_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="6dp"/>
    <solid android:color="@color/api_white"/>
</shape>

上半部分绿色圆角背景:bg_dialog_top.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:topRightRadius="6dp" android:topLeftRadius="6dp"/>
    <solid android:color="#42cba6"/>
</shape>

三、工具类实现

封装一个用于版本更新的工具类 UpgradeUtil.kt ,单例设计,这时候就体现出了 Kotlin 的简便,只用一个 companion object {} 即可,包含操作方法,如果是 Java 引用 Kotlin 方法,方法前面需要加上 @JvmStatic 注解。

1.检查版本号

@JvmStatic //Java使用该方法
fun checkVersion(apkInfo:UpgradeResponse?) :Boolean{
	if (apkInfo == null) {
	    return false
	}
	//完美一点就是先判断包名是否一致,再判断版本号
	val oldVersion = AppVersionUtils.getVersionCode()//本地版本号
	//本地版本号 = BaseApplication.getContext().getPackageManager().getPackageInfo(BaseApplication.getContext().getPackageName(), PackageManager.GET_CONFIGURATIONS).versionCode
	
	val version=apkInfo.version.filter { it.isDigit() }.toInt()  //filter过滤器过滤字符,isDigit()只提取数字,防止其他字符混入
	if ( version > oldVersion) {
	    return true
	}
	return false
}

获取本地版本号的工具类(Java):

AppVersionUtils.java

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import com.auroral.api.BaseApplication;

public class AppVersionUtils {

    private static PackageInfo mPackageInfo;

    /**
     * get app version name.
     *
     * @return version name.
     */
    public static String getVersionName() {
        getPackageInfo();
        return mPackageInfo.versionName;
    }

    private static void getPackageInfo() {
        if (mPackageInfo == null) {
            try {
                mPackageInfo = BaseApplication.getContext().getPackageManager()
                        .getPackageInfo(BaseApplication.getContext().getPackageName(), PackageManager.GET_CONFIGURATIONS);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * get app version code.
     *
     * @return version code.
     */
    public static int getVersionCode() {
        getPackageInfo();
        return mPackageInfo.versionCode;
    }
}

---------------------2023年7月11日 更新--------------------------

BaseApplication类:

public class public class BaseApplication extends Application {
    private static Context context;
    private static MMKV mmkv;

    public static int statusBarHeight = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        MMKV.initialize(this);
        mmkv = MMKV.defaultMMKV();
    }

    public static Context getContext() {
        return context;
    }

    public static MMKV getMMKV() {
        return mmkv;
    }
} 

2.下载apk

DownloadManager 是Android系统自带的下载管理工具,可以很好地进行调度下载。 其下载任务会对应唯一个ID, 此id可以用来去查询下载内容的相关信息,获取下载进度。而跳转安装一般是通过 uri 跳转,uri 主要分为以下两类,两种类型都要考虑进去。

  • content://: 系统提供商的media、downloads,第三方的 fileprovider
  • file:// :旧式file类型的uri

在下载之前先判断是否已经下载过,下载过直接跳转,没下载过下载安装后删除下载任务和文件。主要流程:

1、判断是否下载过apk:下载过直接安装
2、DownloadManager配置
3、获取到下载id
4、动态更新下载进度
5、安装apk:两种uri

具体代码中介绍得很详细:

//下载id
private var downloadId=-1L

//下载apk
@JvmStatic
fun upgradeApk(context: Context, upgradeInfo: UpgradeResponse,view: View,dialog: Dialog){
	//设置apk下载地址:本机存储的download文件夹下
    val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    //找到该路径下的对应名称的apk文件,有可能已经下载过了
    val file = File(dir, "${upgradeInfo.name}v${upgradeInfo.version}.apk")
    //开辟线程
    MainScope().launch {
        val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
        // 1、判断是否下载过apk
        if (file.exists()) {
            val authority: String = context.applicationContext.packageName+ ".fileProvider"
            // "content://" 类型的uri   --将"file://"类型的uri变成"content://"类型的uri
            val uri = FileProvider.getUriForFile(context, authority, file)
            dialog.dismiss()
            // 5、安装apk, content://和file://路径都需要
            installAPK(context,uri,file)
        }else{
        	// 2、DownloadManager配置
            val request = DownloadManager.Request(Uri.parse(encodeGB( upgradeInfo.downloadUrl)))  //处理中文下载地址
            // 设置下载路径和下载的apk名称
            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "${upgradeInfo.name}v${upgradeInfo.version}.apk")
            // 下载时在通知栏内显示下载进度条
            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
            // 设置MIME类型,即设置下载文件的类型, 如果下载的是android文件, 设置为application/vnd.android.package-archive
            request.setMimeType("application/vnd.android.package-archive")
            // 3、获取到下载id
            downloadId = downloadManager.enqueue(request)
            
			// 隐藏按钮显示进度条
            view.ll_actions.visibility= View.GONE
            view.tv_progress.text = "0%"
            view.progressBar.progress = 0
            view.fl_progress.visibility = View.VISIBLE
            
            // 开辟IO线程
            MainScope().launch(Dispatchers.IO) {
            	// 4、动态更新下载进度
                val success = checkDownloadProgress(
                    downloadManager,
                    downloadId,
                    view.progressBar,
                    view.tv_progress,
                    file
                )
                MainScope().launch {
                    if (success) {
                    	// 下载文件"content://"类型的uri ,DownloadManager通过downloadId
                        val uri = downloadManager.getUriForDownloadedFile(downloadId)
                        // 通过downLoadId查询下载的apk文件转成"file://"类型的uri
                        val file= queryDownloadedApk(context, downloadId)
                        dialog.dismiss()
                        // 5、安装apk
                        installAPK(context, uri,file)
                    } else {
                        TastyToast.makeText(context, "下载失败",
                            TastyToast.LENGTH_SHORT, TastyToast.WARNING)
                        if (file.exists()) {
                            // 当不需要的时候,清除之前的下载文件,避免浪费用户空间
                            file.delete()
                        }
                        // 删除下载任务和文件
                        downloadManager.remove(downloadId)
                        // 隐藏进度条显示按钮,重新下载
                        view.fl_progress.visibility = View.GONE
                        view.ll_actions.visibility = View.VISIBLE
                    }
                    cancel()
                }
                cancel()
            }
        }
        cancel()
    }
}

中文路径可能导致乱码找不到下载路径,需要转成GB编码

//中文路径转成GB编码
fun encodeGB(string: String): String{
    //转换中文编码
    val split = string.split("/".toRegex()).toTypedArray()
    for (i in 1 until split.size) {
        try {
            split[i] = URLEncoder.encode(split[i], "GB2312")
        } catch (e: UnsupportedEncodingException) {
            e.printStackTrace()
        }
        split[0] = split[0] + "/" + split[i]
    }
    split[0] = split[0].replace("\\+".toRegex(), "%20") //处理空格
    return split[0]
}

3.安装apk

跳转安装 apk 需要适配不同的安卓版本,Android 6.0-7.0 需要老式的 “file://” 的路径,Android 7.0 以上需要 “content://” 的路径

//调用系统安装apk
private fun installAPK(context: Context, apkUri: Uri,apkFile: File?) {
    val intent = Intent()
  
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    	//安卓7.0版本以上安装
        intent.action = Intent.ACTION_VIEW
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        intent.setDataAndType(apkUri, "application/vnd.android.package-archive")
    } else {
    	//安卓6.0-7.0版本安装
        intent.action = Intent.ACTION_DEFAULT
        intent.addCategory(Intent.CATEGORY_DEFAULT)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        apkFile?.let {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive")
        }
    }
    try {
        context.startActivity(intent)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

通过 downloadId 获取到 “file://” 的路径

private fun queryDownloadedApk(context: Context, downloadId: Long): File? {
    var targetApkFile: File? = null
    val downloader = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
    if (downloadId != -1L) {
        val query = DownloadManager.Query()
        query.setFilterById(downloadId)
        query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL)
        val cur: Cursor? = downloader.query(query)
        if (cur != null) {
            if (cur.moveToFirst()) {
                val uriString: String =
                    cur.getString(cur.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
                if (!TextUtils.isEmpty(uriString)) {
                    targetApkFile = Uri.parse(uriString).path?.let { File(it) }
                }
            }
            cur.close()
        }
    }
    return targetApkFile
}

4.实时更新下载进度

在线程中使用的方法需要带表示 suspend 挂起函数的关键字,通过while循环去读取,监控任务的状态,待状态变成Fail或Success

//检查下载进度
suspend fun checkDownloadProgress(
    manager: DownloadManager,
    downloadId: Long,
    progressBar: ProgressBar,
    progressText: TextView,
    file: File
): Boolean {
    //循环检查,直到状态变成Fail或Success
    while (true) {
        val q = DownloadManager.Query()
        q.setFilterById(downloadId)
        val cursor = manager.query(q)
        if(cursor.moveToFirst()){
            val bytes_downloaded =
                cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
            val bytes_total =
                cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
            val dl_progress = (bytes_downloaded * 100 / bytes_total).toInt()
            progressBar.post {
                progressBar.progress = dl_progress
                progressText.text = "${dl_progress}%"
            }
            when (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))) {
                DownloadManager.STATUS_SUCCESSFUL -> {
                    return true
                }
                DownloadManager.STATUS_RUNNING, DownloadManager.STATUS_PENDING -> {
                    delay(500)
                }
                else -> {
                    if (file.exists()) {
                        //当不需要的时候,清除之前的下载文件,避免浪费用户空间
                        file.delete()
                    }
                    manager.remove(downloadId)
                    return false
                }
            }
        }else{
            if (file.exists()) {
                //当不需要的时候,清除之前的下载文件,避免浪费用户空间
                file.delete()
            }
            manager.remove(downloadId)
            return false
        }

    }
}

5.完整代码

import android.app.Dialog
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.text.TextUtils
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.FileProvider
import com.auroral.api.utils.AppVersionUtils
import com.sdsmdg.tastytoast.TastyToast
import com.vickn.main.upgrade.bean.UpgradeResponse
import kotlinx.android.synthetic.main.dialog_upgrade.view.*
import kotlinx.coroutines.*
import java.io.File
import java.io.UnsupportedEncodingException
import java.net.URLEncoder

class UpgradeUtil {

    companion object {
    	private var downloadId=-1L
    	
    	// 上述各类方法
    	// ...
	}
}

三、外部使用

最后通过网络接口获取到数据后进行版本判断,显示弹窗。接口请求数据和数据监听这里就不列出来了,其次,下载之前必须先对权限进行检查或获取

具体使用:MainActivity中

private val upgradeDialog by lazy{ Dialog(this, R.style.xxx) } //最原生的Dialog, 对应风格样式
private val view by lazy { LayoutInflater.from(this).inflate(R.layout.dialog_upgrade, null) }

val isNewVersion = checkVersion(data, this@MainActivity)
if (isNewVersion) {
    showUpgradeDialog(data)
}

//显示版本更新弹窗
private fun showUpgradeDialog(upgradeInfo: UpgradeResponse) {
    view.tv_version.text = "版本号:${upgradeInfo.version}"
    upgradeInfo.publishTime?.let {
        val index = TextUtils.lastIndexOf(it, ':')
        val date = it.substring(0, index).replace("T", " ")
        view.tv_date.text = "发布时间:$date"
    }
    //当文本被封装到一个类中的某个属性时在传递时会在所有转义字符前加一个"\",例如"\n"变成"\\n"
    view.tv_feature.text = "版本特性:\n\n${upgradeInfo.updateMessage}".replace("\\n", "\n")
    view.tv_cancel.setOnClickListener { upgradeDialog.dismiss() }
    //点击更新
    view.tv_upgrade.setOnClickListener { //权限申请
        AndPermission.with(this@MainActivity)
            .runtime()
            .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            .rationale{ context, data, executor ->
                //显示权限获取的弹窗
                //.....
            }
            .onDenied{
                TastyToast.makeText(
                    this@MainActivity,
                    "未获得存储权限,无法下载", TastyToast.LENGTH_SHORT, TastyToast.ERROR
                )
            }
            .onGranted{
                upgradeApk(this@MainActivity, upgradeInfo, view, upgradeDialog)
            }
            .start()
    }
    upgradeDialog.setContentView(view)
    upgradeDialog.setCancelable(false)
    upgradeDialog.show()
}

权限获取使用的是 com.yanzhenjie.permission.AndPermission 的开源第三方包,获取权限的弹窗自己添加。


总结

到这里就全部结束了,不容易呀😭这算是自己稍微有点技术含量的功能吧,毕竟能拿得出手的不多😂,这必然也会有一些设计上的缺陷和冗余,但无伤大雅。

有问题的也可以在下面评论,我看到都会回复的。文章来源地址https://www.toymoban.com/news/detail-673385.html

到了这里,关于【Android】app应用内版本更新升级(DownloadManager下载,适配Android6.0以上所有版本)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android下载apk并安装apk(用于软件版本升级用途)

    软件版本更新是每个应用必不可少的功能,基本实现方案是请求服务器最新的版本号与本地的版本号对比,有新版本则下载apk并执行安装。请求服务器版本号与本地对比很容易,本文就不过多讲解,主要讲解下载apk到安装apk的内容。 (1)读写外部存储的权限需要动态申请,

    2024年02月01日
    浏览(88)
  • Android & iOS - Android Studio/Xcode历史版本下载(持续更新)

    最近升级开发工具老是遇到各种兼容性问题导致需要降回老版本,Xcode历史版本下载方便倒还好,Android Studio就麻烦了,一开始找到的官方历史版本下载还不全。这里整理一些历史版本信息方便下载,后续会持续更新。 历史版本数据来源Android Studio官方(语言必须选English,不

    2024年02月16日
    浏览(56)
  • 2-远程升级篇(自建物联网平台)-STM32通过EC800使用http或https下载程序文件升级程序(APP通过MQTT控制单片机更新)

    1,用户在APP上点击 \\\"固件升级\\\" 按钮, APP使用MQTT发送:  {\\\"data\\\":\\\"updata\\\",\\\"cmd\\\":\\\"DeviceInfo\\\"} //询问设备固件信息 2,设备通过MQTT收到该消息以后,发送 {\\\"data\\\":\\\"updata\\\",\\\"cmd\\\":\\\"DeviceInfo\\\",\\\"DeviceModel\\\":\\\"STM32EC800BKAPP\\\",\\\"FirmwareVersion\\\":\\\"0.0.0\\\"} (STM32EC800BKAPP是设备的型号;  0.0.0是设备当前的固件版本) 3,APP收到

    2024年04月16日
    浏览(161)
  • iOS和Android手机浏览器链接打开app store或应用市场下载软件

    1.Android主流手机跳转链接 2.判断手机类型 3.iOS跳转app store

    2024年02月11日
    浏览(100)
  • iOS和Android手机浏览器链接打开app store或应用市场下载软件讲解

    当开发一个app出来后,通过分享引流用户去打开/下载该app软件,不同手机下载的地方不一样,比如:ios需要到苹果商店去下载,Android手机需要到各个不同的应用商店去下载(华为手机需要到华为应用商店下载,vivo手机需要到vivo手机的应用商店下载,oppo需到oppo的应用商店去下

    2024年02月13日
    浏览(137)
  • android_adb pm和adb am@启动Activity@杀死app进程@冻结或卸载系统更新和应用商店

    Android 调试桥 (adb) | Android 开发者 | Android Developers (google.cn) 这部分文档并不包含所有的指令功能的介绍 可以和命令行的 --help 选项的本地文档一同查阅 运行方式有两种 不进入shell直接运行 每次执行命令(issue command)需要带上adb shell开头的前缀 例如: adb shell pm list packages -s -d|nl

    2024年02月03日
    浏览(61)
  • uniapp 实战 -- app 的自动升级更新(含生成 app 发布页)

    uniapp 提供了 App升级中心 uni-upgrade-center ,可以便捷实现app 的自动升级更新,具体编码和配置如下: https://ext.dcloud.net.cn/plugin?id=4542 (不要配在第一项) pages/index/index.vue https://blog.csdn.net/weixin_41192489/article/details/135551800 本范例中,版本配置为1.0.1版 详见 https://blog.csdn.net/weixi

    2024年02月02日
    浏览(61)
  • 华硕主板升级更新BIOS版本

    1、查找主板型号 华硕官网https://www.asus.com.cn/support/Download-Center 2、下载BIOS版本 点击下载,解压拷贝到U盘 开机按F2或者Delete按键进入BISO界面,查看到当前主板型号是PRIME B250M-PLUS,BIOS版本 BIOS Ver.1010 按F7进入高级模式,点击“工具”,点击“华硕升级BIOS 应用程序3” 这里选择

    2024年02月06日
    浏览(55)
  • uniapp小程序 如何更新版本 在页面提示升级版本

    在APP.vue页面内,在onLaunch生命周期里面调用 //版本更新提示

    2024年02月16日
    浏览(46)
  • Android Studio 更新升级方法

    Android Studio 更新升级方法 Android Studio 是一款用于开发 Android 应用程序的集成开发环境(IDE)。由于技术的不断发展和改进,Google 会定期发布新版本的 Android Studio,其中包含更多的功能和修复了先前版本中的一些 bug。因此,及时更新和升级 Android Studio 是非常重要的,以确保开

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包