Skip to content

AS快捷键

AS中智能提示:ctrl + space
格式化:ctrl + alt + l
可重写的方法:ctrl + o
重命名:shift + f6

琐碎知识点

  • 项目包名组成:域名+公司名+项目名
  • 可通过Device中的Device File Explorer查看当前虚拟手机中的文件

插件安装

  • VIM
  • Database Navigator

Android原生


安卓属于原生开发,是移动端开发的方式之一

移动开发的三种模式

  • Native App Android 原生

    充分发挥:
    	性能
    	硬件功能
    	稳定性
    	安全性
    问题
    	成本高:学习成本高 && 开发维护开销高
    	兼容性差
  • Hybrid App 混合开发

    Ionic
    	关系:Ionic 是建立在 Cordova/PhoneGap 之上的一个开源UI框架,专注于为混合移动应用提供美观的UI组件和交互体验。
    	特点:使用HTML、CSS(尤其是利用Ionic自己的UI组件库)和JavaScript(或TypeScript)开发,强调原生观感的UI设计,适合快速开发具有原生体验的混合应用。
    React Native (RN)
    	关系:React Native 是由Facebook推出的一个开源框架,允许使用React(一个流行的前端JavaScript库)来开发原生移动应用。
    	特点:采用“Learn once, write anywhere”理念,开发者使用JavaScript编写代码,但最终生成的是原生组件而非WebView,因此在性能上接近原生应用。支持跨平台的同时保持良好的用户体验。
    Flutter
    	关系:Flutter 是由Google开发的开源UI框架,用于创建原生界面的跨平台移动、Web和桌面应用。
    	特点:使用Dart语言编写,拥有自己的渲染引擎,提供丰富的组件和高度定制化的UI设计能力。Flutter强调“一切皆Widget”的理念,使得UI设计更加统一且高效,同时保证了高性能。
    ----------
    交接点:
    	1. web->原生
    	2. 原生->web
    	3. 数据通信本身
  • Web App 前端

    基于浏览器的H5开发
    问题
    	受浏览器制约(版本、兼容性、性能

安卓体系架构

体系架构

  1. 系统应用层

    Dialer
    Email
    Calendar
    Camera
  2. JavaAPI层

    应用层核心
    	Content Providers
    	View System
    	Managers
    		Activity
    		Location
    		Package
    		Notification
    		Resource
    		Telephony
    		Window
  3. C++ && Android Runtime(ART)

    Java->编译DEX文件->虚拟机
  4. Hardware Abstraction Layer(HAL) 硬件抽象层

    隔离Android Framework与Linux
  5. Linux内核

基本理念

  • 逻辑与视图分离

核心概念

  1. 活动 Activity

    应用基于Activity开发,可以理解为前端的路由,每个路由指定一个页面,也标记着页面

    所有程序都运行在Activity中,是Android的根基
    一个Android App由多个Activity组成
    多个Activity之间可以相互跳转

    Activity

    • 生命周期

    • 返回栈

    • 四种启动模式

    Intent 意图

    组件间的通信纽带

    只有MainActivity需要配置intent-filter的活动内容

    分类:

    • 隐式
    • 显式

    数据传递

    Layout 布局管理

    分类:

    • 线性布局(LinearLayout)
    • 限制(ConstraintLayout)
    • 帧(FrameLayout)

    Fragment 片段

    将activity拆分出子模块

    Widget 组件/控件

    Spinner ListView,涉及到Adapter

  2. 服务 Service

    后台运行,用户切换到其它应用,不清除其进程
    可通过Toast & 状态栏对client进行单向通知

    基本用法

    manifest中注册服务

    <service android:name="com.example.servicetest.DIYService"></service>

    启动方式(有其lifespan)

    • 直接启动:startService / stopService
    • 绑定启动:bindService / unbindService
    • 混合启动

    自定义服务

    1. 创建子类(继承
    2. 重写父类方法:onCreate / onStartCommand / onBind / onDestroy
  3. 广播接收器 Broadcast Reciever

    数据通信

    后台服务的子模块

    全局事件监听,可将App与系统的事件进行发布订阅

    自定义广播

    1. 继承BroadcastReceiver
    2. 重写onReceive

    注册

    <receiver>
    	<intent-filter>
    		<action></action>
    		<action></action>
    	</intent-filter>
    </~>

    注意注销,避免内存泄漏

    发送广播

    1. sendOrderedBroadcat(Intent, string) 顺序
    2. sendBroadcast(intent) 随机
    Intent intent = new Intent();
    intent.setAction(ID)
    intent.putExtra() // 可选
    sendBroadcast(intent)
  4. 内容提供者 ContentProvider

    访问第三方应用API、数据时;类似于guard || middleware
    其它app访问自己的数据,需要通过ContentProvider

安装与配置

安装

  • Java SDK5.0以上
  • Android Studio
  • 手机模拟器
    • HAXM 内置模拟器组件
    • AVDManager 安卓虚拟设备,创建需要型号的虚拟手机模拟器

配置

  • 设置SDK版本(包括Android SDK && JDK && NDK):Project Structure-SDK Location
  • 主题风格:Appearance-Theme
  • 字号:Editor-Font

基础知识

类比前端的整体逻辑,Android也可大致分为控件层、样式层、逻辑层;分别对应着HTML、CSS和JS

控件层

控件

控件定义的一般逻辑

  1. ID等特殊属性
  2. 通用属性
  3. 专用属性

原生控件

  • TextView——?

  • Toast 原生信息提示

  • Button——button

  • EditText——input

  • ImageView——img

  • ProgressBar 原生进度条

  • AlertDialog——alert

  • CheckBox——checkbox

  • *ProgressDialog

  • Fragment——可复用的组件

  • ListView——list(列表

    数组中的数据无法直接传递给ListView,需要适配器来完成桥接

Material Design

  • package com.example.prac15;
    
    import android.annotation.SuppressLint;
    import android.content.Intent;
    import android.os.Bundle;
    import android.view.Gravity;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ImageView;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.ActionBar;
    import androidx.appcompat.widget.Toolbar;
    import androidx.core.view.GravityCompat;
    import androidx.drawerlayout.widget.DrawerLayout;
    
    import com.google.android.material.navigation.NavigationView;
    
    public class Home extends BaseActivity {
        DrawerLayout drawerLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.home);
            initNavigator();
            bindNavigationEvent();
        }
    
        private void bindNavigationEvent() {
            NavigationView nav = findViewById(R.id.nav_view);
            nav.setCheckedItem(R.id.nav_to_home);
            nav.setNavigationItemSelectedListener(item -> {
                drawerLayout.closeDrawers();
                switch (item.getItemId()) {
                    case R.id.nav_to_home: {
                        Intent intent = new Intent("home");
                        startActivity(intent);
                        break;
                    }
                    case R.id.nav_to_article: {
                        Intent intent = new Intent("article");
                        startActivity(intent);
                        break;
                    }
                    case R.id.nav_logout:
                        Intent intent = new Intent("com.example.prac15.FORCE_OFFLINE");
                        sendBroadcast(intent);
                        break;
                }
                return true;
            });
        }
    
        private void initNavigator() {
            Toolbar toolbar = findViewById(R.id.system_toolbar);
            setSupportActionBar(toolbar);
    
            drawerLayout = findViewById(R.id.drawer_layout);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null) {
                actionBar.setDisplayHomeAsUpEnabled(true);
                actionBar.setHomeAsUpIndicator(R.mipmap.more);
            }
        }
    
        @Override
        public boolean onOptionsItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case android.R.id.home:
                    this.drawerLayout.openDrawer(GravityCompat.START);
                    break;
            }
            return true;
        }
    }

