本文记录Android 第一行代码(第2版)的读书笔记,同时也会记录学习中的其他非本书知识.基本按照章节分段,方便其他使用
第一章 初识安卓
安卓简介
安卓系统有四大组件: 活动(Activity),服务(Service),广播接收器(Broadcast Receiver),内容提供器(Content Provider)
这四个东西还是挺熟悉的,以前折腾安卓手机,控制唤醒就是在控制应用的这四个东西.
开发环境搭建
参看上一篇搭建时遇到的问题以及解决方案\
AS目录结构
默认结构为AS转换过的,实际目录结构应该是和eclios差不多的,将目录结构切换为Project,这样的目录结构就是项目实际目录了.
大体目录结构
- .gradle和.idea
本目录下是AS自动生成文件,无需关心,不用编辑. - app
项目的代码,资源几乎都在这里,开发工作基本在这里进行 - build
包含编译时自动生成的文件 - gradle
包含gradle wrapper的配置文件 - .gitignore
这个文件是将指定目录或文件排除在版本控制之外的. - build.gradle
这个文件是醒目全局的gradle构建脚本 - gradle.properties
全局gradle配置文件 - gradlew和gradlew.bat
用来在命令行界面执行gradle命令 - Helloworld.iml
iml文件是所有intelij IDEA项目都会自动生成的一个文件(Android Studio是基于IntelliJIDEA开发的)用于标识这是一个IntelliJIDEA项目 - local.properties
这个文件用于指定本机中的Android SDK路径 ,自动生成,除非项目开发中sdk发生了更改才需要手动更改 - settings.gradle
这个文件用于指定项目中所有引入的模块.
app下的目录分析
实际上除了app目录外,大多数的文件都是自动生成的,app才是工作的重点
- build
和外层build相似 - libs
项目中的第三方jar包,都在本目录下,本目录下的jar包都会被自动添加到构建路径里. - androidTest
用于编写android test测试用例,可以对项目做一些自动化测试 - main下java
放置所有java代码的目录 - main下res
所有资源文件放置这里,图片,布局,字符串等资源都放在这里,每种资源放置在不同目录下- drawable开头用来放图片
- mipmap开头放应用图标
- values开头放字符串,样式,颜色等配置
- layout文件夹用来放布局文件
- androidmanifes.xml
整个项目的配置文件,程序中定义的所有四大组建都在本文件内注册,还可以给程序添加权限声明. - test
用来编写unit test测试用例. - .gitignore
和外层.gitignore作用类似 - app.iml
IntelliJIDEA自动生成文件 - build.gradle
app模块的gradle构建脚本. - proguard-rules.pro
指定代码混淆规则
HelloWorld程序简单分析
逻辑和视图分离
先查看java下的mainactivity1
2
3
4
5
6
7
8public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
mainActivity这个活动中实际上是没有”HelloWorld”字符的,android程序设计讲究逻辑和视图分离的,因此活动中一般不会直接写界面.
onCreate方法的第二行,使用setContentView给这个活动设置了一个布局activity_main,我们用ctrl进入这个文件可以看到文件位于layout/activity_main.xml
文件切换为text视图,可以看
到有1
android:text="Hello World!"
所以本程序中的文字是在这个文件内定义的.
res下内容引用
打开values/strings.xml1
2
3<resources>
<string name="app_name">HelloWorld3</string>
</resources>
这里定义了”app_name”的字符串,而我们再打开androidmanifest.xml1
2
3
4
5
6
7<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/AppTheme">
这里android:label="@string/app_name"
对于app名称做了引用.
我们引用这些资源时有两种方式
- 在代码中通过R.string.name获得该字符串的引用
- 在XML中通过@string/name获得该字符串的引用
基本语法是上面的这两种形式,其中string是可以被替换的,例如引用图片资源替换为drawable,引用图标变为mupmap,布局替换为layout,以此类推.
lable也是可以替换的,对应后面的string.
详解build.gradle文件
AS采用Gradle来构建项目,build.gradle一个在外层一个在app目录下.
外层
两处repositories闭包中申明了jcenter():这是一个代码托管仓库,声明后就可以在醒目中引用jcenter上的开源项目.
dependencies闭包中用classpath声明了一个Gradle插件,要想使用Gradle构建安卓项目就必须声明这个插件.(Gradle可以支持多种项目构建java,c++等都有专门的插件)
通常不需要修改外层build.gradle,除非需要修改这个,除非像添加一些全局的项目构建配置
app下的
1 | apply plugin: 'com.android.application' //第一行 |
应用了一个插件com.android.application
,代表这是一个应用程序模块,
还有一个值可选com.android.library
,代表这是一个库模块.
这两个模块区别为,一个可以直接运行,一个只能作为代码库依附于别的应用程序模块运行.
之后的android闭包,可以配置项目构建的各种属性.
compileSdkVersion 指定编译版本,这里28是8.1系统的sdk
android闭包内嵌套了个defaultConfig闭包
applicationID用于指定项目包名,之前创建项目已经指定过包名,如果要修改就在这里修改.
minSdkVersion 用于指定项目最低兼容的android系统版本
targetSdkVersion 指定的值表示已经在该版本上做了充分的测试,系统将会为这个应用启动吸血最新的特性和功能.
versionCode用于指定项目版本号
versionName用于指定醒目的版本名
buildTypes闭包,指定当前项目所有的依赖关系.
通常as项目有三种依赖:
本地依赖,对本地的jar包或者目录添加依赖关系
库依赖,对项目中的库模块添加依赖关系
远程依赖,对jcenter库上的开源项目添加依赖关系.
buildTypes闭包第一行implementation fileTree
就是本地声明,他的参数dir: 'libs', include: ['*.jar']
将libs下所有.jar
后缀的文件都添加到项目的构建路径当中.
第二行是远程依赖声明,参数com.android.support:appcompat-v7:28.0.0-rc02
是远程依赖库格式,com.android.support
是域名部分,appcompat-v7
是组名,28.0.0-rc02
是版本号.加入这局声明后,Gradle构建项目时会首先检查本地时候又这个库的缓存,没有就会自动下载.
库依赖声明这里(HelloWorld程序)没有基本格式为,implementation project
后面加上要依赖的库名称.
后面的testImplementation
是用于声明测试用例的库,忽略
使用日志工具Log
简介
这个日志工具类是Log(android.util.Log)
,在这个类中提供五个方法打印日志.
- Log.v() 用于打印琐碎的日志.对应级别为verbose,是级别最低的一种
- Log.d() 打印调试信息.对应级别debug.比verbose高一级
- Log.i() 打印比较重要的数据,这些是可以分析用户行为的数据.对应级别info,比debug高一级.
- Log.w() 打印警告信息,提示程序在这个地方可能会存在风险,最好去修复下警告出现的地方.对应级别warn,比info高一级
- Log.e() 用于打印程序错误信息,当有错误信息打印时代表程序出现严重问题必须修复.对应级别error比warn高一级.
总共五个方法可以被重载.
习惯使用Log
Log可以发挥和System.out相同的作用,而且拥有分级,过滤器….很多优点.
Log可以使用快捷输入:如Log.d
就可以使用logd
然后按下TAB.
另外,由于Log的所有打印方法都要求传入一个tag参数,我们可以在onCreate()
方法外部输入一个logt
,按下TAB,就会以当前的类名自动生成一个TAG常量,之后使用快捷方法生产Log()就不用再输入tag参数了.
Logcat中的过滤器
在logcat工具中可以添加过滤器
- show only selected aplication 只显示当前选中程序的日志(默认)
- Firebase 谷歌提供的分析工具
- No Filters 没有过滤器
- Edit Filter Configuration 自定义过滤器
自定义过滤器中Fiter Name是过滤器的名称,Log Tag是过滤器tag的值,例如我们Log Tag的值为data
那么我们选定这个自定义过滤器后logcat就只会显示tag值为data
的日志.后面的Log Message就对应的msg参数….之后都是同理
Logcat中的日志级别
就是上文说的五个方法作为五个级别
当选中级别为verbose(最低等级),这意味这不管是用什么方法打印日志一定会显示,如使用Log.d()
也会显示打印;当选中debug级别时,只显示debug及以上的日志,例如使用Log.v
就不会显示打印.
关键字过滤
Logcat中间的搜索框就是关键字过滤,全日制过滤你想要的关键字,支持正则表达式.
第二章 活动
活动的基本用法
手动创建活动
右击Project下java下的com.example.activitytest包→new→activity-Empty Activity,弹出创建活动的对话,名称我们创建为FirstActivity
会话中Generate Layout File
代表勾选后自动为这个活动创建一个对应的布局文件.Launcher Activity
代表勾选后会将这个活动设置为主活动.
为了更好的学习活动,这两个都不勾选.Backwards Compatibility
勾选后表示会为项目启用向下兼容模式,勾选.
点击Finish完成创建.
这是一个几乎空的活动,但是里面仍然有onCreate()
方法,因为任何活动中都应该重写这个方法,所以AS自动帮我们完成了.
1 | @Override |
小知识点:@Override
这个声明告诉编译器下面的方法是重写而不是一个新的方法,如果我们声明了一个洗的方法,编译器就会报错.
可以看到这个方法非常简单.
创建和加载布局
android设计讲究逻辑视图分离,最好每一个活动对应一个布局,布局用来显示界面内容
创建布局
右击app/src/main/res目录-New-Directory,弹出新建目录窗口,这里创建一个layout目录,对layout右键-new-Layout resource file,出现新建布局资源文件的窗口.命名为first_layout,root element(根元素)
默认选择LinearLayout
.
点击ok就进入了布局编辑器(Design/Text)默认是Design可视化视图,点击text切换到文本视图.并且创建一个按钮
1 | <?xml version="1.0" encoding="utf-8"?> |
android:id
是定义当前元素的标识符,它的参数@+id/button_1
上文说到@string/name
是在xml中引用string资源的语法
这里@+id/name
是定义id的语法
我们可以看到右侧的preview已经显示按钮了
活动中加载布局
回到FirstActivity,在onCreate()
方法中加入代码1
2
3
4
5
6
7
8public class FirstActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout); //加入的代码
}
}
这里调用了setContentView()
方法,R.layout.first_layout
是一个在代码中对资源的引用.
项目中添加任何资源都会在R文件中生成一个相应的资源id,使用R.layout.first_layout
引用就会得到一个first_layout.xml
布局的id,这个id传入了setContentView()
注册活动
所有活动要在AndroidManifest.xml
中注册才生效,我们使用右键new活动的时候,这个活动已经被AS注册了,如果手动创建一个活动就需要自己去注册.
打开AndroidManifest.xml
查看1
2
3
4
5
6
7
8
9
10
11
12
13
14<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.elsewhere997.activitytest">
<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/AppTheme">
<activity android:name=".FirstActivity"></activity> //注册的活动
</application>
</manifest>
活动注册声明要放在application标签内,活动通过activity标签注册,其中android:name
用来指定注册哪个活动,后面的.FirstActivity
是com.example.activetytest.FirstActivity
的缩写,因为这个xml文档的最外层标签已经有了这样的代码package="com.example.elsewhere997.activitytest
这通过package属性指定了程序包名com.example.elsewhere997.activitytest
所以前半部分可以不写,直接写缩写了.
主活动
主活动是程序运行时最先启动的活动,程序必须有主活动才能运行,所以必须配置一个主活动.
在<activity>
标签内部加入一个<intent-filter>
标签并在这个标签的内部添加<action android:name="android.intent.action.MAIN" />
和<category android:name="android.intent.category.LAUNCHER" />
这两句声明.
此外在<activity>
标签头内还可以加入android:label
用来指定活动中标题栏的内容,这个内容还会成为启动器中程序显示的名称.
活动中的Toast
定义一个Toast触发点,直接使用FirstActivity
这个活动的按钮作为出发点,点击后弹出Toast.在onCreate()
中添加如下代码(自己导入相关类):1
2
3
4
5
6
7Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(FirstActivity.this, "You clicked Button 1", Toast.LENGTH_LONG).show();
}
});
活动中通过findViewById()
获取布局中定义的元素返回一个对象,R.id.button_1
我们已经很熟悉了,其中button_1
是我们再first_lay_out.xml
中通过android:id
定义了的.
这个方法返回一个view对象,我们通过(Button)
向下强转为button对象来得到按钮的实例,然后 通过声明的button1来调用setOnClickListener()
方法为按钮注册一个监听器.
点击按钮就会执行OnClickListener()
监听器中的Onclick()
方法
Tosat方法,通过静态方法makeText()
创建Toast对象,其中三个参数,第一个是上下文(Context),活动就是一个上下文(Context)对象,直接传入FirstActivity.this
;第二个是文本内容;第三个是显示时常.然后调用show()显示
活动中的Menu
创建布局
在res目录下新建一个menu
文件夹,再在menu下新建一个名为main
的菜单文件(Menu resource file),创建出了一个main.xml
在<menu>
标签加入如下代码:1
2
3
4
5
6<item
android:id="@+id/add_item"
android:title="Add" />
<item
android:id="@id/remove_item"
android:title="Remove" />
这里我们创建了两个菜单项,<item>
标签用来创建菜单项,其中,我们通过android:id
来指定这个菜单项的唯一标识符,通过android:title
给菜单项指定一个名称.
显示menu
接着在活动中重写onCreateOptionsMenu
方法1
2
3
4
5@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
通过getMenuInflater()
获得MenuInflater对象,调用它的inflate()
方法创建当前活动的菜单,inflate()
有两个参数,一个指定通过哪个资源文件创建菜单,这个参数值的形式我们已经很熟悉了,另一个是指定菜单添加到哪个Menu对象中,这个值直接由外部的onCreateOptionsMenu(Menu menu)
方法传入所以我们直接填入menu
onCreateOptionsMenu(Menu menu)
方法返回值为布尔型.返回ture代表菜单允许显示,false表示创建的菜单无法显示.
添加菜单响应事件
依然是活动中重写方法,重写onOptionsItemSelected()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this,"You clicked add", Toast.LENGTH_LONG).show();
break;
case R.id.remove_item:
Toast.makeText(this, "clicked remove", Toast.LENGTH_LONG).show();
break;
default:
}
return true;
}
通过调用item.getItemId()
判断点击的是哪一个菜单项(传入了一个菜单项的id),然后在下面的case中判断,执行case下的语句
销毁活动
在安卓设备中可以通过back键销毁当前活动.
通过代码的方式:
调用活动类的finish()
方法,销毁当前活动.
修改按钮监听器中的代码,把Toast显示代码改成销毁活动的方法,这个按钮就和back按键的功能相同了
活动间的跳转
创建一个新的活动SecondActivity
这次创建时勾选Generate Layout File
系统会自动创建一个名为activity_second.xml
的布局文件,布局文件过于复杂我们修改其中代码,使用之前的<LinearLayout>
1 | <?xml version="1.0" encoding="utf-8"?> |
上述代码中还定义了一个按钮id为button_2.
我们回到活动去加载这个布局,但是惊奇的发现!这个布局文件已经被AS在活动中加载了.
我们同样没有忘记在AndroidManifest.xml
中注册活动,又惊奇的发现后动已经被注册好了.
使用显示Intent
Intent是安卓程序中各个组件之间进行交互的重要方式,它致命当前组件想要执行的动作,还可以在不同组件之间传递数据.一般可用于启动活动,启动服务,发送广播.这里学习启动活动.
Intent分为显示和隐式,这里学习显示
首先构建出了一个Intent
传入了FirstActivity.this
作为上下文,传入SecondActivity.class
作为目标活动,这个Intent
的意思就是在FirstActivity
这个活动的基础上打开目标活动.
然后把intent
传入startActivity()
方法,执行Intent
启动活动
这时运行程序,点击firstactivity的按钮就打开了第二个活动.
隐式Intent
隐式活动不明确指出要启动哪一个活动,而是指定了一些列抽象的action
和category
等信息,然后由系统去分析这个Intent,并帮我们找出适合的活动去启动.
通过在<activity>
标签下配置<intent-filter>
的内容,可以指定当前活动能响应的action和categroy.
我们再AndroidManifest.xml
的SecondActivity的注册activity标签中添加如下代码
1 | <intent-filter> |
在<action>
标签中我们指明了当前活动可以响应ACTION_START
这个action,而<category>
包含一些附加信息,更精确的指明了当前的活动能够响应的Intent中还可能带有的category
.只有<action>
和<category>
中的内容能够同时匹配上Intent中指定的action和category时,这个活动才能响应该Inent.
现在我们为Intent添加匹配条件,其他部分和显示是相同的
代码如下:
1 | public void onClick(View v){ |
我们使用了Intent的另一个构造函数Intent(action)
,直接把action的字符串"com.example.activitytest.ACTION_START"
传了进去,表明这个Intent能够响应"com.example.activitytest.ACTION_START"
这个action的活动.
action和category同时匹配才能响应,但是这个Intent没有指明category.但是前面SecondActivity
注册的<action>
标签中指明了category的信息"android.intent.category.DEFAULT"
这是一种默认的category
,在调用startActivity()
方法时这个category会自动添加到Intent中.
每个Intent中只能指定一个action,但能够指定多个category
现在添加一个category
修改FirstActivity中的点击事件
1 | public void onClick(View v){ |
添加了一个,调用intent中的addCategory()方法来添加一个category,值为"com.example.activitytest.MY_CATEGORY"
直接运行程序,点击主活动的按钮,程序闪退了,Logcat也报错了.intent匹配不到对应的活动当然会出问题,但是是什么问题了,现在看看Logcat
1 | Process: com.example.elsewhere997.activitytest, PID: 24662 |
上述错误信息意思是没有一个活动可以响应我们的Intent,和我们的推测一致.
修复bug,我们再添加一个可以响应category的声明就好了,回到AndroidManifest,为SecondActivity添加一个.
1 | <intent-filter> |
再次运行点击按钮,成功了
只要intent里的要求目标活动都能达到那么就能调用这个活动.所以在<intent-filter>
添加几个其他的category一样可以运行.
更多隐式Intent用法
使用隐式Intent我们还能够启动其他程序的活动.
修改FirstActivity中按钮点击事件的代码
1 | @Override |
这里首先指定了Intent的action为Intent.ACTION_VIEW
这是一个安卓系统内置的动作,其常量值为android.Intent.action.VIEW
.
然后通过Uri.parse()
方法,把一个网址字符串解析为Uri对象再调用Intent的setData()
方法把Uri对象传递进去.
这个setData()
没有讲过,它接收一个Uri对象,用于指定当前Intent正在操作的数据,这些数据通常以字符串的形式传入到Uri.parse()
方法中解析为Uri对象.
与此对应的我们还可以在<intent-filter>
标签中再配置一个<data>
标签用来指定当前活动能够响应什么类型的数据.<data>
标签主要可以配置以下内容.
除了http协议外,还能指定许多其他协议,比如geo表示地理位置,tel表示电话….
下面展示如何调用系统拨号界面.
修改按钮点击事件:
1 | @Override |
首先指定Intent的action为Intent.ACTION_DIAL
.这也是一个android内置action,然后在data指定了协议是tel
启动,点击按钮,成功了.
向下个活动传递数据
使用Intent
Intent中提供了putExtra()
方法的重载,可以把想要的数据暂存在Intent中,启动另一个活动后把数据从Intent中取出.
例如FirstActivity中的字符串传递到SecondActivity中:
1 | @Override |
这里我们使用了显示Intent启动SecondActivity,通过putExtra()
方法传递了一个字符串,它的第一个参数是键,用于后面Intent中取值,第二个参数是值,真正要传递的数据.
然后在SecondActivity中将数据取并打印:
1 | @Override |
通过getIntent()
方法获取到了用于启动SecondActivity的Intent,然后调用intent的getStringExtra()
方法,传入相应的键值,得到传递的数据,赋值给我们声明的data.
然后使用Log打印,d是debug级.
运行,点击活动的按钮,产看Logcat我们传入的字符串打印出来了.
返回数据给上一个活动
我们之前启动活动使用的方法startActivity()
,Activity中还有一个startActivityForResult()
方法也是用于启动活动的,这个方法期望在活动销毁时能够返回一个结果给上个活动.startActivityForResult()
方法接收两个参数,一个Intent,另一个是请求码,用于在之后的回调中判断数据的来源.
修改FirstActivity中的按钮点击事件:
1 | @Override |
这里请求码只要是唯一的就可以了,我们随便传入一个数字1,接下来在SecondActivity中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑:
1 | @Override |
我们还是构建了一个Intent用于传递数据,但这个Intent没有任何”意图”.通过putExtra()
方法把数据存在Intent中.然后调用setResult()
方法,这个方法是专门用于向上一个活动返回数据的,它接收两个参数,第一个用于向上个活动返回处理结果,一般只使用RESULT_OK
和RESULT_CANCELED
两个值,第二个参数把带有数据的intent传递回去.
最后用finish()
方法销毁当前活动.
由于使用的startActivityForResult()
方法来启动这个活动,销毁这个活动后会回调上一个活动的,onActivityResult()
方法,因此我们重写FirstActivity中的这个方法来取得返回数据:
1 | @Override |
onActivityResult()
有三个参数,requestCode
启动活动传入的请求码;resultCode
返回数据时传入的处理结果;data
携带着返回数据的Intent.
此时我们点击button2按钮就会返回数据并打印,但是如果我们直接按返回键就不会反悔数据,这时我们重写onBackPress()
方法,它返回后执行的过程和点击按钮相同,所以方法体的内容应该和onClick()
相同:
1 | @Override |
值得注意的是这里重写了onBackPress()
方法,也就是点击返回按钮时执行的方法,如果方法体里不加上finish()
那么点击返回按钮是不会返回的.
活动的生命周期
返回栈
从之前的例子中可以看出,android的活动是可以层叠的,每启动一个新活动就会覆盖再原活动上.销毁上面的活动,之前的活动就会重新显示出来.
android使用任务(task)来管理活动.
一个任务就是一组存放在栈里的活动集合,这个栈称为返回栈(Back Stack).新活动处于栈顶,销毁新活动(出栈)前一个活动就出现了,系统总是显示处于栈顶的活动给用户.
活动状态
- 运行状态
- 暂停状态 活动不处于栈顶,但仍然可见时
- 停止状态 活动不处于栈顶,且完全不可见时.
- 销毁状态 依然占用内存知道系统回收它(最倾向被回收的状态)
生存周期
活动的七个方法与三个阶段:
onCreate() 每个活动都会有的方法,在活动第一次被创建的时候调用
OnStart() 这个活动在活动由不可见变为可见的时候调用
OnResume() 这个方法在活动准备好和用户进行交互的时候调用,此时的活动一定位于返回栈的栈顶,并且处于运行状态。
onPause() 这个方法在系统准备去启动或者恢复另一个活动的时候调用。 我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
onStop() 这个方法在活动完全不可见的时候调用。它和 onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause()方法会得到执行,而 onStop()方法并不会执行。
onDestroy()这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
onRestart() 这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
以上七个方法中除了 onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。
三个阶段:
开始Activity:在这个阶段依次执行3个生命周期的方法,分别是:onCreate、onStart和onResume方法
Activity重新获得焦点:如果Activity重新获得焦点,会依次执行3个方法,onRestart、onStart和onResume
关闭Activity:当Activity被关闭时系统会依次执行3个方法,onPause、onStop和onDestory。
生存周期体验
页码:P67 为了提高看书效率从这里开始,这样的实际操练就简略记录
设置为对话框(Dialog)活动
进入AndroidManifest.xml
对要注册为Dialog的活动的<activity>
标签头加入代码android:theme
:
1 | <activity android:name=".DialogActivity" |
android:theme
用于指定当前活动主题,我们使用的内置主题.这里出现了问题 参看:打开Dialog活动
活动回收前保存参数
停止状态的活动有可能被回收onSaveInstanceState()
方法可以保证在活动回收前一定会调用,通过这个方法来解决活动被回收时临时数据没有保存的问题.onSaveInstanceState()
方法会携带一个Bundle类型参数,Bundle提供了一些列方法保存数据.例如putString()
用来保存字符串,这个方法第一个参数为键,第二个参数才是数据.
在onCreate()
方法中也有个Bundle类型参数,默认为null当我们用onSaveInstanceState()
方法保存过数据就会把这个Bundle传递进来,使用Bundle响应的方法取出数据即可.
活动的启动模式
四种启动模式: standard, singleTop, singleTask, 在AndroidManifest.xml
中通过<activity>
标签指定android:launchMode
属性来选择启动模式
standard(默认)
standard,标准启动模式,是活动默认启动模式.
使用standard模式的活动,系统不会在乎活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新实例.
singleTop
启动活动时如果发现返回栈的栈顶已经是这个活动,则认为可以直接使用它,不会再创建新的活动实例.
singleTask
每次启动活动系统首先在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用这个实例,并把在这个活动之上的所有活动全部出栈,如果没有发现就会创建这个活动的实例.
singleInstance
这个模式的活动会启用一个新的返回栈来管理这个活动.
返回时,无论上个活动是谁,只返回当前活动所在的返回栈,返回栈为空后才返回上个调用这个活动的返回栈.
活动实践
知晓当前是哪一个活动
P81
随时退出活动
p82
启动活动的最佳写法
p84
第三章 UI开发
常用控件的使用
TextView
android:layout_width
和:layout_height
指定了控件的宽度和高度,安卓所有控件都有着两个属性.
可选值:
- match_parent 当前控件大小和父布局一样(官方推荐)
- fill_parent 当前控件大小和父布局一样
- wrap_content 当前控件大小刚好包住内容
除了这样也可以指定宽高为固定大小,但是在屏幕适配上会出问题.
使用android:gravity
指定当前控件文字对其方式,可以用|
来指定多个值
例如: center
值的意义就和center_vertical|center_horizontal
相同
使用android:textSize
控制文字大小,单位是sp
android:textColor
控制文字颜色
Button
侦听器的另一种使用方式
前面的学习中已经熟练的使用了匿名内部类的方式注册监听器,下面是实现接口的方式来注册,代码如下:
1 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{ //此处实现了一个OnclickListener接口,这个活动具有了监听器类的特性,"单继承,但可以多实现" |
EditText
允许用户在控件里输入和编辑内容.
属性android:hint
实现显示提示性文本,输入文字文本消失.android:maxLines
制定它的最大行数,超过行数后组件不再继续向下拉伸
ImageView
图片控件,图片通常放在drawable
开头的目录下android:src="@drawable/img_1"/>
属性指定图片,后面是对图片资源的引用
在活动中还可以动态覆盖xml中设置好的图片.使用ImageView的setImageResource()
方法.
ProgressBar(进度条)
控件的可见属性:android:visibility
在布局文件中设置
可选值:
- visible,默认的,可见的
- invisible,控件透明,依然占据位置
- gone,不可见且不占用空间
也可以在活动中,通过setVisibility()
方法传入类似View.VISIBLE
的值.
另外可以在布局文件中通过style
属性给它指定进度条样式.
通过android:max
属性指定进度条的最大值
在活动中通过setProgerss
设置进度.
AlertDialog
弹出对话框,且对话框置于最上方,屏蔽其他控件的交互能力,用于提示重要内容或者警告.
在活动中通过AlertDialog.Builder
创建一个AlertDialog实例,用这个实例的方法设置镖旗内容是否可取消等属性,setPositiveButton()
方法设置确定按钮点击事件,setNegativeButton()
按钮设置取消点击事件.最后使用show()
方法显示出来
ProgressDialog
同AlertDialog相似,但是会显示一个进度条,表示后台忙请等待.
四种基本布局
p105
线性布局
LinearLayout,在线性方向上依次排列排列.android:orientation
属性指定排列方向vertical
是竖直方向,horizontal
是水平方向(默认).
android:layout_gravity
属性指定控件在布局中的对其方式.
android:layout_weight
允许使用比例的方式指定控件大小
相对布局
RelativeLayoutandroid:layout_alignParentXXX
相对父的xxx
位置可以是上下左右.android:layout_centerInParent="true"
相对于父的中间.
上面的属性需要布尔值
android:layout_above`
android:layout_below指定当前控件位于另一个控件的上/下方
android:layout_toRightOfandroid:layout_toLeftOf`指定当前控件位于另一个控件的左/右方.
`android:layout_alignLeft
android:layout_alignRightandroid:layout_alignTop
android:layout_alignBottom`当前控件边缘和另一个控件左/右/上/下边缘对齐
上面的属性需要引用相对控件的id
帧布局
FrameLayout,所有控件默认摆放左上角,控件默认会按照先后顺序叠加.
但是可以使用android:layout_gravity
指定对其方式
百分比布局
只有LinearLayout(线性)布局支持使用layout_weight
属性来实现按比例指定控件大小的功能.其他两种布局都不支持
android引入了新的布局方式实现百分比布局PercentFrameLayout`
PercentRelativeLayout`
PercentFrameLayou:
为了让老版本android支持新的布局,在项目的build.gradle
中添加百分比布局库的依赖,就能保证百分比布局的兼容性
打开App/build.gradle文件,在dependencies
闭包中添加
1 | compile 'com.android.support:percent:对应版本号' |
修改布局文件<android.support.percent.PercentFrameLayou>t
因为不是内置sdk必须把完整的包路径写下来
在布局文件头加入xmlns:app="http://schemas.android.com/apk/res-auto"
这样一个命名空间,这样才能使用百分百布局的自定义属性.
使用app:layout_widthPercent
属性将各个按钮的宽度指定为布局的50%,高度同理,这里能够使用app
前缀的属性是因为我们刚才定义了app
的命名空间,当然我们一直能够使用android
前缀的属性也是同理.
PercentFrameLayou会继承FrameLayout的特效,控件默认左上角,使用layout_gravity
属性让四个按钮不重叠
PercentRelativeLayout:
同理
创建自定义控件
先来看看常用控件和布局的继承结构
所有控件直接简介继承View,View是安卓中最基本UI组件,可在屏幕回执矩形区域,并能响应这个区域的各种事件.ViewGroup是一种特殊的View包含很多子ViewGroup和子View,是一种容器.
利用这种继承关系可以自定义控件.
引入布局
创建布局文件在另一个布局文件使用类似代码
1 | <include layout="@layout/title" /> |
引入这个布局文件
下面是隐藏自带标题栏代码
1 | ActionBar actionbar=getSupportActionBar(); //调用actionbar实例 |
创建自定义控件
p122
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控件.
非activity类中调用startActivity(intent)
1 | startActivity(intent); //改为getContext().startActivity(intent); //这个 |
非活动类调用finish();
1 | ((Activity)getContext()).finish(); //getcontext取得context,然后强转为活动类,外面加一个括号区分顺序,实际就是一个 Activity.finish()类似this.finish(); |
常用&难用控件 ListView
简单用法
p124
定制ListView界面
点击事件
p131
RecyclerView
同ListView一样,但是更强大,官方推荐
基本用法
新增控件,在support库中,首相在build.gradle中添加依赖才能使用.
1 | implementation 'com.android.support:recyclerview-v7:28.0.0-rc02' |
由于不是系统内置SDK使用时需要写出完整包名<android.support.v7.widget.RecyclerView>
横向滚动和瀑布流布局
P137
RecyclerView点击事件
p139
编写界面实践
P142
制作Nine_Pach图片
第一行代码中的方式不适用,工具已经集成到了AS中,对着导入的png图片右键,点击 Creat-9-patch file .
第四章 碎片Fragment
Fragment是什么
是一种可以嵌入在活动中的UI片段
Fragment的使用方式
Fragment的简单用法
建议使用support-4库的类不使用系统内置的,兼容差.build.gradle
已经注册了库的依赖我们不需要手动注册.
LeftFragment这个自己创建的控件加入主控件的时候需要使用android:name属性指定LeftFragment的包.
动态添加碎片
p155
添加碎片的方法内部包含步骤:
- 创建待添加碎片实例
- 获取FragmentManager,在活动中通过调用
getSupportFragmentManager()
方法得到 - 开启一个事物,通过fragmentManager调用
buginTransaction()
开启 - 使用开启的事物调用
replace()
方法实现,参数为需要传入容器的id和待添加的碎片实例. - 使用事物调用commti()提交事物.
碎片中模拟返回栈(BackStack)
活动中动态添加碎片后,按返回键会直接退出当前活动,如果要只退出碎片,需要模拟返回栈.
使用FragmentTransaction的addToBackStack()
方法,将事物添加到一个返回栈中,参数是一个名字用于描述返回栈状态,一般为null就可以.
碎片和活动通信
在活动中使用FragmentManager的findFragmentById()
方法获取碎片实例,类似活动中findViewById()
获取控件实例的方法.
1 | RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.right_fragment); |
得到实例后就能轻松使用实例的方法了.
在碎片中使用活动的getActivity()
方法获得和当前碎片关联的活动实例:
1 | MainActivity activity =(MainActivity)getActivity(); |
活动中需要使用Context对象时也可以使用这个方法,活动本身就是一个context.
碎片和碎片之间的通信就通过上面两个方法的组合实现.
碎片的生命周期
碎片的状态和回调
状态:
- 运行状态: 碎片可见且关联的活动处于运行状态
- 暂停状态: 当一个活动进入暂停状态,关联的碎片就进入到暂停状态
- 停止状态: 当活动进入停止状态,与之关联的碎片进入停止状态.或者通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除且调用addToBackStack()方法.
- 销毁状态: 活动销毁时相关的碎片进入销毁,通过调用FragmentTransaction的remove(),replace()方法将碎片从活动中移除且没有调用addToBackStack()方法.
回调:
活动中有得回调方法,碎片中都有,除此之外还有额外的方法.
- onAttach(),当碎片和活动建立关联的时候调用.
- onCreatView(),为碎片创建视图(加载布局)的时候调用
- onActivityCreated().当与碎片相关联的活动一定已经创建完成的时候调用
- onDetach(),当碎片和活动解除关联的时候调用.
体验碎片的生命周期
p163
碎片中的数据也可以通过”savedInstanceStanceState()”方法保存在Bundle参数中,防止停止的时候被消除.
动态加载布局的技巧
p166
使用限定符(Qualifiers)
p167
最小宽度限定符
p168
碎片的实践 (延迟)
广播
标准广播: Normal broadcasts,完全异步广播,发出广播所有接收器会在同一时刻接收,接收率比较高,但无法被截断.
有序广播: Ordered broadcasts,同步执行广播,广播发出后同一时刻只有一个广播接收器能够接受消息.广播接收器逻辑执行完毕后广播才会继续传递.广播接收器有优先级,优先级高的广播接收器先收到消息.优先级高的接收器还能截断广播
接收系统广播
动态注册监听网络变化
广播接收器可以注册广播,有两种方式:
- 代码中注册 称为动态注册
- AndroidManifest.xml注册 称为静态注册
创建广播接收器,需要创建一个类继承自BroadcastReceiver
,并重写onReceive()方法.
在监听网络变化的时候由于会监听网络,所以需要在AndroidManifest
中声明权限.
静态注册实现开机启动
动态注册广播接收器可以自由控制注册与注销,但是必须在程序启动后才能收到广播.静态注册可以在程序未启动的情况下收到广播.
可以直接使用AS直接new一个广播接收器,名字下方的复选框,Exported
代表允许接收器接受程序外的广播,enabled
代表是否启用这个接收器.
广播接收器的onReceive()
方法中不能执行较多的逻辑或者耗时长的操作,因为广播接收器不允许开启线程,这个方法长时间没有结束程序就会报错.
发送自定义广播
发送标准广播
P189
发送有序广播
p191
有序广播使用方法sendOrderedBroadcast()
接受两个参数第一个为Intent第二个是与权限相关的字符串.
注册广播时可以通过android:priority
属性给广播设置优先级
在代码中还可以在Onreceive()
方法中通过abortBroadcast()
方法截断广播,阻止广播继续传播.
使用本地广播
p192
为了安全让程序内的广播不泄露,需要使用本地广播,android提供了一个LocalBroadcastManager
来对广播进行管理,并提供发送广播和注册广播接收器的方法.
本地广播接收器无法在AndroidManifest.xml
中通过静态注册,本地广播更安全,更高效.
广播实践-强制下线
p195
这里我的接收器有问题?反正没有接受广播成功
第六章 持久化技术
p206
文件存储
p207
将数据存储到文件中
context类提供一个方法openFileOutput()
,用于将数据存储到指定文件中,两个参数,一个指定文件名称一个指定文件操作模式:MODE_PRIVATE
如果存在直接覆盖;MODE_APPEND
如果存在则追加.
通过openFileOutput()
方法返回一个FileOutputStream
对象,用它构建一个OutputStreamWriter
对象,接着用这个对象构建一个BufferedWriter
对象.然后使用BufferedWriter对象将数据写到文件中.
从文件中读取数据
p211
对应的Context类中提供了openFileInput()
方法用于从文件中读取数据,接受一个参数,即要读取的文件名,返回一个FileInputStream
对象.然后通过它构建出InputStreamReader
对象,使用这个对象构建出BuffereReader
对象,然后通过这个对象一步步读取,并存放在一个StringBuilder
对象中,最后将读取的内容返回就可以了.
SharedPreferences存储
p213
使用键值对的方式来存储.
将数据存储到SharedPreferences中
android中提供了三种获取SharedPreferences对象的方法:
- Context类中的
getSharePreferences()
方法 :两个参数一个指定文件名称,一个用于指定操作模式,目前就只有MODE_WORLD_READABLE
这个模式推荐,也可以输入0
等同于这个模式. - Activity类中
getPreferences()
方法:雷士上一个方法,但是只有一个参数就是操作模式,文件名称自动用当前活动的类名称. - PreferenceManager类中
getDefaultSharedPreferences()
方法: 静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀.
存储步骤:
使用SharedPreferences
对象的edit()
方法来获取一个SharedPreferences.Editor
对象,向SharedPreferences.Editor
对象中添加数据(使用类似putString()
putBoolean()
的方法),调用apply()
方法,将添加的数据提交完成存储操作
SharedPreferences文件使用xml格式对数据存储.
从SharedPreferences中读取数据
直接使用SharedPreferences
对象对象提供的get方法(getString(),getBoolean()….),两个参数,第一个是键,第二个是默认值即找不到键时以什么样的默认值返回.
实现记住密码功能
p218
SQLite数据库存储
创建数据库
使用SQLiteOpenHelper
帮助类,实现简单的数据库创建升级.
这是抽象类,两个抽象方法onCreate()
onUpgrade()
实现创建,升级数据库逻辑.
两个实例方法:getReadableDatabase()
和getWritableDatabase()
.这两个方法创建或打开一个数据库,并返回一个可对数据库读写操作的对象.不同的是当数据库不可写入的时候getReadableDatabase()
方法返回的对象以只读的方式打开,而getWritableDatabase()
方法将出现异常.
SQLiteOpenHelper
有两个构造方法可提供重写,一般使用参数少的那个构造方法.接受四个参数,Context
,数据库名
,自定义Cursor
数据库查询的时候返回一个自定义cursor一般传入null,当前数据库版本号
可用于对数据库升级操作.
构建出实例后再调用两个实例方法就能够直接创建数据库了,然后重写的onCreate()
方法会的到执行,这里通常会处理一些创建表的逻辑.
使用adb shell查看数据库
p225
使用adb shell进入数据库目录,输入sqlite3 数据库名称
就可以打开数据库
添加数据
getReadableDatabase()
和getWritableDatabase()
.这两个方法创建或打开一个数据库,并返回一个SQLiteDatebase对象,除了使用sql语句外,SQLiteDatebase,中提供了各种方法对数据库进行操作.insert()
方法用于添加数据:三个参数,第一个参数表名,第二个参数指定未添加数据时给耨些可空的列自动赋值null(一般用不到直接传入null即可),第三个是ContentValues
对象它提供了一系列put()方法重载用于向ContentValues中添加数据只需要将表中没得列名及相应的待添加数据传入即可.
更新数据
p232
同样SQLiteDatabase提供了updata()
方法四个参数:
- 表名
- ContentValues对象,把更新的数据在这里装进去
- 第三第四用于约束更新某一行或者某几行中的数据,不指定默认更新所有行.
删除数据
p234
同样SQLiteDatabase提供了delete
方法,三个参数:
- 表名
- 第二第三用于约束删除某行或某几行数据,不指定默认删除所有行.
查询数据
p235
SQLiteDatabase提供了query()
方法用于查询数据
这个方法最短的方法重载也需要7个参数.
- 表名
- 第二个用于指定查询哪几列,不指定这查询所有
- 第三四用于约束查询某一行或者某几行数据
- 第五用于指定要去
group by
的列,不指定则代表不进行group by操作. - 第六个用于对group by之后的数据进行进一步过滤,不指定则代表不过滤
- 第七格用于指定查询结果的排序方式,不指定则默认.
下面是详细参考
query()方法会返回一个Cursor
对象查询到的所有数据从这个对象取出.
使用SQL操作数据库
使用LitePal操作数据库
p240
LitePal是一个开源android数据库框架,采用对象关系映射(ORM)模式,地址是:https://github.com/LitePalFramework/LitePal
配置LitePal
直接在app/build.gradle
中声明开源库的引用就可以了
在dependencies
闭包中添加
1 | implementation 'org.litepal.android:core:2.0.0' |
接下来配置litepal.xml
文件.右击app/src/main新建一个assets
目录,然后在assets目录下再新建一个litepal.xml
文件,编辑如下内容.
1 | <?xml version="1.0" encoding="utf-8" ?> |
然后配置LitePalApplication
,修改AndroidManifest中的代码
在<application>
中添加android:name="org.litepal.LitePalApplication"
创建和升级数据库
创建一个java bean,这个类Book就是一个sql中对应的book表,接下来将Bool类添加到映射模型当中,修改litepal.xml
中的代码
1 | <?xml version="1.0" encoding="utf-8" ?> |
这里使用<mapping>
标签来声明我们要配置的映射模型类,注意一定要使用完整的类名称.不论有多少模型要映射都使用同样的方式配置在<list>
标签中.
所有配置工作完成,只要进行任意一次数据库操作,数据库就会自动创建出来.
使用LitePal添加数据
之前创建的Book是没有继承关系的,LitePal在进行表管理操作的时候不需要模型类有继承结构,但是进行CRUD(增删改查)就必须继承DataSupport
类.
这个类提供了sava
方法用来完成数据添加的操作.也提供了其他的CRUD方法
使用LitePal更新数据
249
删除数据
两种方式
第一种直接调用已存储对象的delete()方法.
第二种调用DataSupport的deleteAll()方法,三个参数,第一个表名称,第二三个限制条件
查询数据
自己查询学习的知识
包括一些学过然后忘记的知识
Android Studio技巧
快捷键
学习过程中我觉得需要牢记的快捷键
Android Studio 平台 Win10
ALT+ENTER 快速import类
Ctrl+O 重写某个方法
ctrl+shift+/ 注释段
ctrl+/ 注释行
alt+insert 快捷生成getter,setter方法
文件名颜色的意思
颜色主要与版本控制(version control)有关
- 绿色,已经加入控制暂未提交
- 红色,未加入版本控制
- 蓝色,加入,已提交,有改动
- 白色,加入,已提交,无改动
- 灰色:版本控制已忽略文件
JSON 简单学习
语法规则
一切都是对象,对象表示为键值对,数据由分号间隔,花括号保存对象,方括号保存数组.
键值对
键在前面用""
包裹,值在:
后面如果是字符串也用""
包裹,值可以是任何数据,也可以是对象或者数组
对象(花括号的那个)里存放键值对,用,
隔开.
同时数组里也可以存放键值对,但是更多的数组里是存放索引,像这样["java","c#","html"]
java内部类调用外部类的方法
直接使用外部类名称.this.方法即可,例如Inner是内部类,Outer是外部类,在Inner中调用Outer.this.Print()即可使用Outer的print方法
关于context
建议阅读
Context是什么
Activity,Serverce都算Context.把场景抽象为一个Context类,用户的每次交互都是在场景里,还有一些没有界面的场景,如服务.一个程序可以看作为一个工作环境,在环境有不同场景,我们会在其中切换.android程序的四大组件,其他的控件等都要在context环境下工作
它是一个纯抽象类,如图是它的实现
关于侦听器中的
1 | new view.onclicklistener(){.....} |
首先我们可以确定的是这个用法是属于java语法范畴的:
new 后面肯定是个构造器,构造器后面是有括号的,所以onClickListener是构造器(要new出来的类),前面的View表示命名空间,这个onClickListener是View中的。
查看View.class的源码可以发现:
public interface OnClickListener {
void onClick(View var1);
}
onClickListener这是View内部的接口(成员接口),new的时候要实现onClick()方法。
this与activity.this
知乎问题 未读
学习过程中我觉得需要牢记的快捷键
win系统
ALT+ENTER 快速import类
Ctrl+O 重写某个方法
ctrl+shift+/ 注释段
ctrl+/ 注释行
alt+insert 快捷生成getter,setter方法
遇到的问题&解决方法
“android.content.res.Resources$NotFoundException: String resource ID #0x2”错误
解决方案 当调用setText()方法时如果传入int型是不会被当成内容而是resourceID来使用!
所以报错!
转为String传入即可
No resource identifier found for attribute ‘roundIcon’ in package ‘android’
问题描述
报错: ERROR:No resource identifier found for attribute ‘roundIcon’ in package ‘android’
解决方案
roundlcon
是高版本才有特性,一般是由于调节targetSdkVersion版本造成的,
删除清单文件AndroidManifest.xml里的 android:roundIcon=”@mipmap/ic_launcher_round”
这句话就OK了
只要看到Error:No resource identifier……,肯定是xml文件里面出问题了,盯着找就好了
如果是in package ‘XXX’,这个多半就是自己定义的控件在应用的时候出了问题
参考博客:https://blog.csdn.net/androidfszl/article/details/61919384
Git如何将本地仓库合并到远程(未解决)
这样一个情景:远程仓库的dev分支已经有内容,我在本地新建了一个仓库代码写完了,准备把这个本地仓库关联到远程仓库并推送到dev分支.
可能的解决方案:https://blog.csdn.net/zhouhuacai/article/details/78284990
安装时遇到的问题
创建控件不显示
问题描述
在按照书本布局章节创建第一个控件时,手动输入代码创建控件(button)不显示,但是控件有一个警告,大致意思是android:text="haha"
等号后面不建议接输入内容(但依然可以直接输入),建议使用引用@string/button_1
这样的引用格式来引用资源,可以手动在values下的strings.xml
创建,也可以直接用警告中的快速修复,编译器自动帮你创建并引用,结果相同.
修复后并没有解决问题,依然不显示.直接在Design中拖一个控件也不显示.
但是直接运行项目是能够现实的,只是在preview中不显示.
解决方式
两种方法原理都不知道…应该是AS的bug
方法一
直接修改布局的主题,直接在preview内修改,似乎只要主题不是AppThem就能够显示
方法二(我使用的方法)
修改app目录下的build.gradle文件内的 dependencies依赖implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
改为 implementation 'com.android.support:appcompat-v7:28.0.0-alpha1'
重启(刷新)AS即可.
运行项目问题1
错误提示:
Instant Run performed a full build and install since
the installation on the device does not match the local build on disk.
解决方法:可忽略
activity响应http的action问题
问题描述
第二章有一个这样的示例,让自己的activity可以响应http的action,但是在AndroidManifest.xml
中报错.
提示为
1 | activity supporting action_view is not set as browsable |
按这个意思看,应该是要把这个activity设为brosable
实际上忽略这个错误直接运行还是能够达到预期效果,但Logcat会报一个错,虽然没有影响程序运行.
解决方案
添加一个category把activity设置为BROSABLE
1 | <activity android:name=".ThirdActivity"> |
错误消失
打开对话框式的活动
问题描述
操作书上P69页例子时出错
点击按钮打开DialogActivity运行报错:
1 | java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.elsewhere997.activityliffecycletest/com.example.elsewhere997.activityliffecycletest.DialogActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity. |
不能启动活动,从caused可以看出是活动的主题出问题了,要使用Theme.AppCompat的主题.因为我们之前的DialogActivity继承自AppComjpatActivity
1 | public class DialogActivity extends AppCompatActivity |
解决方案
方法一 修改主题
修改AndroidManifest.xml中的dialog活动主题的代码为
1 | android:theme="@style/Theme.AppCompat.Dialog"> |
重新编译运行,成功了
方法二 直接继承Activity
如果不需要继承AppCompatActivity,直接继承Activity,就可以使用@android:style/Theme.Dialog
主题了
重新编译运行,成功了
Cannot resolve symbol R
问题描述
提示为:Cannot resolve symbol R
R文件识别不了但是确实存在,正常情况应该不会报错.
Android Studio 无法识别同一个 package 里的其他类,将其显示为红色,但是 compile 没有问题。鼠标放上去后显示 “Cannot resolve symbol XXX”,重启 Android Studio,重新 sync gradle,Clean build 都没有用。
解决方案
方案一
菜单栏Build–Rebuild Project
但是本错误并没有解决.以后备用
方案二
点击菜单中的 “File” -> “Invalidate Caches / Restart”,然后点击对话框中的 “Invalidate and Restart”,清空 cache 并且重启。没有解决,备用
方案三(重点)
重新检查查看build窗口,发现错误提示,布局文件,代码layout_alignParentleft
出错,not found
检查后发现没有大写left
的L
修复解决.
仔细检查代码!善用提示窗口,不要光看代码提示
Cannot set the value of read-only property ‘outputFile’(待解决)
错误描述
我在完成任务`git@lawyer5.cn:selection-coolcode/android.git`
clone后在AS运行,build的时候报错:
1 | Cannot set the value of read-only property 'outputFile' for...... |
解决方案
方案一(备用)
然后照着网上的帖子[ As Android plugin 3.0 migration guide suggests:] 修改了bulid.gradle中的代码
1 | variant.outputs.all { output -> |
然后try again… 继续错误
1 | All of them match the consumer attributes:....... |
照着帖子:https://blog.csdn.net/qqcrazyboy/article/details/77900183 基本配置好apt到annotationProcessor
还是有一个错误
1 | All flavors must now belong to a named flavor dimension |
查看error后面给的网站,于是又在app的build.gradle里的 defaultConfig加了一句 flavorDimensions "versionCode"
继续错误
1 | failed linking references. |
然后禁用aapt2
1 | android.enableAapt2=false |
然后,就是 java complier: 50errors
,哇心累.
这个方法没有解决问题,留作备用
方案二
一开始运行这个项目的时候,我选择了更新Android Gradle Plugin
然后就报错1
Cannot set the value of read-only property 'outputFile' for......
不断的google后还是一直报不同的错误,最后直接错变成了 java complier: 78errors
,搞了一下午成这样,哇心累.
然后用git清空了所有修改,重新打开项目,没有更新gradle,运行成功了.
必须定义为final变量
错误描述
错误的代码
1 | private class ForceOfflineReceiver extends BroadcastReceiver{ |
错误提示1
Variable 'context' is accessed from within inner class, needs to be declared final
解决方式
java中规定,内部类只能访问外部类中的成员变量,不能访问方法中定义的变量,如果要访问方法中的变量,就要把方法中的变量声明为final(常量)的,因为这样可以使变量全局化,就相当于是在外部定义的而不是在方法里定义的
这里需要把onReceive()
中的参数Context 加上final修饰.
使用adb Shell拒绝
错误描述
使用adb Shell进入data/data…文件夹的时候提示Permission denied,全校被拒绝
解决方案
在命令行继续输入 su root,然后手机确认授root权限.
使用sqlite3工具 not found
错误描述
用adb shell 打开数据库文件,提示没有sqlite3,查看了手机system/bin/目录有sqlite3.exe文件,root也给了,android7.1
1 | 2|D6503:/ $ sqlite3 BookStore.db |