最全 Android 常用开源库总结!
Android程序调试
在 Android Studio 调试应用
Adb常用命令收录
热门Android Studio 插件,这里是Top 20
Android shape属性大全
UML学习链接
大幅提高Android开发效率之Android项目模板化
idea中live template的详细使用教程
Android Studio Debug 的 9 个小技巧
设计模式
这一次,彻底了解 Gradle 吧!
Gradle 与 Android 构建入门
AppTheme属性设置集合
Android中style和theme的区别
Style和Theme的有哪些不同点和相同点
不同点:
相同点:
Android Fragment 非常详细的一篇
FragmentTwo
public class FragmentTwo extends Fragment implements View.OnClickListener {
private String mParam;
private static String ARG_PARAM = "param_key";
private Activity mActivity;
@Override
public void onAttach(Context context) {
Log.e("tony", "onAttach");
super.onAttach(context);
// 如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象。 参考链接https://www.jianshu.com/p/11c8ced79193
mActivity = (Activity) context;
mParam = getArguments().getString(ARG_PARAM); //获取参数
}
public interface FOneBtnClickListener {
void onFOneBtnClick();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentlayout_two, container, false);
TextView textView = view.findViewById(R.id.textView);
textView.setText(mParam);
Button button = view.findViewById(R.id.button);
button.setOnClickListener(this);
return view;
}
@Override
public void onClick(View view) {
if(mActivity!=null) {
((FOneBtnClickListener) mActivity).onFOneBtnClick();
}
}
public static FragmentTwo newInstance(String str) {
FragmentTwo fragment = new FragmentTwo();
Bundle bundle = new Bundle();
bundle.putString(ARG_PARAM, str);
fragment.setArguments(bundle); //设置参数
return fragment;
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements FragmentTwo.FOneBtnClickListener {
private FragmentTwo fragmentTwo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("tony", "onCreate");
// // 添加Fragment前检查是否有保存的Activity状态。如果没有状态保存,说明Acitvity是第1次被创建,我们添加Fragment。如果有状态保存,说明Activity刚刚出现过异常被销毁过,现在正在恢复,我们不再添加Fragment。
// //作者:梦想编织者灬小楠
// //链接:https://www.jianshu.com/p/5761ee2d3ea1
// if(savedInstanceState == null){
// getSupportFragmentManager().beginTransaction()
// .add(R.id.container, FragmentOne.newInstance("hello world"), "f1") //.addToBackStack("fname")
// .commit();
// }
FragmentManager fm = getSupportFragmentManager();
fragmentTwo = (FragmentTwo) fm.findFragmentById(R.id.container);
if(fragmentTwo == null){
// 传递参数
fragmentTwo = FragmentTwo.newInstance("MainActivity传递到FragmentTwo的参数");
fm.beginTransaction().add(R.id.container,fragmentTwo).commit();
}
}
// FragmentTwo的button的点击事件
@Override
public void onFOneBtnClick() {
Toast.makeText(MainActivity.this,"onFOneBtnClick",Toast.LENGTH_LONG).show();
}
}
Fragment 与 Activity 通信
Fragment 返回数据给 Activity
在Fragment中定义接口,并让Activity实现该接口
Activity传递参数给Fragment
使用setArguments方法,并在onAttach()中调用getArguments()获取参数
getSupportFragmentManager().beginTransaction()
.add(R.id.container, Fragment1.newInstance("hello world"), "f1")
// 第三个参数是fragment的tag名,指定tag的好处是后续我们可以通过
Fragment1 frag = getSupportFragmentManager().findFragmentByTag("f1")
// 从FragmentManager中查找Fragment对象。
addToBackStack("fname")
是可选的。FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1)
,那么回退操作就是remove(frag1)
;如果没添加该语句,用户点击返回按钮会直接销毁Activity。
Fragment的replace、add、remove方法
Fragment涉及的add、remove和replace方法和回退栈的关系详解
add、remove和replace方法和回退栈基本没有关系。
这里有一个点非常容易混淆,就是因为add也是一层一层的往FrameLayout里添加Fragment,那么在按回退按钮的时候是不是一层一层的再拿出来呢?笔者这里告诉大家,根本不会,除非加入了回退栈。Fragment在ViewPager中的生命周期
使用FragmentPagerAdapter时 页面切换,只是调用detach,而不是remove,所以只执行onDestroyView,而不是onDestroy,不会摧毁Fragment实例,只会摧毁Fragment 的View;
使用FragmentStatePageAdapter时 页面切换,调用remove,执行onDestroy。直接摧毁Fragment。
FragmentPagerAdapter最好用在少数静态Fragments的场景,用户访问过的Fragment都会缓存在内存中,即使其视图层次不可见而被释放(onDestroyView) 。因为Fragment可能保存大量状态,因此这可能会导致使用大量内存。
页面很多时,可以考虑FragmentStatePagerAdapter
FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:
- add(): onAttach()->…->onResume()。
remove(): onPause()->…->onDetach()。- replace(): 相当于旧Fragment调用remove(),新Fragment调用add()。
- show(): 不调用任何生命周期方法,调用该方法的前提是要显示的 Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。
- hide(): 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。
- detach(): onPause()->onStop()->onDestroyView()。UI从布局中移除,但是仍然被FragmentManager管理。
- attach(): onCreateView()->onStart()->onResume()。
- 切换到该Fragment,分别执行onAttach()、onCreate()、onCreateView()、onActivityCreated()、onstart()、onResume()方法。
- 锁屏,分别执行onPause()、onStop()方法。
- 亮屏,分别执行onstart()、onResume()方法。
- 覆盖,切换到其他Fragment,分别执行onPause()、onStop()、onDestroyView()方法。
- 从其他Fragment回到之前Fragment,分别执行onCreateView()、onActivityCreated()、onstart()、onResume()方法。
关于Android架构,你是否还在生搬硬套
MVC即Model、View、Controller即模型、视图、控制器
XML/JAVA
显示。Activity
/Frgament
也承担了View的功能。Activity
、Fragment
之中。MVC 的缺点
- Activity并不是MVC中标准的Controller,既有Controller的职责也有View的职责,导致Activity的代码过于臃肿。
- View层和Model层互相耦合,耦合过重,代码量过大,不易于开发和维护。
Android MVP 架构
参考链接2
MVP在实现代码简洁的同时,额外增加了大量的接口、类,不方便进行管理。通过Contract,将Model、View、Presenter 进行约束管理,方便后期类的查找、维护。
- MVP中绝对不允许View直接访问Model
- 本质是增加了一个接口降低一层耦合度
3.MVP Presenter完全将Model和View解耦,主要逻辑处于Presenter中。
- Model-View-ViewModel,将Presenter替换为ViewModel。
- ViewModel和Model/View进行了双向绑定。
- View发生改变时,ViewModel会通知Model进行更新数据
- Model数据更新后,ViewModel会通知View更新显示
- 谷歌发布了MVVM支持库Data Binding:能将数据绑定到xml中
- 现在谷歌又推出了ViewModel和LiveData组件用于更方便的实现MVVM
解决了各个层级之间耦合度太高的问题,也就是更好的完成了解耦。MVP层中,Presenter还是会持有View的引用,但是在MVVM中,View和Model进行双向绑定,从而使viewModel基本只需要处理业务逻辑,无需关系界面相关的元素了。
解决了代码量太多,或者模式化代码太多的问题。由于双向绑定,所以UI相关的代码就少了很多,这也是代码量少的关键。而这其中起到比较关键的组件就是DataBinding,使所有的UI变动都交给了被观察的数据模型。
解决了可能会有的内存泄漏问题。MVVM架构组件中有一个组件是LiveData,它具有生命周期感知能力,可以感知到Activity等的生命周期,所以就可以在其关联的生命周期遭到销毁后自行清理,就大大减少了内存泄漏问题。
解决了因为Activity停止而导致的View空指针问题。在MVVM中使用了LiveData,那么在需要更新View的时候,如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。也就是他会保证在界面可见的时候才会进行响应,这样就解决了空指针问题。
解决了生命周期管理问题。这主要得益于Lifecycle组件,它使得一些控件可以对生命周期进行观察,就能随时随地进行生命周期事件。
如何避免后台进程被杀死:
调用startForegound
,让你的Service所在的线程成为前台进程
Service的onStartCommond
返回START_STICKY或START_REDELIVER_INTENT
Service的onDestroy
里面重新启动自己
Android开发基础之服务Service
Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户
界面的应用组件,Service是使程序后台运行的保证.
Service与Activity的区别:
当 Activity 被 finish 之后,你不再持有该 Thread 的引用,并且没有办法在不同的 Activity 中对同一 Thread 进行控制。而任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例
startService --> onCreate()--> onStartCommand()--> stopService-->onDestroy()
1.多次调用时onCreate方法只执行一次,onStartConmon()会被多次调用。
2.当我们通过startService启动时候,通过intent传值,在onStartCommand()方法中获取值的时候,一定要先判断intent是否为null
onCreate()-->onBind()-->unBind()-->onDestroy()
1.这种方式更方便Activity与Service的通信,activity获取ServiceConnection对象,通过ServiceConnection来获取service中内部类的类对象
2.绑定服务的生命周期跟Activity是不求同时生,但求同时死,Activity没了,服务也要解绑;
3.绑定服务开启的服务,只可以解绑一次,多次解绑会抛异常;
1、startService这个方法来启动服务的话,是长期运行的,只有stopService才会停止服务。而bindService来启动服务,不用的时候,需要调用unBindService,否则会导致context泄漏,所以bindService不是长期运行的。当context销毁的时候,则会停止服务运行。
2、startService来启动服务可以长期运行,但是不可以通讯,而bindService的方式来启动服务则可以通讯,两者都有优缺点,所以我们就有了混合起来使用的方法。
public class BindService extends Service {
private static final String TAG = "BindService";
public class CommunicateBinder extends Binder {
void callInnerMethod(){
innerMethod();
}
}
private void innerMethod() {
Log.d(TAG, "innerMethod was called...");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return new CommunicateBinder();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
}
public class BindServiceActivity extends AppCompatActivity implements View.OnClickListener {
private Button bt_bindService, bt_unbindService, bt_callSeviceMethod;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
bt_bindService = findViewById(R.id.bindService);
bt_bindService.setOnClickListener(this);
bt_unbindService = findViewById(R.id.unbindService);
bt_unbindService.setOnClickListener(this);
bt_callSeviceMethod = findViewById(R.id.bt_callSeviceMethod);
bt_callSeviceMethod.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.bindService:
//创建意图对象
Intent intent = new Intent(this, BindService.class);
// 第一个是参数是意图对象,第二个参数是回调,第三个参数是标记,这个是自动创建的意,如果服务没有start,那么会自己创建。
bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
break;
case R.id.unbindService:
//解绑服务
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
break;
case R.id.bt_callSeviceMethod:
//调用服务里的方法
callServiceMethod();
break;
default:break;
}
}
private BindService.CommunicateBinder mCommunicateBinder;
private ServiceConnection mServiceConnection = new ServiceConnection(){
// 当调用bindService方法后就会回调Activity的onServiceConnected,在这个方法中会向Activity中传递一个IBinder的实例,Acitity需要保存这个实例
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
if (service instanceof BindService.CommunicateBinder) {
mCommunicateBinder = (BindService.CommunicateBinder) service;
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//这个方法只有出现异常时才会调用,服务正常退出不会调用。
}
};
(1)这两个方法来自不同的类,sleep是来自Thread,wait是来自Object;
(2)sleep方法没有释放锁,而wait方法释放了锁。
(3)wait,notify,notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
Android的View事件分发
Android ViewGroup事件分发机制
View事件的运行顺序为:
dispatchTouchEvent—> onTouch (setOnTouchListener) —> onTouchEvent
VeiwGroup事件的运行顺序:
VeiwGroup的dispatchTouchEvent -> VeiwGroup的onInterceptTouchEvent(需要拦截,只要return true) ->View的dispatchTouchEvent ->View的onTouchEvent
如果最后一个view没有消费事件 ,事件就会依次回传,传递到最高位的Activity,如果Activity也没有消费事件,这个事件就会被抛弃。
子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
在我们点击屏幕时,会有下列事件发生:
事件分发的主要有三个关键方法
Activity首先调用dispathTouchEvent()进行分发,接着调用super向下传递
ViewGroup首先调用dispathTouchEvent()进行分发,接着会调用onInterceptTouchEvent()(拦截事件)。若拦截事件返回为true,表示拦截,事件不会向下层的ViewGroup或者View传递;false,表示不拦截,继续分发事件。默认是false,需要提醒一下,View是没有onInterceptTouchEvent()方法的
事件在ViewGroup和ViewGroup、ViewGroup和View之间进行传递,最终到达View;
View调用dispathTouchEvent()方法,然后在OnTouchEvent()进行处理事件;OnTouchEvent() 返回true,表示消耗此事件,不再向下传递;返回false,表示不消耗事件,交回上层处理。
// 1.直接用intent.putExtra
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
intent.putExtra("name", "Nicole");
intent.putExtra("age", 25);
intent.putExtra("address", "Shenzhen");
//2.用Bundle
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
Bundle bundle = new Bundle();
bundle.putString("name", "Ben");
bundle.putInt("age", 28);
bundle.putString("address", "China");
intent.putExtras(bundle); //将bundle传入intent中。
Intent传递数据和Bundle传递数据的区别?
Intent传递数据和Bundle传递数据是一回事,Intent传递时内部还是调用了Bundle。
// Intent putExtra源码
public @NonNull Intent putExtra(String name, Bundle value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putBundle(name, value); //使用bundle添加参数
return this;
}
public @NonNull Intent putExtra(String name, String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value); //使用bundle添加参数
return this;
}
public @NonNull Intent putExtra(String name, int value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putInt(name, value); //使用bundle添加参数
return this;
}
public @NonNull Intent putExtra(String name, Parcelable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putParcelable(name, value); //使用bundle添加参数
return this;
}
public @NonNull Intent putExtra(String name, Serializable value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putSerializable(name, value); //使用bundle添加参数
return this;
}
... 其他的类型
Serializable Java 序列化接口 在硬盘上读写 读写过程中有大量临时变量的生成,内部执行大量的i/o操作,效率很低。
Parcelable Android 序列化接口 效率高 使用麻烦 在内存中读写(AS有相关插件 一键生成所需方法) ,对象不能保存到磁盘中
使用 Intent 来传递对象通常有两种实现方式,Serializable 和 Parcelable,本小节中我们先来学习一下第一种的实现方式。
Serializable 是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个类去实现 Serializable 这个接口就可以了。
比如说有一个 Person 类,其中包含了 name 和 age 这两个字段,想要将它序列化就可以这样写
public class Person implements Serializable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
其中 get、set 方法都是用于读取和赋值字段数据的,最重要的部分是在第一行。这里让 Person 类去实现了 Serializable 接口,这样所有的 Person 对象就都是可序列化的了。
接下来在 FirstActivity 中的写法非常简单:
Person person = new Person();
person.setName("Tom");
person.setAge(20);
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
intent.putExtra("person_data", person);
startActivity(intent);
可以看到,这里我们创建了一个 Person 的实例,然后就直接将它传入到 putExtra()方法中了。由Person 类实现了 Serializable 接口,所以才可以这样写。
接下来在 SecondActivity 中获取这个对象也很简单,写法如下:
Person person = (Person) getIntent().getSerializableExtra("person_data");
这里调用了 getSerializableExtra()方法来获取通过参数传递过来的序列化对象,接着再将它向下转型成 Person 对象,这样我们就成功实现了使用 Intent 来传递对象的功能了。
除了 Serializable 之外,使用 Parcelable 也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable 方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是 Intent 所支持的数据类型,这样也就实现传递对象的功能了。
下面我们来看一下 Parcelable 的实现方式,修改 Person 中的代码,如下所示:
public class Person implements Parcelable {
private String name;
private int age;
⋯⋯
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name); // 写出name
dest.writeInt(age); // 写出age
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.
Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
Person person = new Person();
person.name = source.readString(); // 读取name
person.age = source.readInt(); // 读取age
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
Parcelable 的实现方式要稍微复杂一些。可以看到,首先我们让 Person 类去实现了Parcelable 接口,这样就必须重写 describeContents()和 writeToParcel()这两个方法。其中 describeContents()方法直接返回 0 就可以了,而 writeToParcel()方法中我们需要调用 Parcel 的 writeXxx()方法将 Person 类中的字段一一写出。注意字符串型数据就调用writeString()方法,整型数据就调用 writeInt()方法,以此类推。
除此之外,我们还必须在 Person 类中提供一个名为 CREATOR 的常量,这里创建了Parcelable.Creator 接口的一个实现,并将泛型指定为 Person。接着需要重写createFromParcel()和 newArray()这两个方法,在 createFromParcel()方法中我们要去读取刚才写出的 name 和 age 字段,并创建一个 Person 对象进行返回,其中 name 和 age都是调用 Parcel 的 readXxx()方法读取到的,注意这里读取的顺序一定要和刚才写出的顺
序完全相同。而 newArray()方法中的实现就简单多了,只需要 new 出一个 Person 数组,并使用方法中传入的 size 作为数组大小就可以了。
接下来在 FirstActivity 中我们仍然可以使用相同的代码来传递 Person 对象,只不过在 SecondActivity 中获取对象的时候需要稍加改动,如下所示:
Person person = (Person) getIntent().getParcelableExtra("person_data");
注意这里不再是调用 getSerializableExtra()
方法,而是调用 getParcelableExtra()
方法来获取传递过来的对象了,其他的地方都完全相同。
Message
Message是在线程之间传递的信息,它可以在内部携带少量的信息,用于在不同线程之间交换信息。
Handler
Handler 顾名思义,也就是处理者的意思,它主要用于发送和处理信息,发送信息一般是使用Handler的sendMessage()方法,而发出的一条信息,经过一系列辗转处理后,最终会传递到Handler HandleMessage方法中。
MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过handler发送的信息,这部分消息会一直存在于消息队列中,等待被处理,每个线程只会有一个MessageQueue对象。
Looper
Looper是每个线程的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出。并传递到Handler的HandelMessage方法中,每个线程只会有一个looper对象。
首先需要在主线程中创建一个Handler对象并重写handleMessage方法,然后当子线程中需要进行UI操作时,就创建一个Message对象并通过Handler将这条信息发送出去,之后这条信息会被添加到MessageQueue的队列当中等待被处理,Looper一直会尝试从MessageQueue中取出待处理信息,最后分发给Handler的handleMessage()方法。由于Handler是在主线程中创建的,所以此时handlerMessage()方法中的代码也会在主线程中运行。
Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,
我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法
不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。
Handler一定要在主线程实例化吗?new Handler()和new Handler(Looper.getMainLooper())的区别
不带参数的实例化:Handler handler = new Handler();那么这个会默认用当前线程的looper;
一般而言,如果你的Handler是要来刷新操作UI的,那么就需要在主线程下跑。
- 1.要刷新UI
handler要用到主线程的looper。那么在主线程 Handler handler = new Handler();,如果在其他线程,也要满足这个功能的话,要Handler handler = new Handler(Looper.getMainLooper());
- 2.不用刷新ui,只是处理消息。
- 当前线程如果是主线程的话,Handler handler = new Handler();
- 不是主线程的话
Handler handler = new Handler(Looper.getMainLooper());
或者手动调用Looper.prepare(); Handler handler = new Handler();Looper.loop();
在非主线程中直接new Handler() 会报错,原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。
主线程系统会自动为其创建Looper对象,开启消息循环。
Looper.prepare()和Looper.loop() —深入版
1.打开MainActivity
(MainActivity)onCreat --> (MainActivity)onStart --> (MainActivity)onResume
2.点击按钮跳转到SecondActivity
(MainActivity)onPause --> (SecondActivity)onCreate --> (SecondActivity)onStart --> (SecondActivity)onResume --> (MainActivity)onStop
3.点击按钮返回到MainActivity
(SecondActivity)onPause --> (MainActivity)onRestart --> (MainActivity)onStart --> (MainActivity)onResume --> (SecondActivity)onStop --> (SecondActivity)onDestory
4.app 返回到后台
onPasue --> onStop
5.由后台返回到APP
onRestart --> onStart --> onResume
1.onSaveInstanceState(Bundle outState)
调用时机 :
Activity 被销毁的时候调用, 也可能没有销毁就调用了;
按下Home键 : Activity 进入了后台, 此时会调用该方法;
按下电源键 : 屏幕关闭, Activity 进入后台;
启动其它 Activity : Activity 被压入了任务栈的栈底;
横竖屏切换 : 会销毁当前 Activity 并重新创建;
onSaveInstanceState方法调用注意事项
2.onRestoreInstanceState(Bundle outState)
1.如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍。
2.如果设置android:configChanges=“orientation|keyboardHidden|screenSize”>,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。
显示隐式跳转activity
显式启动 是明确指定了需要启动的Activity 或 service 的类名或包名。
隐式启动 不明确制定需要启动哪个Activity,而是通过设置action、data、 Category 等让系统来匹配出合适的目标
解锁Activity的跳转新姿势———使用scheme跳转
浏览器为什么能唤起App的Activity?
Android进阶-Context
Content与application都继承与contextWrapper,contextWrapper继承于Context类。
Context:表示当前上下文对象,保存的是上下文中的参数和变量,它可以让更加方便访问到一些资源。
Context通常与activity的生命周期是一样的,application表示整个应用程序的对象。
Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application.
在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了单线程串行执行任务。
AsyncTask的三个泛型参数说明
第一个参数:传入doInBackground()方法的参数类型
第二个参数:传入onProgressUpdate()方法的参数类型
第三个参数:传入onPostExecute()方法的参数类型,也是doInBackground()方法返回的类型
package com.zejian.handlerlooper;
import android.graphics.Bitmap;
import android.os.AsyncTask;
/**
* Created by zejian
* Time 16/9/4.
* Description:
*/
public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {
/**
* onPreExecute是可以选择性覆写的方法
* 在主线程中执行,在异步任务执行之前,该方法将会被调用
* 一般用来在执行后台任务前对UI做一些标记和准备工作,
* 如在界面上显示一个进度条。
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
}
/**
* 抽象方法必须覆写,执行异步任务的方法
* @param params
* @return
*/
@Override
protected Bitmap doInBackground(String... params) {
return null;
}
/**
* onProgressUpdate是可以选择性覆写的方法
* 在主线程中执行,当后台任务的执行进度发生改变时,
* 当然我们必须在doInBackground方法中调用publishProgress()
* 来设置进度变化的值
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
/**
* onPostExecute是可以选择性覆写的方法
* 在主线程中执行,在异步任务执行完成后,此方法会被调用
* 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为
* doInBackground方法中的返回值
* @param bitmap
*/
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
}
/**
* onCancelled是可以选择性覆写的方法
* 在主线程中,当异步任务被取消时,该方法将被调用,
* 要注意的是这个时onPostExecute将不会被执行
*/
@Override
protected void onCancelled() {
super.onCancelled();
}
}
1、HandlerThread作用
当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。
2、HanlderThread的优缺点
Android多线程:线程池ThreadPool全面解析
Android多线程:手把手教你全面学习神秘的Synchronized关键字
Android多线程:带你了解神秘的线程变量 ThreadLocal
1)ListView布局单一,RecycleView可以根据LayoutManger有横向,瀑布和表格布局。
2)自定义适配器中,ListView的适配器继ArrayAdapter;RecycleView的适配器继承RecyclerAdapter,并将范类指定为子项对象类.ViewHolder(内部类)。
3)ListView优化需要自定义ViewHolder和判断convertView是否为null。 而RecyclerView是存在规定好的ViewHolder。
4)绑定事件的方式不同,ListView是在主方法中ListView对象的setOnItemClickListener方法;RecyclerView则是在子项具体的View中去注册事件。
在android中Activity的最长执行时间是5秒。
BroadcastReceiver的最长执行时间则是10秒。
Service的最长执行时间则是20秒。
超出执行时间就会产生ANR。注意:ANR是系统抛出的异常,程序是捕捉不了这个异常的。
解决方法:
运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message 的方式做一些操作,比如更新主线程中的ui等)
应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。
正确的切换方式是 add(),切换时 hide(),add()另一个 Fragment;再次切换时,只需 hide()当前,show()另一个。
这样就能做到多个 Fragment 切换不重新实例化:
Android:这是一份全面 & 详细的动画入门学习指南
Android 逐帧动画:关于 逐帧动画 的使用都在这里了!
Android:这是一份全面 & 详细的补间动画使用教程
Android 属性动画:这是一篇全面 & 详细的 属性动画 总结&攻略
逐帧动画(Frame Animation)
加载一系列Drawable资源来创建动画,简单来说就是播放一系列的图片来实现动画效果,可以自定义每张图片的持续时间
补间动画(Tween Animation)
Tween可以对View对象实现一系列动画效果,比如平移,缩放,旋转,透明度等。但是它并不会改变View属性的值,只是改变了View的绘制的位置,比如,一个按钮在动画过后,不在原来的位置,但是触发点击事件的仍然是原来的坐标。
属性动画(Property Animation)
动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了
Android目前支持以下7种ABIs:
mips, mips64, X86, X86–64, arm64-v8a, armeabi, armeabi-v7a
默认情况下,为了使APP有更好的兼容性,我们使用Android Studio 或者命令打包时,会默认支持所有的架构,但相应的APK size 会疯狂的增大。对于用户来说,目标设备只需要其中一个版本,但当用户下载APK时,会全部下载(对用户来说相当的不友好)。怎么办呢?
abifilters 为我们提供了解决方案,abifilters为我们提供了选择适配指定CPU架构的能力,只需要在app下的build.gradle添加如下配置:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
if(Build.VERSION.SDK_INT >= 11/*HONEYCOMB*/) {
new searchAsyncTask(searchText)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
new searchAsyncTask(searchText)
.execute();
}
Android中子线程为什么不能更新UI
约束布局ConstraintLayout看这一篇就够了
一文看懂ConstraintLayout的用法
ConstraintLayout,看完一篇真的就够了么?
Constraintlayout 2.0:你们要的更新来了
xml中的android、app、tools你真的了解吗
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:app=“http://schemas.android.com/apk/res-auto”
xmlns:app="http://schemas.android.com/apk/res-auto
,因为res-auto可以引用所有的自定义包名。xmlns:tools=“http://schemas.android.com/tools”
apply:异步执行,没有返回值;
commit:同步执行,有返回值。
如果不考虑结果并且是在主线程执行推荐使用 apply;
需要确保操作成功且有后续操作的话,用 commit()
刘望舒-Android View体系
Android自定义View全解
自定义View,有这一篇就够了
Android 开发进阶: 自定义 View 1-1 绘制基础
函数 | 作用 | 相关方法 |
---|---|---|
measure() | 测量View的宽高 | measure(),setMeasuredDimension(),onMeasure() |
layout() | 计算当前View以及子View的位置 | layout(),onLayout(),setFrame() |
draw() | 视图的绘制工作 | draw(),onDraw() |
测量模式 | 表示意思 |
---|---|
UNSPECIFIED | 未指定模式,父容器没有对当前View有任何限制,当前View可以任意取尺寸 |
EXACTLY | 精确模式,对应于match_parent属性和具体的数值,父容器测量出View所需要的大小,也就是specSize的值。 |
AT_MOST | 最大模式,对应于wrap_comtent属性,只要尺寸不超过父控件允许的最大尺寸就行。 |
而上面的测量模式跟我们的引用自定义View时在xml中设置wrap_content、match_parent以及写成固定的尺寸有什么对应关系呢?
match_parent
—>EXACTLY
。怎么理解呢?match_parent就是要利用父布局给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。
wrap_content
—>AT_MOST
。怎么理解:就是我们想要将大小设置为包裹我们的view内容,那么尺寸大小就是父布局给我们作为参考的尺寸,只要不超过这个尺寸就可以啦,具体尺寸就根据我们的需求去设定。
固定尺寸(如100dp)
—>EXACTLY
。用户自己指定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主啦。
private int getMySize(int defaultSize, int measureSpec) {
int mySize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
mySize = defaultSize;
break;
}
case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
//我们将大小取最大值,你也可以取其他值
mySize = size;
break;
}
case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
mySize = size;
break;
}
}
return mySize;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMySize(100, widthMeasureSpec);
int height = getMySize(100, heightMeasureSpec);
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
}
public void draw(Canvas canvas) {
...
//步骤1:绘制View的背景
drawBackground(canvas);
...
//步骤2:如果需要的话,保存canvas的涂层,为fading做准备
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left,top,right, top + length, null, flags);
...
//步骤3:绘制View的内容
onDraw(canvas);
...
//步骤4:绘制View的子View
dispatchDraw(canvas)
...
//步骤5:如果需要的话,绘制View的fading边缘并恢复图层
canvas.drawRect(left, top, right, top +length, p);
...
canvas.restoreToCount(saveCount);
...
//步骤6: 绘制View的装饰(例如滚动条)
onDrawScrollBars(canvas)
...
Android 代码动态布局 LayoutParams 使用
LayoutParams 的作用是:子控件告诉父控件,自己要如何布局。
public View inflate(int resource, ViewGroup root, boolean attachToRoot)
它们其实是用于设置 View 在布局中的大小的,也就是说,首先 View 必须存在于一个布局中,layout_width 和 layout_height 才能生效。
平时在 Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和 layout_height 都是有作用的。确实,这主要是因为,在 setContentView()
方法中,Android 会自动在布局文件的最外层再嵌套一个 FrameLayout,所以layout_width 和 layout_height 属性才会有效果。
- 1.自定义属性的声明和获取
- 2.分析需要的自定义属性
在res/values/attrs.xml定义声明
在layout文件中进行使用
在View的构造方法中进行获取- 3.测量onMeasure
布局onLayout(ViewGroup)- 4.绘制onDraw
- 5.处理点击事件
- 6.状态的恢复与保存
include标签
include 就是为了解决重复定义相同布局的问题。
include 使用注意
1.一个xml布局文件有多个include标签需要设置ID,才能找到相应子View的控件,否则只能找到第一个include的layout布局,以及该布局的控件
2.include标签如果使用layout xx属性,会覆盖被include的xml文件根节点对应的layoutxx属性,建议在include标签调用的布局设置好宽高位置,防止不必要的bug
3.include 添加id,会覆盖被include的xml文件根节点ID,这里建议include和被include覆盖的xml文件根节点设置同名的ID,不然有可能会报空指针异常
4.如果要在include标签下使用RelativeLayout,如layout_margin等其他属性,记得要同时设置layout_width和 layout_height,不然其它属性会没反应
merge 标签
merge标签主要用于辅助 include标签,在使用 include后可能导致布局嵌套过多,多余的 layout 节点或导致解析变慢(可通过 hierarchy viewer 工具查看布局的嵌套情况)。官方文档说明:merge 用于消除视图层次结构中的冗余视图,例如根布局是Linearlayout,那么我们又 include 一个 LinerLayout 布局就没意义了,反而会减慢 UI 加载速度。
merge 官方文档
merge 标签使用注意
1.因为merge标签井不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点. 2.因为merge不是View,所以对merge标签设置的所有属性都是无效的.
3.注意如果include的layout用了merge,调用include的根布局也使用了merge标签,那么就失去布局的属性了 4. merge标签必须使用在根布局
5.ViewStub标签中的layout布局不能使用merge标签
ViewStub 标签
ViewStub 标签最大的优点是当你需要时才会加载,使用它并不会影响 UI 初始化时的性能.各种不常用的布局像进度条、显示错误消息等可以使用 ViewStub标签,以减少内存使用量,加快渲染速度.ViewStub 是一个不可见的,实际上是把宽高设置为 0 的 View.效果有点类似普通的 view.setVisible(),但性能体验提高不少
ViewStub 标签使用注意点
- ViewStub标签不支持merge标签
- ViewStub的inflate只能被调用一次,第二次调用会抛出异常,setVisibility可以被调用多次,但不建议这么做(ViewStub 调用过后,可能被GC掉,再调用setVisibility()会报异常)
- 为ViewStub赋值的android∶layout_XX属性会替换待加载布局文件的根节点对应的属性
Space 组件
在 ConstraintLayout 出来前,我们写布局都会使用到大量的 margin 或padding,但是这种方式可读性会很差,加一个布局嵌套又会损耗性能鉴于这种情况,我们可以使用 space,使用方式和 View一样,不过主要用来占位置,不会有任何显示效果
Android 优化之路(一)布局优化
Android 广播
(1)按照发送的方式分类
- 标准广播
是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。- 有序广播
是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。
(2)按照注册的方式分类
- 动态注册广播
顾名思义,就是在代码中注册的。- 静态注册广播
动态注册要求程序必须在运行时才能进行,有一定的局限性,如果我们需要在程序还没启动的时候就可以接收到注册的广播,就需要静态注册了。主要是在AndroidManifest中进行注册。
(3)按照定义的方式分类
- 系统广播
Android系统中内置了多个系统广播,每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。- 自定义广播
由应用程序开发者自己定义的广播
(1)实现一个广播接收器
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
(2)注册广播
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
myBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(myBroadcastReceiver, intentFilter);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
sendBroadcast(intent); // 发送广播
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
}
}
Android SQLite使用
1.SQL SELECT 语句
SELECT 语句用于从表中选取数据。
从名为 "Persons" 的数据库表,获取名为 "LastName" 和 "FirstName" 的列的内容
SELECT LastName,FirstName FROM Persons
2. WHERE 子句
有条件地从表中选取数据,可将 WHERE 子句添加到 SELECT 语句。
SELECT 列名称 FROM 表名称 WHERE 列 运算符 值
从名为 "Persons" 的数据库表,选取居住在城市 "Beijing" 中的人
SELECT * FROM Persons WHERE City='Beijing'
3.AND 和 OR 运算符
选取所有姓为 "Carter" 并且名为 "Thomas" 的人:
SELECT * FROM Persons WHERE FirstName='Thomas' AND LastName='Carter'
选取所有姓为 "Carter" 或者名为 "Thomas" 的人:
SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'
4.INSERT INTO 语句
INSERT INTO 语句用于向表格中插入新的行。
INSERT INTO 表名称 (列1, 列2,...) VALUES (值1, 值2,....)
INSERT INTO Persons (LastName, Address) VALUES ('Wilson', 'Champs-Elysees')
5.Update 语句
Update 语句用于修改表中的数据。
UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
将列名称为LastName = 'Wilson',对应的Address修改为'Zhongshan 23',City修改为'Nanjing'
UPDATE Person SET Address = 'Zhongshan 23', City = 'Nanjing'
WHERE LastName = 'Wilson'
6.DELETE 语句
DELETE 语句用于删除表中的行。
DELETE FROM 表名称 WHERE 列名称 = 值
Android:这是一份全面 & 详细的Webview使用攻略
Android:你要的WebView与 JS 交互方式 都在这里了
Android:手把手教你构建 全面的WebView 缓存机制 & 资源加载方案
Android WebView 性能轻量优化
满满的WebView优化干货,让你的H5实现秒开
网易考拉移动团队-如何设计一个优雅健壮的Android WebView?(上)
网易考拉移动团队-如何设计一个优雅健壮的Android WebView?(下)
JsBridge 实现 JavaScript 和 Java 的互相调用
WebView与JavaScript交互——JsBridge
shouldOverrideUrlLoading返回值的处理及webview重定向造成无法返回的解决
Android WebView 优化梳理
类名 | 作用 | 常用方法 |
---|---|---|
Webview | 创建对象 生命周期管理 状态管理 |
●loadUrl (): 加载网页 ●goBack: () :后退 |
WebSettings | 配置&管理WebView | ●与JS交互: setJavaScriptEnabled () ●缓存: setCacheMode () |
WebViewClient | 处理各种通知&请求事件 | ●shouldOverrideUrlL oading (): 如果遇到了重定向,或者点击了页面中的a标签实现页面跳转,那么会回调这个方法 ●shouldInterceptRequest(): 无论是普通的页面请求(使用GET/POST),还是页面中的异步请求,或者页面中的资源请求,都会回调这个方法,给开发一次拦截请求的机会。 ●onPageStarted () :载入页面调用(如加载进度) ●onPageFinished () :页面加载结束时调用 |
WebChromeClient | 辅助WebView处理Javascript对话框 | ●onProgressChanged () :获得网页的加载进度&显示 ●onReceivedTitle () :获取Web页中的标题 ●onJsAlert () :支持javascript的警告框 ●onJsConfrm () :支持javascript的确认框 onJsPrompt () :支持javascript输入框 |
类型 | 调用方式 | 优点 | 缺点 | 使用场景 |
---|---|---|---|---|
Android调用JS | loadUr () | 方便简洁 | 效率低、获取返回值麻烦 | 不需要获取返回值,对性能要求较低时 |
evaluateJavascript () | 效率高 | 向下兼容性差 (仅Android 4.4以上可用) |
Android 4.4以上 | |
JS调用Android | 通过addJavascriptInterface ()进行添加对象映射 | 方便简洁 | Android 4. 2以下存在漏洞问题 | Android 4.2以上相对简单互调场景 |
通过WebViewClient.shouldOverrideUrlL oading ()回调拦截url | 不存在漏洞问题 | 使用复杂:需要进行协议的约束; 从Native层往Web层传递值比较繁琐 |
不需要返回值情况下的互调场景 | |
通过WebChromeClient的onJsAler(). onJsConfirm()、onJsPrompt () 方法回调拦截JS对话框消息 | 不存在漏洞问题 | 使用复杂:需要进行协议的约束; | 能满足大多数情况下的互调场景 |
MutableContextWrapper是Context家族中一员。其显著特点是可以动态替换baseContext来达到预加载需要Context创建的资源。
腾讯祭出大招VasSonic,让你的H5页面首屏秒开
关于第四点,我们想分享一些Android平台上的细节,由于Android系统的生态原因,导致用户的系统版本和系统Webkit内核处于极其分裂状态,所以我们公司在手Q和微信统一使用X5内核。相对系统WebView来说,首次启动X5内核时,创建WebView比较耗时,因此我们尽量想复用WebView,但是WebView却是与Activity Context绑定。销毁复用的时候,需要释放Activity的Context,否则会内存泄露。针对这种情况,有没有一种两全其美的办法呢?
计算机有一句经典的名言:计算机领域任何一个问题都可以通过引入中间层来解决。于是我们通过包装的方式,实现了一个Context的壳,真正的实现体包装在里面,逻辑调用真正调用到对应的实现体的函数。 经过实验发现,Android系统本身提供了这么一个MutableContextWrapper,作为Context的一个中间层。
我们会将Activity context包在MutableContextWrapper里面,destory的时候,会将WebView的Context设置为Application的Context,从而释放Activity Context。
类似如下:
//precreate WebView
MutableContextWrapper contextWrapper = new MutableContextWrapper(BaseApplicationImpl.sApplication);
mPool[0] = new WebView(contextWrapper);
//reset WebView
ct =(MutableContextWrapper)webview.getContext();
ct.setBaseContext(getApplication());
//reuse WebView
((MutableContextWrapper)webview.getContext()).setBaseContext(activityContext);
缓存机制 | 原理 | 优点 | 应用场景 | 备注 |
---|---|---|---|---|
浏览器 | 根据HTTP协议头里的Cache-Control (或Expires)和 Last-Modified (或Etag)等字段来控制文件缓存的机制 | 支持Http协议层 | 存储静态文件(如JS、字体文件) | Android 4.4后的WebView浏览器版本内核:Chrome 浏览器缓存机制是浏览器内核的机制,一般是标准实现,不能更改或设置 |
Application Cache | 以文件为单位进行缓存,且文件有一定更新机制 (类似于浏览器缓存机制) | 方便构建Web App的缓存 | WebApp的商线缓存; 存储静态文件(如JS、字体文件) | 对浏览器缓存机制的补充; (根据官方文档,AppCache已经不推荐使用) |
Dom Storage | 通过存储字符串的Key - Value对来提供 | 存储空间大(5MB); 存储安全、便捷:Dom Storage存储的数据在本地 | 存储临时、简单的数据 (Cookie的扩展) | 代替将不需要让服务器知道的信息存储到cookies的这种传统方法; 类似于 Android 的 Shared Preference 机制 |
Web SQL Database | 基于SQL | 充分利用数据库的优势,可方便对数据进行増加、删 除、修改、查询 | 存储复杂、数据是大的结构化数据 | Web SQL Database存储机制不再推荐使用(不再维护); (使用IndexedDB代替) |
Indexed DB | 通过存储字符串的Key - Value对来提供 (同Dom Storage存储机制) | 功能强大、存储空间大、使用简单&灵活 (集合了 Dom Storage 和 Web SQL Database优点) | 存储复杂、数据量大的结构化数据 | 取代Web SQL Database存储机制 |
File System API | 提供一个虚拟的文件系统 (提供了临时&持久性的存糖空间) | 可存储数据体积较大的二进制数据 可预加载资源文件 可直接编辑文件 | 通过文件系统管理数据 | 虚拟的文件系统是运行在沙盒中 不同WebApp的虚拟文件系统是互相隔离的,虚拟文件系统与本地文件系 统也是互相隔离的 Android WebView不支持 |
在Android中使用多进程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidManifest中指定android:process
属性。
<activity
android:name="com.ryg.chapter_2.MainActivity"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
android:launchMode="standard" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity
android:name="com.ryg.chapter_2.SecondActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process=":remote" />
<activity
android:name="com.ryg.chapter_2.ThirdActivity"
android:configChanges="screenLayout"
android:label="@string/app_name"
android:process="com.ryg.chapter_2.remote" />
上面的示例分别为SecondActivity和ThirdActivity指定了process属性,并且它们的属性值不同,这意味 着当前应用又增加了两个新进程。假设当前应用的包名为com.ryg.chapter_2
,当SecondActivity启动时, 系统会为它创建一个单独的进程,进程名为com.ryg.chapter_2: remote
;当ThirdActivity启动时,系统也会为它创建一个单独的进程,进程名为com.ryg.chapter_2.remote
。同时入口Activity是MainActivity,没有 为它指定process属性,那么它运行在默认进程中,默认进程的进程名是包名。
用shell来查看,命令为:adb shell ps或者adb shell ps | grep com.ryg.chapter_2
其中com.ryg.chapter_2是包名,如图2-2所示,通过ps命令也可以查看一个包名中当前所存在的进程信息。
SecondActivity 和 ThirdActivity 的 android:process
属 性 分 别 为:remote
和com.ryg.chapter_2.remote
,那么这两种方式有区别吗?其实是有区别的,区别有两方面: 首先,:
的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写的方法,对于SecondActivity来说,它完整的进程名为com.ryg.chapter_2:remote
,这一点通过上图进程信息也能看出来,而对于ThirdActivity中的声明方式,它是一种完整的命名方式,不会附加包名信息;其次,进程名以:
开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以:
开头的进程属于全局进程。
我们知道Android为每一个应用分配 了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。
一般来说,使用多进程会造成如下几方面的问题:
为了更加清晰地展示Application会多次创建,下面我们来做一个测试,首先在Application的onCreate方法中打印出当前进程的名字,然后连续启动三个同一个应用内但属于不同进 程的Activity,按照期望,Application的onCreate应该执行三次并打印出三次进程名不同的log,代码如下所示。
public class MyApplication extends Application {
private static final String TAG = "MyApplication";
@Override
public void onCreate() {
super.onCreate();
String processName = MyUtils.getProcessName(getApplicationContext(),
Process.myPid());
Log.d(TAG,"application start,process name:" + processName);
}
}
通过log可以看出,Application执行了三次onCreate,并且每次的进 程名称和进程id都不一样,它们的进程名和我们为Activity指定的android:process属性一致。这也就证实了 在多进程模式中,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,这会给实际的开 发带来很多困扰,是尤其需要注意的。
名 称 | 优 点 | 缺 点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程间通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的即时通信 | 无并发访问情形,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需要处理好线程同步 | 对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通 信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle 支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求 |
Content Provider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call方法扩展其他操作 | 可以理解为受约束的AIDL, 主要提供数据源的CRUD操作 | 一对多的进程间的数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点烦琐,不支持直接的RPC | 网络数据交换 |
Android AIDL使用详解
参考视屏: 安卓高级开发—架构师/1.3.2 Binder核心原理/1.3.2.3NetEase_Binder
Android AIDL与proxy,stub
Android WebView独立进程解决方案
哔哩哔哩-构建跨进程webview架构
Mac环境下反编译apk
Thread.sleep()会抛出异常,而SystemClock.sleep()不会抛出异常 对于Android,推荐直接使用 SystemClock.sleep()即可 通过查看源码,发现 SystemClock.sleep() 其实调用的就是 Thread.sleep()方法 除了抛不抛异常,本质的区别是: SystemClock.sleep()不能被中断,无论如何都会让当前线程休眠指定的时间 而Thread.sleep()可以被中断,有可能在指定的休眠时间前被中断
Android中tools:replace的使用
当我们的项目的某些属性和第三方库中的属性有冲突时或者我们想修改第三方库中某些资源的属性时,我们就需要使用tools:replace来处理。
那么解决办法就是在你的Application节点中加入tools:replace来表示替换三方库中的相关属性,如下:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@drawable/box_icon"
android:label="@string/app_name"
android:theme="@style/AppTheme"
tools:replace="android:icon, android:label">
一篇文章搞懂android存储目录结构
彻底搞懂Android文件存储—内部存储,外部存储以及各种存储路径解惑
Android存储及getCacheDir()、getFilesDir()、getExternalFilesDir()、getExternalCacheDir()区别
Android-FileProvider-轻松掌握