样式层

基础

常用通用属性:
	id							ID
	layout_width  match_parent  适配父容器大小
    layout_height wrap_content  刚好包裹住当前内容
    text						适用于TextView
    gravity 	 				对齐
    orientation  				方向
    visibility					可见性(visible / invisible / gone )
    
文本
    textSize 
    textColor

Button 关闭默认英文全部大写展示
	textAllCaps 				默认特性-英文全部大写
	
ProgressBar 设置为水平进度条
	style="@style/Widget.AppCompat.ProgressBar.Horizontal"
	android:max="100"

布局

布局分为三种

  • 线性——flex

    指定orientation为horizontal,那么内部组件的layout_width不能为match_parent,否则会占满一行
    指定orientation为vertical,那么内部组件的layout_height不能为match_parent,否则会占满一列
    
    内部组件属性:
    	layout_gravity 类比到justify-content & align-items
    	layout_weight 类比flex-grow & flex-shrink
  • 相对——relative

    相对固定的绝对布局

    layout_alignParentLeft
    layout_alignParentRight
    layout_alignParentTop
    layout_alignParentBottom
    layout_centerInParent
    
    layout_above / layout_below
    layout_toLeftOf 
    layout_toRightOf
  • 约束——absolute

    更加灵活的绝对布局

    layout_constrainVertical_bias
    layout_contrainHorizontal_bias
    
    layout_constrainStart_toStartOf
    layout_constrainEnd_toEndOf
    layout_constrainTop_toTopOf
    layout_constrainBottom_toBottomOf
  • 引用——自定义layout

