利用暑假在“BiliBili 开放大学”来学习一下 Java 以及 Android Studio ,跟着 longway777 老师从新手的角度下学习了 Android 开发的基本内容,目前已经学到 P13 了,今天晚上就不继续学了,来复盘一下最近学的东西,我将按照 内容优先>集数顺序 由表及里的进行回顾复盘。
俗话说要致富,先下载编译工具!在 Android Studio 官网 下载并安装编译工具,Android Studio 也是用于开发 Android 应用的官方 IDE,提供 Compose 设计工具、灵活的构建系统和 Android 模拟器。
刚下载这个就感觉头有点大,他的环境真的难得搭建,光是 Gradle 的配置与拉取就要花费很长的时间,每次新建一个 demo 项目就会要重新下载一次 gradle 下载倒还是小事,问题是下载的像乌龟一样,明明挂了加速器,仍然下的好慢,在网上一查说是要用阿里云的加速库就会快一些,结果到现在我都不知道配置正确没有,配置了以后仍然在 gradle 的官网上面给我拉去,真的有一种吐血的想法,好在现在已经下载过许多次了,直接复制之前项目的 .gradle 与 gradle 目录就好了 因为目前我使用的 gradle 版本都是 8.0 的保证环境的一定性是学习的前提!
Hello World! 是编程爱好者新接触一个语言比接触的一个 Demo AS的 Hello World!
需要以界面的形式存在,在命名的时候就需要注意[code] Hardcoded string [/code]问题,我们要用@string
的方式将静态资源存放到 resource 里面就不会报提示三角形了
我们可以将这些常量信息保存到 values 里面,values 文件夹也分为 string.xml color.xml theme.xml 默认是这三个,就可以将数据在里面定义 调用再使用 [code]@string/id[/code] 取出来!
在之前使用 iApp 编程的时候,我无不赞叹线性布局是个好东西,相对布局有时候也有妙用,但是在 Android Studio 里面 ConstraintLayout 瞬间吸引到我了他可以创建百分比布局,可以解决不同尺寸的设备界面显示问题
说到 ConstraintLayout 布局就不能不提到他的 guideline 组件了 完美的辅助线来对控件进行排布,而不会是单纯限于线性布局的 dp 大法 十分的银杏?
1. 编程语言之首——Java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
2. 嵌入式领域的王者——C语言
#include<stdio.h>
int main(void) {
printf("Hello World!\n");
return 0;
}
3. 通用型脚本语言——Python
print("Hello World")
4. Web前端开发主流语言——JavaScript
console.log("Hello, World")
5. 世界范围内网站运用率最高的编程语言——PHP
<?php
echo "Hello World";
?>
6. 多范式编程语言——C++
#include<iostream>
Using namespace std;
int main(){
cout << "\nHello World!";
return 0;
}
7. 高级程序编程语言——C#
using System;
namespace helloWorld{
class HelloWorld
{
static void Main(String[] args)
{
Console.WriteLine("Hello World!");
}
}
}
8. 基于对象的程序设计语言——Visual Basic
subsub main
msgbox "Hello World!"
end sub
}
9. 统计分析的可编程语言——R
cat("helloworld")
10. 静态强类型、编译型、并发型、并具有垃圾回收功能的编程语言——GO
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
在Google I/O 2018发布了JetPack之后,Android应用架构和开发技术都发生了很大的变化。
Lifecycles 其实从名字看肯定是与生命周期相关,那它与生命周期又有什么联系?先参考一下官方文档:
Lifecycles 是一个生命周期感知组件,当 Activity 或者 Fragment 的生命周期发生改变的时会,Lifecycles也会做出相应的生命周期状态的改变,它保存关于组件生命周期状态的信息(比如活动或片段),并允许其他对象观察这种状态。
可以看出 Lifecycles 是一个组件,具有感知生命周期的功能,既然是个组件,那就说明可以嵌入到其他地方,比如 VewModel 便是其中之一。
我们知道,当我们需要处理与生命周期相关的组件的时候,在没有Lifecycles提供的时候,需要设置各种回调,如下所示:
class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}
void start() {
// connect to system location service
}
void stop() {
// disconnect from system location service
}
}
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;
@Override
public void onCreate(...) {
myLocationListener = new MyLocationListener(this, (location) -> {
// update UI
});
}
@Override
public void onStart() {
super.onStart();
myLocationListener.start();
// manage other components that need to respond
// to the activity lifecycle
}
@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
// manage other components that need to respond
// to the activity lifecycle
}
}
当组件数目过多的时候,便会需要在生命周期回调中管理多个组件从而徒增代码量,使得项目难以维护,而且在生命周期中执行过多的耗时操作极易引起内存泄漏,而这些都可以通过Lifecycles来解决。
看下Lifecycles的源码吧,反正也不长
public abstract class Lifecycle {
/**
* Lifecycle coroutines extensions stashes the CoroutineScope into this field.
*
* @hide used by lifecycle-common-ktx
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@NonNull
AtomicReference<Object> mInternalScopeRef = new AtomicReference<>();
//添加生命周期状态的观察者
@MainThread
public abstract void addObserver(@NonNull LifecycleObserver observer);
//这个也就是移除观察者
@MainThread
public abstract void removeObserver(@NonNull LifecycleObserver observer);
//获得当前生命周期状态
@MainThread
@NonNull
public abstract State getCurrentState();
//再熟悉不过的生命周期回调
//是从框架和生命周期类分派的生命周期事件,这些事件映射到活动和片段中的回调事件。
@SuppressWarnings("WeakerAccess")
public enum Event {
/**
* Constant for onCreate event of the {@link LifecycleOwner}.
*/
ON_CREATE,
/**
* Constant for onStart event of the {@link LifecycleOwner}.
*/
ON_START,
/**
* Constant for onResume event of the {@link LifecycleOwner}.
*/
ON_RESUME,
/**
* Constant for onPause event of the {@link LifecycleOwner}.
*/
ON_PAUSE,
/**
* Constant for onStop event of the {@link LifecycleOwner}.
*/
ON_STOP,
/**
* Constant for onDestroy event of the {@link LifecycleOwner}.
*/
ON_DESTROY,
/**
* An {@link Event Event} constant that can be used to match all events.
*/
ON_ANY
}
//这个也就此当前所处于的状态了
@SuppressWarnings("WeakerAccess")
public enum State {
/**
* Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
* any more events. For instance, for an {@link android.app.Activity}, this state is reached
* <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
*/
DESTROYED,
/**
* Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
* the state when it is constructed but has not received
* {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
*/
INITIALIZED,
/**
* Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
* <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
* </ul>
*/
CREATED,
/**
* Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached in two cases:
* <ul>
* <li>after {@link android.app.Activity#onStart() onStart} call;
* <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
* </ul>
*/
STARTED,
/**
* Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
* is reached after {@link android.app.Activity#onResume() onResume} is called.
*/
RESUMED;
/**
* Compares if this State is greater or equal to the given {@code state}.
*
* @param state State to compare with
* @return true if this State is greater or equal to the given {@code state}
*/
public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}
}
public class MyChronometer extends Chronometer implements LifecycleObserver {
private long time;
public MyChronometer(Context context) {
super(context);
}
public MyChronometer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyChronometer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
//以注解的形式完成监听
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private void stopMeter(){
time = SystemClock.elapsedRealtime() - getBase();
stop();
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private void resumeMeter(){
setBase(SystemClock.elapsedRealtime() - time);
start();
}
}
然后Activity如下:只需要添加观察者即可
public class LifecyclesActivity extends AppCompatActivity {
MyChronometer chronometer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lifecycles);
chronometer = findViewById(R.id.meter);
getLifecycle().addObserver(chronometer);
}
}
是不是感觉Activity与Chronometer之间解耦了,Chrononmeter自我实现了良好的封装,也方便了移植。
这也就是使用Lifecycles带来的好处。
ViewModel的出现主要为了解决两个问题:
1.当Actvitiy销毁重建过程中的数据恢复问题,虽然原来可以使用onSaveInstanceState()来完成,但是只支持能被序列化的数据而且是小量数据,对于大量数据则显得有点无力。
2.UI控制器的工作繁忙,UI控制器主要用于处理显示,交互,其他的额外操作可以委托给其他类完成,将不应该分配给UI的任务分离出来是必要的,这也就是上面所说的分离关注点原则
ViewModel是一个负责为Fragment/Activity配置和管理数据的类,同时处理Fragment/Activity与Application其余部分的通信。 |
---|
ViewModel始终与Scope(Fragment/Activity)相关联创建,同时一直保留当Scope存在时,例如Activity被finish。换句话说,ViewModel不会随着其拥有者因配置更改被销毁而销毁(旋转,横竖屏切换),新的拥有者会重新建立与ViewModel的关联。 |
ViewModel旨在获取和保存Activity或Fragment必要的信息,Activity或Fragment能够观察到ViewModel的数据改变,ViewModel通常通过LiveData或Android Data Binding暴露数据,也可以在你喜欢的框架中使用可观察构造。 |
ViewModel只负责管理UI数据,不应访问视图结构和持有Activity或Fragment的引用。 |
ViewModel在配置更改期间能自动保留其对象,以便它们所持有的数据可立即用于下一个 Activity 或片段Fragment
下面通过一个具体的实例:
MainActivity.java
public class MainActivity extends AppCompatActivity {
private MyViewModel myViewModel;
private TextView tv;
private Button button1, button2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
tv = findViewById(R.id.textView);
tv.setText(String.valueOf(myViewModel.num));
button1 = findViewById(R.id.button1);
button2 = findViewById(R.id.button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myViewModel.num++;
tv.setText(String.valueOf(myViewModel.num));
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myViewModel.num += 2;
tv.setText(String.valueOf(myViewModel.num));
}
});
}
}
MyViewModel.java
public class MyViewModel extends ViewModel {
public int num = 0;
}
布局如下:Welcome对应TextView,+1对象button1,+2对应button2
数据仍能得到保留,即使更改系统语言都可以,也就是说如果Activity被重建,将接收到第一个Activity创建的相同的MyViewModel实例。当所有者Activity完成后,会调用ViewModel对象的onCleared()方法,以便它能够清理资源。
同时这里也需要注意一个问题,当ViewModel持有外部引用的时候会阻止回收,所以ViewModel绝不能引用View、Lifecycle(也是Jetpack组件之一)或任何可能包含对Activity上下文的引用的类。
回到最上面的那个图,图说明了ViewModel的作用域涉及到整个生命周期,当获取ViewModel时,ViewModel的生命周期限定为传入ViewModelProvider的对象的生命周期。也就是对于以下场景(引用官方示例)
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
由于传入的是Activity,所以其作用域为整个Activity,不同的Fragment可以通过ViewModelProviders获取到同一个ViewModel,这样有以下的好处:
每个Fragment都有自己的生命周期,即使被替换也不会有任何影响。
我们知道,有些时候在Activity被意外杀死,如清理后台等会直接跳过onDestory()而是回调onSaveInstanceState()异常杀死下的生命周期,这个时候ViewModel也会被杀死,再次恢复的时候便会被重建,这样,原来的数据也就丢失了,因此我们需要改进一下ViewModel以支持异常退出情况下的重建。
首先很容易是想到通过onSaveInstanceState() 来保存,然后通过SaveInstanceState 来恢复,虽然也是一种可行的方法,但是好像全程跟ViewModel没什么关联,未免优点没有物尽其用,所以ViewModel也提供了类似SavedInstanceState的方法。
SavedStateHandle :用于保存状态的数据类型,是一个key-value的map,其实也就是类似Bundle。采用Hashmap来实现的。
具体使用:
public class ViewModelWithData extends ViewModel {
private MutableLiveData<Integer> number;
private SavedStateHandle handle;
private static final String KEY_NUMBER = "number";
public ViewModelWithData(SavedStateHandle handle) {
this.handle = handle;
number = new MutableLiveData<>();
number.setValue(0);
}
public MutableLiveData<Integer> getNumber() {//每次都通过SavedStateHandle来获取相应的值
if (!handle.contains(KEY_NUMBER)) {
handle.set(KEY_NUMBER, 0);
}
return handle.getLiveData(KEY_NUMBER);
}
public void addNumber(int n) {
getNumber().setValue(getNumber().getValue() + n);
}
}
public class LiveDataActivity extends AppCompatActivity {
private ViewModelWithData viewModelWithData;
ActivityLiveDataBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_live_data);
//需要将创建的SavedStateVMFactory传入
viewModelWithData = ViewModelProviders.of(this, new SavedStateVMFactory(this)).get(ViewModelWithData.class);
binding.setData(viewModelWithData);
binding.setLifecycleOwner(this);
}
}
好了,这样就完成了将VIewModel的生命周期与整个Activity的同步,包括异常情况下。
因为SharedPreference需要使用到Application来获取到,所以要想配合ViewModel还需要传入Application作为参数,当然,Jetpack已经为我们准备好了
AndroidViewModel:感知应用上下文的ViewModel,继承自ViewModel。
其实实现也很简单,传入Application就行了,需要时获取。
public class AndroidViewModel extends ViewModel {
@SuppressLint("StaticFieldLeak")
private Application mApplication;
public AndroidViewModel(@NonNull Application application) {
mApplication = application;
}
/**
* Return the application.
*/
@SuppressWarnings("TypeParameterUnusedInFormals")
@NonNull
public <T extends Application> T getApplication() {
//noinspection unchecked
return (T) mApplication;
}
}
然后剩下的便可以通过获取SharedPreference来存储数据,再配合LiveData,SaveStataHandler实现跨越整个App的生命周期同步。当然,数据的持久化存储不仅仅时这一种方式,配合任意方式都可。
特别对于数据库这类数据,由于需要保持UI与数据库间的数据同步,常常导致在UI控制器(Activity等)进行数据加载,这有违分离关注点的原则。
使用VIewModel配合Room和LiveData能够方便的取代CusorLoader的操作,VIewModel保证数据的稳定性,Room通知数据更改,LiveData则自动使用更新的数据更新UI。
以上便是ViewModel在整个Jetpack的作用和功能了,现在只是冰山一角,慢慢学习。
LiveData是一个可观察的数据持有者类,不过它和其他的可观察对象不同,它会与生命周期相关联,比如Activity的生命周期,LiveData能确保仅在Activity处于活动状态下才会更新。也就是说当观察者处于活动状态,才会去通知数据更新。
个人觉得这是为了避免内存泄漏,可以说是很实用了,因为要想避免内存泄漏,必须要感知到生命周期,而原本并没有提供额外的方法,像Glide采用了一个透明的Fragment来感知Activity的生命周期,这虽然是一个可行的方法,但总共感觉并不是一个最优的方法。
下面是官方说明的使用LiveData的优点
不需要手动控制生命周期
下面便是LiveData在MVVM中的角色
LiveData是配合ViewModel使用的
ViewModelWithData.java
public class ViewModelWithData extends ViewModel {
private MutableLiveData<Integer> number;
//注意这个构造方法需要public,因为他是通过方式来创建实例的
public ViewModelWithData(){
number = new MutableLiveData<>();
number.setValue(0);
}
public MutableLiveData<Integer> getNumber() {
return number;
}
public void setNumber(MutableLiveData<Integer> number) {
this.number = number;
}
public void addNumber(int n){
number.setValue(number.getValue() + n);
}
}
LiveDataActivity.java
public class LiveDataActivity extends AppCompatActivity {
private ViewModelWithData viewModelWithData;
private TextView textView;
private ImageButton imageButton1, imageButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_data);
textView = findViewById(R.id.tv1);
imageButton1 = findViewById(R.id.ib1);
imageButton2 = findViewById(R.id.ib2);
viewModelWithData = ViewModelProviders.of(this).get(ViewModelWithData.class);
//这里添加对viewModelWithData的对象的观察者
viewModelWithData.getNumber().observe(this, new Observer<Integer>() {
@Override
public void onChanged(Integer integer) {
textView.setText(String.valueOf(integer));//响应数据变化
}
});
imageButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModelWithData.addNumber(1);
}
});
imageButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
viewModelWithData.addNumber(-1);
}
});
}
}
其实感觉这个liveData和rxJava大同小异
官方示例代码
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});