Android GNSS开发前置知识——Android基础开发

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的作用:启动下一个活动

  1. 指明当前组件想要执行的动作
  2. 在不同组件之间传递数据

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 活动的生命周期

在这里插入图片描述

  1. onCreate():在活动第一次被创建时调用,初始化操作需要在这个函数中完成,包括页面布局、控件响应函数的定义等等
  2. onStart():活动由不可见变为可见时调用
  3. onResume():活动准备好和用户交互时调用
  4. onPause():系统准备去启动或恢复另一个活动时调用,即被其它活动遮挡
  5. onStop():活动完全不可见时调用,即被完全遮挡
  6. onDestroy():活动被销毁之前调用
  7. 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 相对布局

当有一定数量的控件需要按它们之间的相对位置来摆放时,需要用到相对布局。
常见的指定相对位置的方式有:

  1. android:layout_alignParent[ Top / Bottom / Left / Right ] =“true”
  2. android:layout_center[ Horizontal / InParent / Vertical ] =“true”
  3. android:layout_[ above / below / toLeftOf / toRightOf ] ="@id/${相对的控件id}" 使控件位于相对控件的上/下/左/右位置
  4. 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个方法来供我们打印日志:

  1. Log.v() 对应级别verbose
  2. Log.d() 对应级别debug
  3. Log.i() 对应级别info
  4. Log.w() 对应级别warn
  5. 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()函数,实际上它也反映了动态添加碎片的一般过程:

  1. 创建待添加碎片的实例,即传入的 fragment 参数
  2. 获取 FragmentManager,在活动中可以调用 getSupportFragmentManager() 得到
  3. 开启一个事务,即 beginTransaction()
  4. 操作容器添加、隐藏、或替换碎片,对应的有 add()、hide()、replace() 方法,第一个参数为容器的id,第二个参数为待操作的碎片实例
  5. 提交事务,必须提交系统才会执行事务中的操作,通过commit()方法完成

3.5.2 碎片的生命周期

在这里插入图片描述
我们可以给碎片生命周期中的每一个函数添加Log.e()打印出函数的执行信息来观察碎片在不同情况下的生命历程,获得更深刻的理解。

上图中的几个重要函数其意义如下:

  1. onAttach()
    当碎片和活动建立关联的时候调用
  2. onCreateView()
    为碎片创建视图(加载布局)时调用
  3. onActivityCreated()
    确保与碎片关联的活动一定已经创建完毕时调用
  4. onDestroyView()
    当与碎片关联的视图被移除的时候调用
  5. 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() {}

在代码中,我们

  1. 给出三个权限的字符串常量 REQUIRED_PERMISSIONS
  2. 在主活动的 onCreate() 方法中调用 requestPermissionAndSetupFragment() 函数检查是否拥有相应权限,如果有,则进行初始化操作,如果没有,则申请权限。检查权限和申请权限的系统函数及其用法都在代码中。
  3. 在申请权限时,我们需要发送一个请求码,这样在得到回应时,我们才能判断返回的结果对应哪一次的请求,如果判断返回的是请求权限的结果,那么我们遍历返回的整型数组,如果有一个整型值不等于系统给的申请成功对应的整型值,说明申请失败,这时我们通过Toast显示出来
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值