逻辑层

activity

  • 创建

  • 注册

    1. manifest中注册activity,并添加<activity></activity>
    2. 为activity设置intent-filter表明主动作
    	<intent-filter>
    		<action></action>
    		<category></category>
    	</intent-filter>

intent

  • Intent是对程序员想法的一个抽象,你想要做什么,就是什么样的意图;和字面意思是一样的

    * 一个intent可以有多个category,但只能有一个action;可以使用编程式添加category
    创建intent:
    	Intent intent = new Intent(MainActivity.this, FirstActivity.class); // 跳转到First页面
    执行intent:
    	startActivity(intent)

R

  • Resource的简称,常量、组件等都可以通过调用其静态方法获取资源对象的引用

    使用资源对象
    	R.xxx.xxx

Layout

  • 布局,和Web布局是一样的道理

    创建Layout:
    	1. res中mkdir layout
    	2. 右键,创建layout文件
    	3. activity中在lifespan onCreated中setContentView(R.layout.xxx)

Toast

  • 一段时间后自动消失的消息提示,不占用屏幕空间

    直接使用静态方法
    Toast.makeText(MainActivity.this, "点赞", Toast.LENGTH_SHORT).show()

Menu

  • 点击后显示的Menu栏,一般位于角落

事件监听

有两种方式

  • 事件绑定
  • 接口声明
new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        }

Log

Log.v(): Verbose(详细),用于输出最详细的调试信息,通常在开发阶段使用,发布时可关闭。
Log.d(): Debug(调试),用来输出调试信息,比Verbose级别略高,常用于跟踪程序运行流程。

Log.i(): Info(信息),用于记录一般的信息,比如应用程序启动、用户操作等,这些信息在正常运行时可能需要查看。
Log.w(): Warning(警告),表示有潜在错误的状况,虽然程序仍能继续执行,但需注意可能会导致问题。
Log.e(): Error(错误),记录的是程序出错的信息,表明发生了应当被立即关注并修复的错误。

广播接收器

实质上是一个全局的事件监听,本身不起到任何作用,只是一个hook

设置方式

  • 动态注册——代码注册
  • 静态注册——manifest注册

数据储存

方式

  • 通过子节流的方式,保存在本地,数据为原始格式
  • SharedPreferences方式,保存在本地,数据为xml格式

高级特性

Manifest配置

<uses-permission /> 用于申请硬件权限

主题

  • Dark Theme

    方式
    	- 强制设置暗黑模式:style.sml中添加: android:forceDarkAllowed = "true"
    	- 增加一套暗黑配置:res-> mkdir values-night, 创建资源文件colors-night.xml
    功能
    	1. 降低功耗
    	2. 用户体验:夜晚使用 || 低光线下使用
  • Multiple Language

划屏

需要Adapter,对不同的view管理

动画

针对Activity与Fragment

Material Design

使用Jetpack Compose,基于Jetpack Compose,仅支持kotlin

地理位置

