安卓开发(3)—1— Activity
安卓开发(3)—1— Activity
3.1 Activity是什么:
在前面安卓概述中有提到,Activity是Android开发中的四大组件,所有在app里可以看到的东西都是Activity里面的,Activity主要是用来和用户直接进行交互的。
3.2 Activity的基本用法
最开始创建的HelloAndroid采用的是IDE自带的MainActivy,这次直接创建一个什么都没有的Android项目来方便学习:
其它的配置参考:https://www.cnblogs.com/Sna1lGo/p/14823681.html 该博客。
3.2.1 手动创建Activity
采用了Empty Activity创建项目后,app/src/main/java/包名/ 文件夹是空的目录:
所有就需要我们手动来添加Activity组件,右键包名,新建一个Empty Activity:
勾选Generate Layout File会自动为该Activity创建一个对应的布局文件。勾选Launcher Activity表示会为项目开启向下兼容旧版系统的模式,这个需要勾上不然兼容不了。这里为了学习,所以就不勾选Generate a Layout File ,自己手动创建布局文件。
在Android的项目中任何一个Activity文件都应该重写 onCreate函数,默认创建生成的onCreate函数很简单,就是调用了父类的onCreate函数而已。
3.2.2 创建和加载布局文件
Android程序讲究的是逻辑和视图分离,最好每一个Activity都有一个布局文件对应,布局文件就是资源文件里面用来显示内容的文件。
手动创建一个布局文件:右键app/src/main/res目录 选择New然后选择目录,先创建一个名叫layout的目录,然后再右键layout目录,创建一个layout xml File文件,将其按下图配置:
该layout文件可以查看多种格式,Code就是XML源代码模式,Split就是xml和可视化模式一样一半,Design就是可视化设计模式。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</LinearLayout>
现在通过XML代码模式添加一个Button控件给布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"
/>
</LinearLayout>
可以通过旁边的desigh看到预览视角,添加了一个名为Button 1的按钮,接下来讲一下这段xml代码:
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 1"
/>
第一个:android:id="@+id/button1"
@+id 这种用法比较少见,但是先不看加号 @id/button1不就是xml中引用资源的用法吗,只不过把string换成了id而已。
这里的@+id就是定义一个id资源的用法,如果要定义一个id资源就需要采用 @+id/id_name这种用法。
第二个:android:layout_width="match_parent"
这里指定了布局文件的宽度,match_parent表示和父类的宽度一样。(宽度也就是横着的长度)
第三个:android:layout_height="wrap_content"
指定了布局文件的高度,wrap_content表明和父类的高度一样。(高度也就是竖着的长度)
第四个:android:text="Button 1"
指定了布局文件的文字内容。
Activity文件创建了,layout资源布局文件也创建了,接下来要做的就是把Activity和layout布局文件结合在一起:
setContentView(R.layout.first_layout)
这里调用了一个setContentView函数,来给该Activity加载一个布局文件,而setContentView一般的调用是给它传一个布局文件的id。
其中安卓(1)里面讲了在Android项目中调用资源的方式:
可以看到这里定义了一个应用程序名字的字符串,有两种方式可以拿来引用它:
1:在代码里面通过R.string.app_name可以获得该字符串的引用
2:在X M L中通过 @string/app_name可以获得该字符串的引用
其中的string部分是可以替换的,如果用的是图片资源就可以替换成drawable,如果是应用图标就可以替换成mipmap,布局文件就可以替换成layout
这里因为我们在res资源文件中有定义layout下的first_layout文件,所以就可以直接调用了。
3.2.3 在AndroidManifest文件中注册
所有的Activity组件都需要在AndroidMainifest文件中注册才能生效,实际上这里通过前面的流程,FirstActivity已经被注册了:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld3">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld3">
<activity android:name=".FirstActivity"></activity>
</application>
</manifest>
当自动创建组件的时候Android Studio就会自动添加AndroidManifest.xml文件,来防止程序允许崩溃,也是AS的一种人性化。
在<activity>标签中,使用了android:name来指定具体注册哪一个Activity,那么这里的.FirstActivity是什么呢,其实就是com.example.helloworld3.FirstActivity的缩写,就是com.example.hellworld3这个项目下的包名的Activity组件的名字:
其实这也是因为在该AndroidManifest.xml文件中前面的包(package)指定了是com.example.helloworld3,所以后面才可以这样写。
这样注册了Activity后还不行,因为还需要指定主Activity,就好比在C语言中指定main函数一样。要指定主Activity直接在AndroidMainifest中的<activity>标签中添加<intent-filter>标签就好了,并在该intent-filter标签中添加两行代码:<action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER" /> 就好了。还可以添加android:label来指定Activity标题栏中的内容。需要注意的是,给主Activity指定label不仅会成为标题栏中的内容,还会成为启动器(launcher)中应用程序显示的内容。
修改后的AndroidManifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld3">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.HelloWorld3">
<activity android:name=".FirstActivity"
android:label="This is FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
这样该Activity就是这个程序的主Activity,点击打开APP时就是首先打开该Activity就和main函数一样,启动C程序首先启动main函数。
这样就实现了一个简单的Activity了:
总结如何创建Activity
Android项目讲究的是将逻辑和视图分离,所以要创建一个Activity不仅需要添加代码逻辑,还需要添加布局资源文,在 app/src/main/java/包名 目录下新建Activity代码逻辑,然后再在layout中添加Activity布局资源,最后再再AndroidManifest中检查是否有注册该Activity(一般都会自动注册)。要使用布局文件的什么内容就在在 app/src/main/java/包名 目录下新建Activity代码逻辑中使用,要让Activity展示什么内容就在Activity的资源布局文件的XML中修改。
3.2.4 在Activity中使用Toast
Toast是Android提供的一种提醒方式,用来在程序中给用户通知,在一段时间后会自动消失,且不会占用屏幕任何资源。
要创建Toast,首先需要定义一个弹出Toast的触发点,正好我们刚刚设计的Activity有一个button按钮,就让这个按钮的点击事件作为弹出Toast的出发点。在该Activity的onCreate中添加以下代码:
var button1: Button = findViewById(R.id.button1)
button1.setOnClickListener {
Toast.makeText(this,"You clicked Button 1",Toast.LENGTH_SHORT).show()
}
var button1: Button = findViewById(R.id.button1)
//通过findViewById该API函数来获取在布局文件中定义的元素
//这里通过前面在定义布局文件中定义的ID来引入获取得布局文件中的按钮的实例 button1就是对应的button实例
//findViewById函数返回的是一个继承自View的泛型对象,所以kotlin没有办法自动推导它是Button还是其它控件
//所以需要显示地将button1这个变量声明称Button类型。
button1.setOnClickListener {
Toast.makeText(this,"You clicked Button 1",Toast.LENGTH_SHORT).show()
}
//调用setOnClickListener()方法给这个button按钮注册一个监听器,当被OnClick(被点击)的时候就会启动
//该监听器中使用了Toast.makeText()函数,Toast刚刚介绍过了,就是在APP中弹出内容的一个类
//然后该makeText()函数就是给该Toast修改内容,然后.show()函数,就是展示该Toast
//makeText()中有三个参数,第一个参数要传的内容是一个context,由于Activity本身也是一个Context对象
//这里可以直接将该Activity传进去,第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长
//第三个参数有两个宏定义可以选:Toast.LENGTH_SHORT和Toast.LENGTH_LONG
3.2.5 在Activity中使用Menu
菜单这个内容也是一个很常见的内容。
首先在res目录下新建一个menu文件夹,来存放菜单资源。接着创建一个名为main的Menu源代码文件:
然后在该新建的main.xml菜单源文件中添加以下代码:
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
这里创建了两个菜单项,其中<item>这个标签是用来创建具体的某一个菜单项,然后通过android:id来给该菜单项指定唯一的一个标识符,通过android:title来给该菜单项指定名称。
然后,还需要在Activity中重写onCreateOptionsMenu()该函数,因为该函数涉及菜单和Activity的联系:
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main,menu);
return true
}
在讲解这段代码的时候,需要先讲解一个关于Kotlin语法糖的知识。Java Bean是一个很简单的类:
public class Book
{
private int pages;
public int getPages()
{
return pages;
}
public void setPages(int pages)
{
this.pages = pages
}
}
在Kotlin中调用该语法结构的Java代码时有一种非常简便的办法:
val book = Book()
book.pages = 500
val bookpage = book.pages
看起来是在瞎JB写,但是其实它是在后面自动调用了Book类的getPages和setPages函数。
这样类比到刚刚的Menu代码里面:
menuInflater.inflate(R.menu.main,menu)
//这里实际上就是调用了getMenuInflater()方法得到了menuInflater对象,再调用它的inflate方法
//inflate方法有两个参数:第一个参数表明通过哪一个Menu资源文件来创建菜单
//第二个参数用来指定菜单项添加到哪一个Menu对象中,这里使用该Activity中重写的onCreateOptionsMenu
//传入的menu参数,表示添加到该Activity默认的菜单中
//如果该函数返回的是true表明会将新建的菜单显示出来
//如果返回的是false表明不会将新建的菜单显示出来
当然光显示出来是不够的,还需要给菜单添加响应,这样才可以完美配合。在Activity中重写onOptionsItemSelected()方法:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId)
{
R.id.add_item -> Toast.makeText(this,"You Clicked Add",Toast.LENGTH_LONG).show()
R.id.remove_item -> Toast.makeText(this,"You Clicked Remove",Toast.LENGTH_LONG).show()
}
return true
}
这样生成的APP右上角就会有一个三个点的按钮,这个按钮就是菜单按钮,点开就是我们写的Add和Remove内容,单机菜单的内容,会弹出Toast消息。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when(item.itemId)
{
R.id.add_item -> Toast.makeText(this,"You Clicked Add",Toast.LENGTH_LONG).show()
R.id.remove_item -> Toast.makeText(this,"You Clicked Remove",Toast.LENGTH_LONG).show()
}
return true
}
这段代码,通过区分item的itemId来分辨是选择了菜单的哪一个选项,ItemId是前面在res资源文件中的menu中定义的菜单资源,然后就通过Toast来达到响应的提示内容。
3.2.6 销毁一个Activity
前面讲述了手动创建Activity,现在要学习怎么销毁一个Activity,调用一个内置的API,finish()就好了。
这里我们把button按键响应的按钮监听器代码改一下,调用一下一个API就好了:
这样再单击button1这个按钮,就会自动销毁Activity。
3.2.0 总结
Android项目讲究的是逻辑和视图分离,代码逻辑主要是在Activity中,视图则是在res资源中,通过在res资源中定义的一些标识符,可以直接在代码逻辑中使用来一一对应确定。
3.3 使用intent在Activity之间穿梭
一个完整的Android项目是会在多个Activity之间交换,这一节就是分析如何切换Activity。
3.3.1 使用显示Intent
新建一个Activity文件,这次勾选上Generate Layout File,自动配置布局文件,但不要勾选:
AS会自动帮我们生成一些必要文件:
只不过自动生成的layout文件有点看不懂:
将其替换为和第一个自己手动建立的Activity源代码类似的框架:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button 2"
/>
</LinearLayout>
该代码还是定义了一个Button按钮,只不过这次改名为Button2了,Activity的代码也自动帮我们生成了,暂时也不用改吧,然后还有创建Activity的最后一步,就是在AndroidManifest文件中注册该Activity(通常AS会自动帮忙注册:
这样一个新的Activity就创建完成了,其实还是挺简单的,按照流程来就好了。
这个新建的Activity并不是一个主Activity所以也没有必要添加intent-filter标签来修饰,那么如何来启动这个Activity呢,就要用到Android里面的一个新的概念:Intent。
Intent:是Android中的各种组件之间进行交互的一种重要方式,不仅可以用来指明动作,还可以用来传递数据。Intent一般用来启动Activity,启动Service以及发送广播等场景(这里先专注于启动Activity)。
Intent一般分为两种:显示Intent和隐式Intent。
先分析显示Intent:Intent有多个钩子函数的重载,其中一个是Intent(Context packageContext,Class<?>cls),第一个参数Context要求提供一个启动Activity的上下文,第二个参数class用于指定想要启动的目标Activity,通过这个构造函数就可以构建出Intent,那么如何启动这个intent呢,Activity类中提供了一个startActivity()方法专门用来启动Activity,它接受一个Intent参数,这里将构建好的intent传入到startActivity()函数中,就可以启动目标Activity了。
//修改FirstActivity中的button点击事件来实现:
var button1: Button = findViewById(R.id.button1)
button1.setOnClickListener {
val intent = Intent(this,SecondActivity::class.java)
startActivity(intent)
}
//这里首先新建了一个Intent对象,第一个参数传入this,也就是把FirstActivity作为上下文
//第二个参数传入了SecondActivity::class.java作为启动目标的activity
//在Kotlin中的SecondActivity::class.java就和java里面的SecondActivity.class的写法一样
//接下来再通过这个startActivity()来执行这个Intent就好了。
总结:建立一个intent来指定下一个Activity,然后再通过API调用Intent就可以跳转到下一个Activity。
3.3.2 隐式调用Intent
隐式调用比显示调用要麻烦很多,采用一些列的action和category等信息来调用,然后由系统去分析这个Intent,并找出合适的Activity来启动,系统会自动找出可以响应该隐士Intent的Activity来调用,通过在<active>标签下配置<intent-filter>的内容,可以指定当前的Activity能够响应的action和category:
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在这个<action>标签中我们指明了当前Activity中可以响应com.example.activitytest.ACTION_START这个action,而<category>这个标签中则附加了一些附加信息,更精确地指明了当前Activity能够响应的Intent中还可能带有的category,只有action和category中的内容同时匹配Intent中的内容时,这个Activity才能响应该Intent。
修改FirstActivity中按钮的点击事件:
var button1: Button = findViewById(R.id.button1)
button1.setOnClickListener {
val intent = Intent("com.example.activitytest.ACTION_START")
startActivity(intent)
}
这里使用了Intent的另一个构造函数,直接将action传了进来,而category有一个默认值:android.intent.category.DEFAULT,所以这里就可以不用传参了。每一个Intent中只能指定一个action,但能指定多个category,当这个Intent触发时,随之绑定的都会触发。
3.3.3 更多的隐式调用Intent用法
使用隐式Intent不仅可以启动自己程序内的Activity,还可以启动其它程序的Activity。
修改FirstActivity中按钮点击事件的代码:
var button1: Button = findViewById(R.id.button1)
button1.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.baidu.com")
startActivity(intent)
}
这里指定了Intent的action是Intent.ACTION_VIEW这是android系统内置的动作,它的常量值为android.intent.action.VIEW,然后通过Uri.parse()函数,将一个网址解析为uri对象,再调用intent的setData()函数将这个uri对象传递进去。最后的内置API:startActivity函数就是直接调用了这个intent对应的Activity。
这样再单击按钮button1就会直接进入系统内置的浏览器并且访问前面输入的url网站了。
还可以在AndroidManifest文件中的<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前Activity能够响应的数据。
<data>可以配置一下数据:只有当<data>标签中指定的内容和Intent中携带的Data内容完全一致时,才能响应对应的Intent。
android:scheme | 用于指定数据的协议部分,就好比前面的url的https这一部分 |
android:host | 用于指定数据的主机名部分:好比前面那个的www.baidu.com |
android:port | 用于指定数据的端口部分,一般紧随在主机名后面 |
android:path | 用于指定域名后面的部分 |
android:mimeType | 用于指定可以处理的数据类型,允许使用通配符的方式来指定。 |
比如前面的响应浏览器访问百度,如果把android:scheme指定为https那么就只能响应https协议的Intent了。
例如:这里我们新建一个Activity来响应网页的intent。
1 新建一个Activity叫ThirdActivity,再新建一个layout布局文件叫third_layout :
然后修改third_laytout文件内容为以下代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="button3"
/>
</LinearLayout>
ThirdActivity的代码可以不用更改,最后再在AndroidManifest.xml中修改ThirdActivty的注册信息:
代码讲解
这里首先用 action和category指定了intent的调用响应值,然后<data android:scheme中指定了响应的协议必须是https协议。
这样再启用后就会有选择了,是使用普通的chrome还是使用只能有https。
3.3.4 向下一个Activity传递数据
使用Intent启动Activity的时候还可以传递数据。
Intent中提供了一系列的putExtra()函数的重载,可以把想要传递的数据暂存在Intent中,然后在启动另一个Activity后,只需要将其从Intent数据中取出就可以了。
比如说:在FirstActivity中有一个字符串想传递给ThirdActivity中就可以这样写:
button1.setOnClickListener{
val data = "Hello ThirdActivity"
val intent = Intent(this,ThirdActivity::class.java)
intent.putExtra("extra_data",data)
startActivity(intent)
}
这段代码采用的显示传递intent,然后通过putExtra()来传递字符串,putExtra的第一个参数是健用于之后从Intent中取值,第二个参数才是真正要传递的数据。
然后再在ThirdActivity中将传递的数据取出来打印,代码如下:
class ThirdActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.third_layout)
val extraData = intent.getStringExtra("extra_data")
Log.d("ThirdActivity","extra data is $extraData")
}
}
安卓开发(3)—1— Activity的更多相关文章
- 安卓开发笔记——深入Activity
在上一篇文章<安卓开发笔记——重识Activity >中,我们了解了Activity生命周期的执行顺序和一些基本的数据保存操作,但如果只知道这些是对于我们的开发需求来说是远远不够的,今天我 ...
- 安卓开发入门之activity
安卓开发主要用到的是java语言,对于一个activity,自己写的程序可以继承至Activity,该Activity先会运行一个叫 onCreat()的类,可以在其中申明一些初始化的函数等,这个函数 ...
- 学习安卓开发[2] - 在Activity中托管Fragment
目录 在上一篇学习安卓开发[1]-程序结构.Activity生命周期及页面通信中,学习了Activity的一些基础应用,基于这些知识,可以构建一些简单的APP了,但这还远远不够,本节会学习如何使用Ac ...
- 安卓开发-intent在Activity之间数据传递
安卓开发-intent在Activity之间数据传递 [TOC] intent实现普通跳转 使用intent的setclass方法,示例(由此界面跳转到NewActivity界面) //使用setOn ...
- 安卓开发-Activity-多个Activity的开发方法。
原文链接:https://blog.csdn.net/weixin_38420342/article/details/84344496 一.切换Activity的5种方式 Intent intent ...
- Xamarin安卓开发:去掉Activity的头部标题栏及全屏显示
http://blog.csdn.net/u012234115/article/details/35814209 以下是用修改布局文件的方法,其实还有用C#代码的方法. 打开AndroidManife ...
- 学习安卓开发[3] - 使用RecyclerView显示列表
在上一篇学习安卓开发[2] - 在Activity中托管Fragment中了解了使用Fragment的好处和方法,本次记录的是在进行列表展示时RecyclerView的使用. RecyclerView ...
- 安卓开发30:AsyncTask的用法
http://blog.csdn.net/initphp/article/details/10392093 安卓开发笔记系列(43) 在开发Android应用时必须遵守单线程模型的原则: Andro ...
- 安卓开发笔记——关于照片墙的实现(完美缓存策略LruCache+DiskLruCache)
这几天一直研究在安卓开发中图片应该如何处理,在网上翻了好多资料,这里做点小总结,如果朋友们有更好的解决方案,可以留言一起交流下. 内存缓存技术 在我们开发程序中要在界面上加载一张图片是件非常容易的事情 ...
随机推荐
- JPEG头部解析
6.3 JPEG格式 6.3.1简介 微处理机中的存放顺序有正序(big endian)和逆序(little endian)之分.正序存放就是高字节存放在前低字节在后,而逆序存放就是低字 ...
- OO第三单元作业(JML)总结
OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...
- MySQL中MyISAM为什么比InnoDB查询快
大家都知道在MySQL中,MyISAM比InnoDB查询快,但很多人都不知道其中的原理. 今天我们就来聊聊其中的原理,另外也验证下是否MyISAM比InnoDB真的查询快. 在探索其中原理之前,我们先 ...
- 大数据开发-Flink-1.13新特性
介绍 大概4月,Flink1.13就发布了,参加 了Flink1.13 的Meetup,收获还是挺多,从大的方面讲就是FlingSql的改进和优化,资源调度管理方面的优化,以及流批一体Flink在运行 ...
- Linux中169.254.0.0/24的路由来自哪里
在Linux中,发现每次系统启动时,都会将(169.254.0.0/16)路由启动并将其添加到路由表中.但是并不知道这条路由具有什么功能和它到底来自于哪里? [root@master01 ~]# ro ...
- [bug] Flask:jinja2.exceptions.UndefinedError: 'None' has no attribute 'id'
问题 Python Flask做的购物网站,添加购物车时,提示错误 解决 检查发现是MySQL中不正常的空数据导致,删除此条记录即可 参考 https://www.jb51.cc/python/186 ...
- [Qt] 事件机制(四)
滚轮事件:滚动滚轮实现窗口大小缩放 widget.h中增加: protected: void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; void ...
- downloader middleware的三个methods不同返回的情况
要激活一个meddleware, 要在设置里面添加.例如: DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMidd ...
- Linux(CentOS7)下安装jdk1.8
Linux(CentOS7) 下安装 jdk1.8 操作过程. 一.检查是否自带jdk rpm -qa|grep java 如果存在则用下面命令删除,xxx yyy zzz代表查询出来的自带jdk名称 ...
- 如何彻底禁止 macOS Big Sur 自动更新,去除更新标记和通知
作者:gc(at)sysin.org,主页:www.sysin.org 请访问原文链接:https://sysin.org/article/Disable-macOS-Update/,查看最新版.原创 ...