文章目录
1 引言
毕业设计做了一些Android和Android GNSS的开发工作,做完后老师给换了方向,后面大概率不会再涉及这一块了,因此写个总结,如果将来再碰到,可以做个参考
另外,Android体系太庞大了,细说起来太复杂(实则笔者功力不够),因此写得比较简单,主要写一下常见、基本的用法和笔者印象比较深的几个问题
2 开发环境配置
2.1 Android Studio
笔者没有体验过别的IDE,但Android Studio应该是公认的安卓开发大杀器
Android Studio官网下载
在Android Studio中,依次选择Settings —— Appearance & Behavior —— System Settings —— Android SDK,分别下载SDK Platforms(运行平台)、SDK Tools(开发工具)中你需要的组件
2.2 JDK
Android使用Java语言进行最上层的开发,所以需要安装JDK
JDK官网下载
下载后进行安装、配置环境变量
3 Android基础开发
3.1 Activity活动
打开Android Studio创建一个Empty Activity,IDE会生成一个 MainActivity.java 和 activity_main.xml(在 res/layout 目录下),其中前者是Java代码,后者是xml布局文件
package ... // 包名
import ...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 调用父类方法进行初始化
super.onCreate(savedInstanceState);
// 为该活动设置布局文件
setContentView(R.layout.activity_main);
}
}
MainActivity是我们的app启动时的第一个活动 ,也称为主活动,其初始代码如上,onCreate() 方法是活动生命周期中的一环,系统会根据不同的交互情况自动调用活动生命周期中的一系列函数,我们要做的就是在其中一个或几个函数中自定义行为。在 onCreate() 方法中,为该活动设置了布局文件,另外,初始化操作也应在该方法中完成,类似 C/C++ 中的 main() 函数。
activity_main.xml是该活动的布局文件,它定义了主活动的可视化界面。
IDE默认创建的布局文件使用约束布局,我们可以修改布局方式为线性布局、相对布局、帧布局等,下面的布局文件使用了线性布局,其中有一个TextView控件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"> // context指明上下文,即对应的类
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" /> // 宽度和高度适应控件大小,显示文本为"Hello World!"
</LinearLayout>
3.1.1 Intent
Intent的作用:启动下一个活动
- 指明当前组件想要执行的动作
- 在不同组件之间传递数据
Intent分为显示和隐式,显式即在代码中通过调用 startActivity() 或者 startActivityForResult() 来使用Intent;隐式则是在 AndroidManifest.xml 中配置 <activity> 标签下的 <intent-filter>
笔者主要使用显式Intent,因为方便:只需要使用①当前活动的类实例②目的活动的类创建一个 Intent 对象,再调用相应的方法就能启动下一个活动。
Intent使用实例:
/**
* 初始化视图
*/
public void initView(){
button = findViewById(R.id.button);
//为按钮添加监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { // onClick()方法会在 button按下时自动调用
// 显示 Intent
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
// 隐式 Intent
Intent intent = new Intent("com.example.activitytest1.START_ACTION");
startActivity(intent);
// 传递数据
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("Data", "Hello SecondActivity, this is MainActivity");
startActivity(intent);
startActivityForResult(intent, 1);
}
});
}
/**
* 处理 startActivityForResult()的返回结果
*/
@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 returnData = data.getStringExtra("data_return");
// 打印日志
Log.d("MainActivity", returnData);
}
break;
}
}
3.1.2 活动的生命周期
- onCreate():在活动第一次被创建时调用,初始化操作需要在这个函数中完成,包括页面布局、控件响应函数的定义等等
- onStart():活动由不可见变为可见时调用
- onResume():活动准备好和用户交互时调用
- onPause():系统准备去启动或恢复另一个活动时调用,即被其它活动遮挡
- onStop():活动完全不可见时调用,即被完全遮挡
- onDestroy():活动被销毁之前调用
- onRestart():活动由停止状态变为运行状态之前调用
在活动1中启动活动2,然后点击back回到活动1,在点击back的动作中,活动1先onRestart(),活动2再onDestroy()
3.2 XML布局
布局是一种可用于放置很多控件的容器,使用XML(扩展标记语言)来定义其内部的元素及规则,它可以按照一定的规律调整内部控件的位置,布局的内部也可以放置布局,形成嵌套
3.2.1 线性布局
线性布局是笔者最常用的布局方式,用它可以实现大多数布局效果
一. android:orientation 指定了排列方向(horizontal/vertical),默认是horizontal
二. 线性布局允许使用 比例 的方式来指定控件的大小,这点非常重要而且时长会用到:
① 下面的代码中,两个Button的宽度被设为"0dp",而其 android:layout_weight 属性值为1,这表示两个Button将平分该布局的宽度(因为线性布局为默认horizontal)。
② android:layout_weight 属性值可以给任意正整数,系统最后会根据 [ 当前控件的weight分量值 / 当前布局中所有控件的weight值之和 ] 来确定当前控件的宽度/高度(取决于布局为水平还是垂直)
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:padding="8dp">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Reset"
android:textAllCaps="false"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Send"
android:textAllCaps="false"/>
</LinearLayout>
另外,在布局中,也可以只指定一部分控件的 android:layout_weight 属性值,例如下面的代码,该布局的效果为:首先满足 TextView 的宽度,然后将剩下所有的宽度分配给 Button。
使用这种方式编写的界面,不仅在各种屏幕的适配方面会非常好,而且看起来也更加舒服。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:padding="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Input..."
android:textAllCaps="false"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Send"
android:textAllCaps="false"/>
</LinearLayout>
3.2.2 相对布局
当有一定数量的控件需要按它们之间的相对位置来摆放时,需要用到相对布局。
常见的指定相对位置的方式有:
- android:layout_alignParent[ Top / Bottom / Left / Right ] =“true”
- android:layout_center[ Horizontal / InParent / Vertical ] =“true”
- android:layout_[ above / below / toLeftOf / toRightOf ] ="@id/${相对的控件id}" 使控件位于相对控件的上/下/左/右位置
- android:layout_align[ Top / Bottom / Left / Right ] ="@id/${相对的控件id}" 使控件和相对控件的上/下/左/右边缘对齐
3.2.3 帧布局
帧布局笔者的使用情况仅限于作为碎片Fragment加载的容器/对象
3.2.4 常用属性
下表列出了包括布局、控件在内的各种XML元素的常用属性
属性 | 意义 |
---|---|
android:layout_gravity | 指定控件在布局中的对齐方式 |
android:gravity | 指定控件中文字的对齐方式 |
android:alpha | 指定控件的透明度,0(全透明)~ 1(全不透) |
android:layout_margin[ Top / Bottom / Left / Right ] ="_dp" | 指定控件和上/下/左/右的外边缘 |
android:padding[ Top / Bottom / Left / Right ] ="_dp" | 指定控件上/下/左/右的内边缘(空白) |
android:textSize ="_sp" | 指定字体的大小 |
android:background | 指定控件背景,一般为XML定义的形状+颜色 |
android:text | 指定控件的文本 |
android:id | 指定控件的标识名,不能重复 |
android:textColor ="#ff0000" | 指定控件的文本颜色,16进制定义RGB |
3.3 常用控件
3.3.1 TextView
TextView 主要用于在界面上显示一段文本信息
几个重要的属性有:
android:text、android:gravity、android:textSize、android:textColor、android:textStyle、android:background
<TextView
android:id="@+id/tv_title"
android:text="这是TextView的内容"
android:padding="10dp"
android:textSize="18sp"
android:textColor="#ff0000"
android:background="#999999"
android:gravity="center"
android:textStyle="bold"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
还有很多属性就不一一介绍了,这里有 包索引,开发者指南 两篇文档供开发者查阅相关属性,在开发的时候,我们的重点不是记住了一个控件的多少属性,而是为了实现我们的目的,我们要意识到它应该有哪些属性,如果是熟悉的,直接写,如果不熟,再去查阅文档
3.3.2 Button
Button是程序用于和用户交互的一个重要控件
Button的可配置的属性和TextView差不多,不同的是我们可以为Button注册监听器,相应点击事件:
我们先在布局文件中添加一个Button控件,XML代码如下
<Button
android:id="@+id/btn_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提交"
android:textSize="18sp"
android:padding="10dp"/>
然后在布局文件对应的context中,获取该控件,并为其注册监听器,代码如下
public class RegisterActivity extends AppCompatActivity {
private Button btnSubmit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
public void initView() {
btnSubmit = findViewById(R.id.btn_submit); // XML中为控件指定的 id
btnSubmit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(RegisterActivity.this, "Submit", Toast.LENGTH_SHORT).show();
}
});
}
}
也可以使用实现接口的方式来注册监听器,代码如下所示:
public class RegisterActivity extends AppCompatActivity implements View.OnClickListener {
private Button btnSubmit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initView();
}
public void initView() {
btnSubmit = findViewById(R.id.btn_submit); // XML中为控件指定的 id
btnSubmit.setOnClickListener(this);
}
// 实现 View.OnClickListener 接口必须重写该方法
@Override
public void onClick(View v){
switch (v.getId()) {
case R.id.btn_submit:
Toast.makeText(RegisterActivity.this, "Submit", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
}
}
3.3.3 EditText
EditText是程序用于和用户交互的另一个重要控件,它允许用户在控件里输入和编辑内容,并可以在程序中对这些内容进行处理。
Android控件的用法基本上都很相似:给空间定义一个id,再指定控件的宽度和高度,然后在适当加入一些控件特有的属性就差不多了。
EditText特有的属性有:
android:hint 指定一段提示性的文本,当输入任何内容时,提示文本会消失
android:maxLines 指定EditText的最大行数为两行,当输入的内容超过两行时,文本就会向上滚动,而EditText则不会再继续拉伸
3.3.4 ImageView
ImageView用于在界面上展示图片,使用前要提前准备好图片,图片通常放在以"drawable"开头的目录下,项目初建时有一个空的drawable目录,但它没有指定具体的分辨率,可以在res目录下新建一个drawable-xhpi(xhpi指定对应的分辨率),将图片放入后就可以引用
通过 android:src 属性即可指定要引用的图片
<ImageView
android:id="@+id/iv_image"
android:layout_width="wrap_content"
android:layout_height="311dp"
android:layout_gravity="top"
android:background="@android:drawable/picture_frame"
android:src="@drawable/girl" />
也可以在Java代码中动态指定,代码如下
public class SimpleUIComponentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_uicomponent);
ImageView imageView = findViewById(R.id.iv_image);
imageView.setImageResource(R.drawable.girl); // 指定 ImageView 显示的图片
}
}
3.4 日志工具
Android 中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志:
- Log.v() 对应级别verbose
- Log.d() 对应级别debug
- Log.i() 对应级别info
- Log.w() 对应级别warn
- Log.e() 对应级别error
不同级别的信息区别主要体现在Log的过滤中,笔者一般直接使用Log.e(),因为其它信息较少,可以直接看到我们打印的信息。
下方的代码中,我们在主活动的onCreate()方法中添加了一行Log.e()打印错误信息,该方法传入两个参数:第一个参数是tag,一般传入当前类名,主要用于对打印信息进行过滤。第二个参数是msg,即想要打印的具体内容。
package ...
import ...
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("MainActivity:", "onCreate()"); // 通过打印信息显示执行了 onCreate() 方法
}
}
3.5 Fragment碎片
碎片是一种可以嵌入在活动当中的UI片段,它和活动非常相似,同样都能包含布局,同样都有自己的生命周期。
在Android Studio中新建一个碎片,IDE会给出一个碎片类(Java代码)和一个XML布局文件
3.5.1 动态添加碎片
我们在新建的碎片的布局文件中做一点简单的修改(此处我们仅修改一下背景颜色),然后去主活动(MainActivity)中通过代码动态地加载它,主活动的布局文件如下,其整个布局由上方一个Button,下方一个FrameLayout组成,由于这里仅需要在子布局里放入一个碎片,不需要任何定位,因此非常适合使用FrameLayout。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_load_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Load Fragment"
android:textAllCaps="false" />
<FrameLayout
android:id="@+id/frag"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" /> // 使碎片铺满余下的高度
</LinearLayout>
主活动的代码如下,在该段代码里,我们于主活动的onCreate()方法中,对btnLoad进行初始化,将其绑定至布局文件中的控件对象,然后为其设置监听器,当点击Button时,将执行replaceFragment()函数,动态地替换帧布局(R.id.frag)的内容。
package ...
import ...
public class MainActivity extends AppCompatActivity {
private Button btnLoad;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnLoad = findViewById(R.id.btn_load_fragment);
btnLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MainActivity.this.replaceFragment(new BlankFragment());
}
});
}
private void replaceFragment(Fragment fragment) {
// getSupportFragmentManager().beginTransaction().replace(R.id.frag, fragment).commit();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.frag, fragment);
transaction.commit();
}
}
解释一下replaceFragment()函数,实际上它也反映了动态添加碎片的一般过程:
- 创建待添加碎片的实例,即传入的 fragment 参数
- 获取 FragmentManager,在活动中可以调用 getSupportFragmentManager() 得到
- 开启一个事务,即 beginTransaction()
- 操作容器添加、隐藏、或替换碎片,对应的有 add()、hide()、replace() 方法,第一个参数为容器的id,第二个参数为待操作的碎片实例
- 提交事务,必须提交系统才会执行事务中的操作,通过commit()方法完成
3.5.2 碎片的生命周期
我们可以给碎片生命周期中的每一个函数添加Log.e()打印出函数的执行信息来观察碎片在不同情况下的生命历程,获得更深刻的理解。
上图中的几个重要函数其意义如下:
- onAttach()
当碎片和活动建立关联的时候调用 - onCreateView()
为碎片创建视图(加载布局)时调用 - onActivityCreated()
确保与碎片关联的活动一定已经创建完毕时调用 - onDestroyView()
当与碎片关联的视图被移除的时候调用 - onDetach()
当碎片和活动解除关联的时候调用
此外,碎片的生命周期中还有一个 onViewCreated() 函数,该函数的执行时间位于 onCeateView() 之后,onActivityCreated()之前,该函数的意义在于,执行该函数时,碎片布局上的所有view对象都加载完毕,在该函数中获取控件对象的索引将更加安全。
一个最初始的Fragment类如下
package ...
import ...
public class BlankFragment extends Fragment {
public BlankFragment() {}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
该类有一个空的构造函数,在onCreateView中,我们通过膨胀器参数 inflater 加载碎片对应的布局。
该类对应的初始布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
tools:context=".BlankFragment"> // 背景定义为红色,突出加载效果
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
3.6 权限申请
Android中的权限分为系统权限和应用自定义权限,系统权限又分为正常权限和危险权限。使用系统权限需要在manifest文件中注册权限,若是危险权限,还需要在使用时动态申请。
AndroidManifest.xml 中申请权限的写法如下所示,语句写在 <manifest> 标签中
<!-- 用于访问GPS定位 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 读取缓存数据 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 写入扩展存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--拍照(记录结束后分享文件需要该权限)-->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 在SDCard中创建与删除文件权限 -->
<permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
上面列出的权限中,ACCESS_FINE_LOCATION、WRITE_EXTERNAL_STORAGE、CAMERA 三个权限都属于危险权限,除了在AndroidManifest.xml中注册,还需要动态申请。动态申请这三个权限的代码如下
package ...
import ...
public class MainActivity extends AppCompatActivity {
private final String[] REQUIRED_PERMISSIONS = new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
};
private final int REQUEST_PERMISSION_CODE = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
requestPermissionAndSetupFragment();
}
private void requestPermissionAndSetupFragment() {
if (hasPermissions()) {
setupFragments();
} else {
ActivityCompat.requestPermissions(MainActivity.this, REQUIRED_PERMISSIONS, REQUEST_PERMISSION_CODE);
}
}
private boolean hasPermissions() {
for (String p : REQUIRED_PERMISSIONS) {
if (ActivityCompat.checkSelfPermission(MainActivity.this, p) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CODE) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(MainActivity.this, "未拥有权限!", Toast.LENGTH_SHORT).show();
return;
}
}
setupFragments();
}
}
private void setupFragments() {}
在代码中,我们
- 给出三个权限的字符串常量 REQUIRED_PERMISSIONS
- 在主活动的 onCreate() 方法中调用 requestPermissionAndSetupFragment() 函数检查是否拥有相应权限,如果有,则进行初始化操作,如果没有,则申请权限。检查权限和申请权限的系统函数及其用法都在代码中。
- 在申请权限时,我们需要发送一个请求码,这样在得到回应时,我们才能判断返回的结果对应哪一次的请求,如果判断返回的是请求权限的结果,那么我们遍历返回的整型数组,如果有一个整型值不等于系统给的申请成功对应的整型值,说明申请失败,这时我们通过Toast显示出来