特点

  • 精度问题:精度越高越耗电
  • 需要用户授权

传感器

  • 近场通信 NFC
  • 陀螺仪
  • 方向 && 加速 && 旋转矢量

功能代码块

查找控件ById

适用于各种控件,和getElementById一样
	findViewById(R.id.xxx)

水平垂直居中

1. 布局的android:gravity="center"
2. 组件的android:gavity="center"

按钮点击监听

 btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点赞", Toast.LENGTH_SHORT).show();
                Log.v("MainActivity", "btn:" + btn);
            }
        });

点击按钮弹出消息提示

package com.example.prac8;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn = (Button) findViewById(R.id.button);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点赞", Toast.LENGTH_SHORT).show();
                Log.v("MainActivity", "btn:" + btn);
            }
        });
    }
}

获取EditText的内容

 EditText target = (EditText) findViewById(R.id.mainInput);
 
 private void initButton() {
        EditText ipt = this.mainInput;
        Button button = (Button) findViewById(R.id.mainButton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String tipValue = ipt.getText().toString(); // 获取内容
                Toast.makeText(MainActivity.this, tipValue, Toast.LENGTH_SHORT).show();
            }
        });
    }

设置ImageView

1. 图片资源放到对应文件夹中
2. 
			<ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/img" />
* 设置宽高均为wrap_content,可以保证图片完全显示

设置hidden

        this.hideButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                progressBar.setVisibility(View.INVISIBLE);
            }
        });

水平进度条

        <ProgressBar
            android:id="@+id/secondProgressBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:max="100"
            />

        this.progressButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int currentValue =  progressBar.getProgress();
                progressBar.setProgress(currentValue + 10);
            }
        });

弹出AlertDialog

this.alertButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AlertDialog.Builder dialog = new AlertDialog.Builder(FirstActivity.this);
                dialog.setTitle("欢迎来到黑乎乎的小屋");
                dialog.setMessage("阿巴阿巴阿巴");
                dialog.setCancelable(false);
                dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(FirstActivity.this, "雨下整夜", Toast.LENGTH_SHORT).show();
                    }
                });
                dialog.show();
            }
        });

设置基础Menu栏

1. res中mkdir menu && touch main
2. 编写menu
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/menu1"
        android:title="@string/menu1">MENU1
    </item>
    <item
        android:id="@+id/menu2"
        android:title="@string/menu2">MENU2
    </item>
</menu>

3. 重写activity中onCreateOptionsMenu方法
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
设置事件处理
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu1:
                Toast.makeText(MainActivity.this, "点击了第一个", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menu2:
                Toast.makeText(MainActivity.this, "点击了第二个", Toast.LENGTH_SHORT).show();
                break;
        }
        return super.onOptionsItemSelected(item);
    }

设置基础ListVIew

    private void init() {
        String[] data = {"213b", "alksjf", "askljf"};
        // android.R.layout 调用系统配置
        ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, data);
        ListView listView = findViewById(R.id.mainList);
        // 构建List与适配器之间的关系
        listView.setAdapter(arrayAdapter);
    }

自定义Adapter,实现复杂ListView

本质上是通过适配器桥接数据结构,实现一个循环渲染,Java做起来有些麻烦

1. 定义数据结构
package com.example.prac10;

public class Cloth {
    public String fabric;
    public String price;
    public String season;

    public Cloth(String fabric, String price, String season) {
        this.fabric = fabric;
        this.price = price;
        this.season = season;
        System.out.println(this.fabric + this.price + this.season);
    }

    public String getFabric() {
        return fabric;
    }

    public String getPrice() {
        return price;
    }

    public String getSeason() {
        return season;
    }

    public String getMessage(){
        return this.getFabric() + this.getSeason() + this.getPrice();
    }
}


2. 重写自定义适配器类
package com.example.prac10;

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

import java.util.List;

public class ClothAdapter extends ArrayAdapter<Cloth> {
    private int resourceId;
    public ClothAdapter(Context context, int resourceId, List<Cloth> object){
        super(context, resourceId, object);
        this.resourceId = resourceId;
    }

