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开发 问题 受浏览器制约(版本、兼容性、性能
安卓体系架构
体系架构
系统应用层
Dialer Email Calendar Camera
JavaAPI层
应用层核心 Content Providers View System Managers Activity Location Package Notification Resource Telephony Window
C++ && Android Runtime(ART)
Java->编译DEX文件->虚拟机
Hardware Abstraction Layer(HAL) 硬件抽象层
隔离Android Framework与Linux
Linux内核
基本理念
- 逻辑与视图分离
核心概念
活动 Activity
应用基于Activity开发,可以理解为前端的路由,每个路由指定一个页面,也标记着页面
所有程序都运行在Activity中,是Android的根基 一个Android App由多个Activity组成 多个Activity之间可以相互跳转
Activity
生命周期
返回栈
四种启动模式
Intent 意图
组件间的通信纽带
只有MainActivity需要配置intent-filter的活动内容
分类:
- 隐式
- 显式
数据传递
Layout 布局管理
分类:
- 线性布局(LinearLayout)
- 限制(ConstraintLayout)
- 帧(FrameLayout)
Fragment 片段
将activity拆分出子模块
Widget 组件/控件
Spinner ListView,涉及到Adapter
服务 Service
后台运行,用户切换到其它应用,不清除其进程 可通过Toast & 状态栏对client进行单向通知
基本用法
manifest中注册服务
<service android:name="com.example.servicetest.DIYService"></service>
启动方式(有其lifespan)
- 直接启动:startService / stopService
- 绑定启动:bindService / unbindService
- 混合启动
自定义服务
- 创建子类(继承
- 重写父类方法:onCreate / onStartCommand / onBind / onDestroy
广播接收器 Broadcast Reciever
数据通信
后台服务的子模块
全局事件监听,可将App与系统的事件进行发布订阅
自定义广播
- 继承BroadcastReceiver
- 重写onReceive
注册
<receiver> <intent-filter> <action></action> <action></action> </intent-filter> </~>
注意注销,避免内存泄漏
发送广播
- sendOrderedBroadcat(Intent, string) 顺序
- sendBroadcast(intent) 随机
Intent intent = new Intent(); intent.setAction(ID) intent.putExtra() // 可选 sendBroadcast(intent)
内容提供者 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
控件层
控件
控件定义的一般逻辑
- ID等特殊属性
- 通用属性
- 专用属性
原生控件
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,支持双语