1. .gradle 和 .idea
这两个目录下放的是Android Studio 自动生成的一些文件
2. app
项目中的代码、资源等都放在这个文件夹
(1).build
和外层的build类似,主要包含在编译时自动生成的文件
(2).libs
项目中使用的第三方jar包,放在这个文件夹下的jar包都会自动添加到构建路径里去
(3).androidTest
此处是用来编写Android Test 测试用例的,可以对项目进行一些自动化测试
(4).java
放置编写的代码的地方
(5).res
项目中使用的所有图片、布局、字符串等资源都放在这个文件夹下,图片放在drawable目录下、布局放在layout目录下、字符串放在value目录下,所有mipmap开头的文件都是来放应用图标的(之所以有这么多mipmap文件是为了让程序兼容各种设备(分辨率不同))。
(6).AndroidManifest.xml
这是整个Android项目的配置文件,在程序中指定的所有四大组件都需要放在这个文件里注册,另外还可以在这个文件中给应用程序添加程序声明
(7).test
用于编写Unit Test 测试用例的,是对项目进行自动化测试的另一种方式
(8). .gitgnore
将app模块中指定的目录或文件夹排除在版本控制之外
(9).app.iml
IDEA 自动生成
(10).build.gradle
这是app模块的gradle构建脚本,这个文件中会指定很多项目构建相关的配置
(11).proguard-rules.pro
这个文件用于指定项目代码的混淆规则,当代码开发完成之后打成安装包文件,如果不希望代码被破解,通常会将代码进行混淆
3. build
包含一些编译时自动生成的文件
4. gradle
包含 gradle wrapper 的配置文件
5. .gitigonre
将指定的目录或文件排除在版本控制之外
6. build.gradle
项目全局的gradle构建脚本
7. gradle.properties
全局的 gradle 配置文件,在这里配置的属性将会影响到项目中的所有的gradle编译脚本
8. gradlew 和 gradlew.bat
这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew 是在Linux 或 Mac系统中使用的,gradlew.bat 是在Windoes系统中使用的
9. local.properties
用于指定本机中的 Android SDK 路径,通常内容都是自动生成,除非本机中的Android SDK 位置发生了变化,否则不需要修改
10. setting.gradle
这个文件用于指定项目中所有引入的模块,因为新建的这个项目只有一个app模块,所以文件中只引入了app这一个模块,通常情况下模块引入是自动完成的。
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这是对 MainActivity 这个活动进行注册,没有注册的活动是不能进行使用的,intent-filter
中的两行代码表示这个是主活动,在手机点击应用图标,首先启动的就是这个活动
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
MainActivity 继承于AppCompatActivity ,这是一种向下兼容的 Activity 最低兼容到Android2.1系统(AppCompatActivity 是 Activity 的子类),setContentView
方法给当前的活动引入了一个布局 activity_main
- 在代码中通过 R.string.app_name 可以获得该字符串的引用
- 在XML中通过 @String/app_name 可以获得该字符串的引用
如果引用图片资源将string替换成 drawable 引用图标替换成 mipmap 引用布局替换成 layout,以此类推
build.gradle文件
最外层的 build.gradle 文件是项目的全局构建配置
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
// jcenter()
maven{ url'http://maven.aliyun.com/nexus/content/groups/public/' }
maven{ url'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
// jcenter()
maven{ url'http://maven.aliyun.com/nexus/content/groups/public/' }
maven{ url'http://maven.aliyun.com/nexus/content/repositories/jcenter'}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
buildScript块 主要是为了Gradle脚本自身的执行,获取脚本依赖插件。
allprojects块 用于项目构建,为所有项目提供共同所需依赖。
两处 repositories 闭包都声明了jcenter(),jcenter()是一个代码托管仓库,由谷歌提供需要翻墙,这里可以配置成阿里云的镜像仓库
dependencies 闭包中使用 classpath 声明了一个Gradle插件,因为Gradle不是专门为Android项目开发的,Java、C++等很多项目都可以使用Gradle构建,如果想使用它来构建Androd项目,就要添加com.android.tools.build:gradle
来兼容
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
buildToolsVersion "30.0.1"
defaultConfig {
applicationId "com.example.helloworld"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
apply plugin: 应用了一个插件,一般有两种值可选:com.android.application(表示这是一个应用程序模块)com.android.library(表示这是一个库模块),应用程序模块是可以直接执行的。库模块只能依附于应用程序模块来运行
android的闭包,在这个闭包中可以配置项目构建的各种属性
compileSdkVersion
表示项目编译的版本
buildToolsVersion
表示项目构建工具的版本
然后嵌套了一个defaultConfig 闭包,defaultConfig 闭包可以对项目中的更多细节进行配置
applicationId
用于指定项目的包名
minSdkVersion
指定项目最低兼容的Android系统版本 15 表示最低兼容到Android4.0系统
targetSdkVersion
指定的值表示在该版本上已经做过了充分的测试,系统将会为应用程序启动一些新的功能和特性
versionCode
指定项目的版本号
versionName
指定项目版本名称
在下面有嵌套了一个buildTypes 闭包,buildTypes 闭包用于指定生成安装文件的配置,通常会有两个常见的子闭包,一个是debug
一个是release
, debug用于指定生成测试版安装文件的配置,release 用于指定生成正式版安装文件的配置
minifyEnabled
用于指定是否对项目的代码进行混淆
proguardFiles
用于指定混淆时使用的规则文件,proguard-android-optimize.txt是在Android SDK 目录下,里面是所有项目通用的混淆规则,proguard-rules.pro是在当前项目的目录下的,里面可以编写挡墙项目特有的混淆规则
最后dependencies 闭包是指定当前项目所有的依赖关系,一般有3中依赖方式:本地依赖、库依赖、远程依赖
Android 日志工具
Android的日志工具是Log(android.util.Log),这个类提供了5中方法来打印日志
- Log.v():(verbose)用于打印最琐碎,意义最小的日志信息,是Android 日志里级别最低的一种
- Log.d():(debug)用于打印调试信息,这些信息对调试程序和分析问题有帮助
- Log.i():(info)打印一些比较重要的数据,这些数据可以帮你分析用户行为数据
- Log.w():(warn)打印一些警告信息,提示程序有潜在的风险,需要去修复
- Log.e():(error)打印程序中的错误信息,表示程序出现严重错误,尽快修复
活动Activaty
可以使用android:label="This is MainActivity"
指定活动中标题栏的内容,注意指定活动的 label 不仅会成为标题栏的内容,还会成为启动器(Launcher)中应用程序显示的名称
Toast
Toast是android系统提供的一个提醒方式,通过 makeText() 创建出一个 Toast,然后调用 show() 将Toast 显示出来,makeText() 需要三个参数:Context、显示的文本内容,显示的时长(Toast.LENGTH_SHORT 和 Toast.LENGTH_LONG)
Toast.makeText(MainActivity.this, "你点击了按钮", Toast.LENGTH_SHORT).show();
Menu
在res目录下新建一个 menu 文件夹,在文件夹中新建 main 的菜单文件 New——Menu resource file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
Ctrl+o重写 onCreateOptionsMenu 方法 getMenuInflater() 方法可以获得 MenuInflater 对象,再调用 inflate() 方法就可以创建当前活动的菜单,inflate() 接收两个参数,一个是菜单的资源文件,一个是将菜单添加到哪一个 Menu 对象,返回 true 表示允许菜单显示出来
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
重写 onOptionsItemSelected() 方法定义菜单响应事件
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(MainActivity.this, "你点击了Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(MainActivity.this, "你点击了Remove", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
销毁一个活动
finish() 方法,和手机上按下 Back 键是一样的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
finish();
}
Intent
一般用于启动活动,启动服务以及发送广播,大致分为两种:隐式Intent、显式Intent
显式Intent
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "你点击了按钮", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(MainActivity.this, FristActivity.class);
startActivity(intent);
}
});
隐式Intent
通过在标签下配置可以指定当前活动能够相应的 action 和 category
<activity android:name=".FristActivity"
android:label="This is FristActivity">
<intent-filter>
<action android:name="com.example.helloworld.fristactivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
指明了当前活动可以相应 则包含了一些附加信息,只有action 和 category同时满足,这个活动才能相应Intent
android.intent.category.DEFAULT 是一种默认的 category,在调用 startActivity() 时会自动将这个category添加到 Intent 中
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.helloworld.fristactivity");
startActivity(intent);
}
});
每个 Intent 只能指定一个 action,但能指定多个 category ,通过下面代码添加
intent.addCategory("com.example.helloworld.mycategory");
// 同时在AndroidManifest.xml 中添加
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.example.helloworld.mycategory"/>
隐式的 Intent 还可以用来启动其他的应用程序的活动,比如打开一个网页
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "你点击了按钮", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});
Intent.ACTION_VIEW 是Android 系统内置的动作,常量为 android.intent.action.VIEW,然后通过 Uri.parse() 方法将一个网址字符串解析成一个Uri对象,再调用 setData() 方法将这个Uri 对象传递进去,对此还可以在标签中配置标签:
- android:scheme:用于指定数据的协议部分,例如http
- android:host:用于指定数据的主机名,例如www.baidu.com
- android:port:用于指定数据的端口部分,一般紧跟主机之后
- android:path:用于指定主机名和端口之后的部分
- android:mimeType:用于指定可以处理数据的类型
只有data的内容与指定的内容完全一致时,当前活动才能相应,例如上面的浏览器只需要指定 android:scheme=“http” 就能相应所有http协议的 Intent
新建一个 Activity 更改 AndroidManifest.xml
报错:支持ACTION_VIEW的活动未设置为BROWSABLE 但是不影响项目的发布和运行,添加下面可消除错误
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
</intent-filter>
</activity>
除了http协议外还有很多协议,例如geo表示显示地理位置、tel表示拨打电话
传递数据
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.helloworld.fristactivity");
intent.putExtra("zwt_data", "Hi,你好!");
startActivity(intent);
}
});
接受数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activate_first);
Intent intent = getIntent();
String data = intent.getStringExtra("zwt_data");
Toast.makeText(FristActivity.this, data, Toast.LENGTH_LONG).show();
}
getIntent() 获得用于启动这个活动的 Intent,然后调用 getStringExtra 取出数据,由于传递的是字符串,如果传递的是整型就用 getIntExtra,布尔类型就用 getBooleanExtra,以此类推
返回数据
Activity 中有一个 startActivityForResult() 方法,这个方法也是用来启动活动,但这个方法在后动销毁的时候能够返回一个结果给上一个活动,有两个参数,一个是Intent、一个是请求码,请求码只要唯一就行
@Override
public void onClick(View view) {
Intent intent = new Intent("com.example.helloworld.fristactivity");
intent.putExtra("zwt_data", "Hi,你好!");
startActivityForResult(intent,1);
}
构建了一个 Intent ,只不过仅仅用来传递数据,调用 setResult() 方法向上一个活动传递数据,有两个参数,一个是返回处理结果的状态码,一个是带有数据的intent,最后调用finish销毁当前活动
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.putExtra("data_return", "返回数据");
setResult(RESULT_OK, intent);
finish();
}
在销毁活动的时候会回调 onActivityResult() 方法来传递数据,要在之前的活动中重写这个方法,这个方法有三个参数,requestCode 就是唯一的请求码,resultCode 返回处理结果的状态码,data 带有数据的 Intent
注意,项目中可能会多次调用 startActivityForResult() 方法,每一个活动销毁时都会回调 onActivityResult() 方法,所以要通过请求吗判断数据来源
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String return_data = data.getStringExtra("data_return");
Toast.makeText(MainActivity.this, return_data, Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
如果不是通过按钮销毁活动,而是通过Back键来销毁活动的话,就需要重写 onBackPressed() 方法,当按下Back键的时候就会调用 onBackPressed() 里面,就可以同样返回数据
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "返回数据");
setResult(RESULT_OK, intent);
finish();
}
活动状态
- 运行状态:当一个活动位于返回栈顶时,这个活动处于运行状态,系统最不愿意回收这个状态的资源
- 暂停状态:当一个活动不再处于栈顶,但是仍然可见,这个活动就进入了暂停的状态,并不是每一个活动都会沾满整个屏幕,比如对话框形式的活动只会占用屏幕中间的区域,处于暂停状态的活动任然是完全存活的,系统也不会去回收这种活动,只有在内存极低的情况下才会考虑
- 停止状态:当一个活动不再处于栈顶,并且完全不可见,就进入了停止状态,系统仍会保存相应的状态和成员变量,但当其他地方需要内存时就可能会被系统回收
- 销毁状态:当一个活动从返回栈中移除就变成了销毁状态,系统会最先回收处于这种状态的活动资源
活动的生命周期
- onCreate():活动第一次被创建的时候调用,可以在此完成初始化操作(加载布局、绑定事件)
- onStart():在活动由不可见变为可见的时候调用
- onResume():在活动准备好和用户进行交互的时候调用,此时活动一定处于活动栈顶,并处于运行状态
- onPause():在系统准备去启动或者恢复另一个活动的时候调用,通常将一些消耗CPU的资源释放掉,以及保存一些关键数据
- onStop():在活动完全不可见的时候调用,如果启动的新活动是一个对话框式的活动,会调用onPause()不会调用onStop()
- onDestroy():在活动被销毁之前调用,之后活动变为相会状态
- onRestart():在活动由停止状态变为运行状态之前调用,也就是被重新启动
完整生存期:活动在 onCreate() 和 onDestroy() 之间
可见生存期:活动在 onStart() 和 onStop() 之间,在屏幕上是可见的,有的能进行交互,有的不能
前台生存期:活动在 onResume() 和 onPause()之间,活动处于隐形状态,可以和用户交互
活动的启动模式
活动的启动模式有四种:standard、singleTop、singleTask、singleInstance,可以在AndroidManifest.xml 中通过标签指定 android:launchMode 属性选择启动模式
standard:默认启动模式,当启动一个新活动,就会在返回栈中入栈,并处于栈顶的位置,不会在乎该活动是否已经存在栈中,每次启动都会创建新实例
singleTop:在启动活动的时候如果该活动已经在返回栈的栈顶,则直接使用,不再创建,若不再栈顶,则会创建新的实例
singleTask:没次启动活动时,系统会在返回栈中检查是否存在该实例,如果有就直接使用,并把这个活动之上的所有活动出栈,如果没有就创建一个新的实例
singleInstance:指定 singleInstance 模式的活动会启动一个新的返回栈来管理这个活动(singleTask指定不同的 taskAffinity 也会启动一个新的返回栈)一个活动内可能保存着一些数据,每个程序都有自己的返回栈,同一个活动在不同的返回栈中会创建新的实例,这就达不到共享活动实例的需求,使用 singleInstance 会有一个单独的返回栈来管理这个活动,不管那个应用程序来访问,都共用这个单独的返回栈,就实现了活动的共享
经验
通常为了方便管理会创建一个 BaseActivity 类,让其他的 Activity 都继承于这个 BaseActivity ,由于 BaseActivity 没有界面,不需要启动,所以不需要在 AndroidManifest.xml 中注册
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, getClass().getSimpleName());
}
}
创建Activity的管理类 addActivity() 向 List 中添加一个活动,removeActivity() 在 List 中移除这个活动,finishAll() 在 List 中的所有活动进行全部销毁
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity){
activities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for (Activity activity : activities){
if(!activity.isFinishing()){
activity.finish();
}
}
}
}
然后修改 BaseActivity
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
还可以在调用 finishAll() 后杀掉当前进程,保证完全退出程序
android.os.Process.killProcess(android.os.Process.myPid())
数据传递:若 FristActivity 需要传递信息给 SecondActivity 最好的写法是
在 SecondActivity:
public static void actionStart(Context context, String data1, String data2){
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1", data1);
intent.putExtra("param2", data2);
context.startActivity(intent);
}
在 FristActivity 调用上面的方法启动 SecondActivity
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
SecondActivity.actionStart(FristActivity.this, "data1", "data2")
}
});