    public View getView(int position, View coverView, ViewGroup parent){
        Cloth cloth = new Cloth("棉", "32", "夏");
        View view = LayoutInflater.from(getContext()).inflate(this.resourceId, parent, false);
        TextView fabric = (TextView) view.findViewById(R.id.item_fabric);
        TextView price = (TextView) view.findViewById(R.id.item_price);
        TextView season = (TextView) view.findViewById(R.id.item_season);
        fabric.setText(cloth.getFabric());
        price.setText(cloth.getPrice());
        season.setText(cloth.getSeason());
        return view;
    }
}

3. 对应的layout.xml文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/item_fabric"
            android:layout_width="match_parent"
            android:layout_height="30dp" />

        <TextView
            android:id="@+id/item_price"
            android:layout_width="match_parent"
            android:layout_height="30dp" />

        <TextView
            android:id="@+id/item_season"
            android:layout_width="match_parent"
            android:layout_height="30dp" />

    </LinearLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

4. 设置适配器关联
package com.example.prac10;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private List<Cloth> clothList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initCloth();
        init();
    }

    private void init() {
        ArrayAdapter arrayAdapter = new ClothAdapter(MainActivity.this, R.layout.cloth_item, this.clothList);
        ListView listView = findViewById(R.id.mainList);
        // 构建List与适配器之间的关系
        listView.setAdapter(arrayAdapter);
    }

    private void initCloth() {
        Cloth clo1 = new Cloth("丝", "90", "夏");
        Cloth clo2 = new Cloth("麻", "40", "春秋");
        Cloth clo3 = new Cloth("羽绒", "400", "冬");
        this.clothList.add(clo1);
        this.clothList.add(clo2);
        this.clothList.add(clo3);
    }
}

ListView的Item绑定点击事件

        ArrayAdapter arrayAdapter = new ClothAdapter(MainActivity.this, R.layout.cloth_item, this.clothList);
        ListView listView = findViewById(R.id.mainList);
        // 构建List与适配器之间的关系
        listView.setAdapter(arrayAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Cloth cloth = clothList.get(position);
                Toast.makeText(MainActivity.this, cloth.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });

全局广播注册

动态注册,程序必须启动才能接收到广播

1. 开启权限
manifest中:
	    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
2. 注册广播
package com.example.prac11;

import androidx.appcompat.app.AppCompatActivity;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity  {
    private IntentFilter intentFilter;
    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        this.intentFilter = new IntentFilter();
        this.networkChangeReceiver = new NetworkChangeReceiver();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        registerReceiver(networkChangeReceiver, intentFilter); // 注册到全局
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }

    class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(MainActivity.this, "网络变化", Toast.LENGTH_SHORT).show();
        }
    }
}

静态注册,可以在APP未启动就注册

直接通过AS的Receiver创建,AS会自动注册到Manifest中

1. 在Manifest中开启对应的硬件权限,并设置intent-filter
    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>


    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.Prac11"
        tools:targetApi="31">
        <receiver
            android:name=".BootReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


2. 在重写的类中,设置对应的逻辑
package com.example.prac11;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "重启成功", Toast.LENGTH_SHORT).show();
    }
}

数据存储与读取

Stream

存储:使用流存储数据,将字节流转换为字符流,通过文件输出流写入数据到本地目录的指定文件中

读取:同上

Android Java中的文件流的读取和存储逻辑

读取
	文件输入流 FileInputStream in = openFileInput(path)
	缓存读取器 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 子节流转字符流
	临时存储字符 StringBuilder content = "";
		 while ((line = reader.readLine()) != null) {
                System.out.println(line);
                content.append(line);
            }
存储
	文件输出流 FileOutputStream out = openFileOutput(savePath, saveMode);
	缓存写入器 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out)); 
		writer.write(data)

代码

package com.example.prac12;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.Toast;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class MainActivity extends AppCompatActivity {
    private EditText editText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        String savedData = loadData();
        if (!TextUtils.isEmpty(savedData)) {
            editText.setText(savedData);
            editText.setSelection(savedData.length()); // 将输入光标移至末尾
            Toast.makeText(MainActivity.this, "Data Reload Successfully", Toast.LENGTH_SHORT).show();
        }
    }

    private void init() {
        this.editText = (EditText) findViewById(R.id.main_text);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        String inputValue = this.editText.getText().toString();
        saveData(inputValue);
    }

    protected void saveData(String data) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            out = openFileOutput("dataSaveDir", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    protected String loadData() {
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder content = new StringBuilder();
        try {
            in = openFileInput("dataSaveDir");
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
                content.append(line);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return content.toString();
    }
}

SharedPreferences

使用SharedPreferences键值对存储,保存为xml文件到本地

启动后读取已有数据

package com.example.prac14;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
        String msg = pref.getString("msg", "-");
        int code = pref.getInt("code", 0);
        String status = pref.getString("status", "-");
        String res = msg + code + status;
        Toast.makeText(MainActivity.this, res, Toast.LENGTH_SHORT).show();
    }

    private void init() {
        EditText editText = findViewById(R.id.main_edit_text);
        Button button = findViewById(R.id.main_button);
        String value = editText.getText().toString();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
                editor.putString("msg", value + "\nsave successfully");
                editor.putInt("code", 200);
                editor.putString("status", "ok");
                editor.apply();
            }
        });

    }
}

路由跳转 / Intent使用

intent使用(显式)

package com.example.prac8;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class FirstActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_first);
        Button btn = (Button)findViewById(R.id.button3);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(FirstActivity.this, MainActivity.class); //跳转到根路由,意图很明显,称为显示Intent
                startActivity(intent);
            }
        });
    }
}

intent使用(隐式)

隐式Intent可以启动其它APP,是多APP数据共享的必经之路

  • 显式路由跳转

    1. 注册activity,设置action的name为路由路径
    		<activity
                android:name=".SecondActivity"
                android:exported="false" >
                <intent-filter>
                    <action android:name="test" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
    2. 
    	private void switchToSecond(){
            Log.v("===","===ToSecond===");
            Button btn = (Button)findViewById(R.id.buttonToSecond);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent("test");
                    startActivity(intent);
                }
            });
        }
  • 使用内置浏览器跳转到指定网址

      private void toBili(){
            // 使用Intent的内置常量作为参数
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("http://www.bilibili.com"));
            Button btn = (Button)findViewById(R.id.buttonToBilibili);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    startActivity(intent);
                }
            });
        }
  • 调用系统自动拨号

        private void toPhone(){
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setData(Uri.parse("tel:10086"));
            Button btn = (Button)findViewById(R.id.buttonToBilibili);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    startActivity(intent);
                }
            });
        }
  • 带数据的跳转

    发送:
    	private void switchToFirst(){
            Log.v("===","===ToFirst===");
            Button btn = (Button) findViewById(R.id.button);
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "点赞", Toast.LENGTH_SHORT).show();
                    Intent intent = new Intent(MainActivity.this, FirstActivity.class);
                    // 设置data数据
                    intent.putExtra("data","FirstActivity-数据传输-FromMain");
                    startActivity(intent);
                }
            });
        }
    
    接收:
    	private void logIntentTransferData(){
            Intent receiveIntent = getIntent();
            String data = receiveIntent.getStringExtra("data");
            Log.v("First", data);
        }

登录注册

基础版本,不使用数据库,拥有记住密码、退出登录的基础功能

1. 骨架层——main

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_marginLeft="33dp"
    android:layout_marginRight="33dp"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/account" />

        <EditText
            android:id="@+id/main_account"
            android:layout_width="wrap_content"
            android:layout_height="48dp"
            android:layout_weight="1"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.419" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout2"
        app:layout_constraintVertical_bias="0.078">

        <TextView
            android:layout_width="60dp"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@string/password" />

        <EditText
            android:id="@+id/main_password"
            android:layout_width="wrap_content"
            android:layout_height="48dp"
            android:layout_weight="1"
            android:inputType="textPassword"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </LinearLayout>

    <CheckBox
        android:id="@+id/main_checkbox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/remember"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.135"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/linearLayout"
        app:layout_constraintVertical_bias="0.072" />

    <Button
        android:id="@+id/main_submit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/submit"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/main_checkbox"
        app:layout_constraintVertical_bias="0.059" />

</androidx.constraintlayout.widget.ConstraintLayout>
2. 骨架层——home
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".Home">

    <Button
        android:id="@+id/home_logout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/logout"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
3. 常量
<resources>
    <string name="app_name">Prac15</string>
    <string name="account">帐号</string>
    <string name="password">密码</string>
    <string name="submit">登录</string>
    <string name="logout">退出登录</string>
    <string name="remember">记住密码</string>
</resources>
4. 逻辑层
=========================================================ActivityController
package com.example.prac15;

import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.os.Bundle;

import java.util.ArrayList;
import java.util.List;

public class ActivityCollector  {
    public static List<Activity> activityList = new ArrayList<>();

    public static void addActivity(Activity instance) {
        activityList.add(instance);
    }
    public static void removeActivity(Activity instance) {
        activityList.remove(instance);
    }
    public static void finishAll(){
        for (Activity activity:activityList){
            if (!activity.isFinishing()){
                activity.finish();
            }
        }
    }
}

=========================================================BasicActivity
package com.example.prac15;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    private ForceOfflineReceiver receiver;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityCollector.addActivity(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (receiver != null) {
            unregisterReceiver(receiver);
            receiver = null;
        }
    }

    @Override
    protected void onPostResume() {
        super.onPostResume();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.prac15.FORCE_OFFLINE");
        receiver = new ForceOfflineReceiver();
        registerReceiver(receiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ActivityCollector.removeActivity(this);
    }

    class ForceOfflineReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            AlertDialog.Builder dialog = new AlertDialog.Builder(context);
            dialog.setTitle("系统提示");
            dialog.setCancelable(false);
            dialog.setMessage("帐号状态已变更, 请重新登录");
            dialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCollector.finishAll();
                    Intent intent = new Intent(context, MainActivity.class);
                    context.startActivity(intent);
                }
            });
            dialog.show();
        }
    }
}

===============================================================Home
package com.example.prac15;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class Home extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        bindLogoutEvent();
    }

    private void bindLogoutEvent(){
        Button button = findViewById(R.id.home_logout);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.prac15.FORCE_OFFLINE");
                sendBroadcast(intent);
            }
        });
    }
}
===========================================================MainActivity
package com.example.prac15;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Toast;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends BaseActivity {
    EditText accountComponent;
    EditText passwordComponent;
    Button submitComponent;
    CheckBox checkBoxComponent;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        bindSubmit();
        isCheckedRememberPassword();
    }

    private void init() {
        EditText account = findViewById(R.id.main_account);
        EditText password = findViewById(R.id.main_password);
        Button submit = findViewById(R.id.main_submit);
        CheckBox checkBox = findViewById(R.id.main_checkbox);
        this.accountComponent = account;
        this.passwordComponent = password;
        this.submitComponent = submit;
        this.checkBoxComponent = checkBox;
    }

    private void bindSubmit() {
        this.submitComponent.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String account = accountComponent.getText().toString();
                String password = passwordComponent.getText().toString();
                SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
                if (account.equals("admin") && password.equals("12345")) {
                    if (checkBoxComponent.isChecked()) {
                        editor.putString("account", account);
                        editor.putString("password", password);
                        editor.apply();
                    }else {
                        editor.clear();
                        editor.apply();
                    }
                    route();
                }
            }
        });
    }

    private void isCheckedRememberPassword() {
        SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
        String account = pref.getString("account", "");
        String password = pref.getString("password", "");
        if (password.length() != 0) {
            accountComponent.setText(account);
            passwordComponent.setText(password);
            checkBoxComponent.setChecked(true);
        }
    }

    private void route() {
        Intent intent = new Intent(MainActivity.this, Home.class);
        Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
        // 设置定时器, 延迟2s进入
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(intent);
                        finish();
                    }
                });
            }
        }, 2000);
    }
}

SQLite数据库

学习项目详见github的nesReading-Demo,支持双语