糖葫芦


  • Startseite

  • Tags

  • Archiv

  • Suche

Animation动画相关知识点

Veröffentlicht am 2019-01-15 |

关于 Animation 的一些回顾和总结

关于Animation 和 Animator

Animation 是我们经常用到的一些动画,包括帧动画和补间动画,而 Animator 是Android 3.0 的时候推出的动画框架,也称为属性动画

版本兼容 实现效率 适用性 是否产生内存泄漏 使用效果
Animation 可兼容Android3.0以下的版本 直接通过代码对矩阵进行处理 仅对View对象有用 不会 假的移动
Animator 无法兼容,也没有向下兼容的support包 通过设置对象的setter,getter方法,来达到动画目的,使用了java反射机制,可用于任意对象,效率低于Animation 任意对象(但是需要有get/set方法) 可能会,当设置为无限循环时,如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏。 真的位置变换

关于动画的基本使用

  • Animation 相关动画的使用

    帧动画

    一般我们使用帧动画都是采取xml定义的方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // yun_anim.xml
    <?xml version="1.0" encoding="utf-8"?>
    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">

    <item
    android:drawable="@mipmap/app_loading0"
    android:duration="150" />
    <item
    android:drawable="@mipmap/app_loading1"
    android:duration="150" />
    <item
    android:drawable="@mipmap/app_loading2"
    android:duration="150" />
    <item
    android:drawable="@mipmap/app_loading3"
    android:duration="150" />

    </animation-list>
    //将它定义成一个drawable,这样引用
    <ImageView
    android:id="@+id/img_progress"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/yun_anim" />

    补间动画

    包括一些平移(Translate),旋转(Rotate),透明度(Alpha),缩放动画(Scale),使用都差不多,可以在xml中创建使用也可以动态创建使用。

    xml 中类似于这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //这是一个动画集合
    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
    android:duration="200"
    android:fromYDelta="100%p"
    android:toYDelta="0" />
    </set>
    //使用xx布局文件的名称
    Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.xx);
    iv.startAnimation(translateAnimation);

    java 代码设置:

    1
    2
    3
    4
    //位移动画,相应还有旋转其它类似
    Animation animation = new TranslateAnimation(0,100,0,100)
    translateAnimation.setDuration(3000);
    iv.startAnimation(translateAnimation);

    属性动画

    ValueAnimator 是属性动画的一个核心类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
    anim.setDuration(300);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    float currentValue = (float) animation.getAnimatedValue();
    Log.d("TAG", "cuurent value is " + currentValue);
    }
    });
    anim.start()

    ObjectAnimator

    1
    2
    3
    4
    5
    6
    7
    8
    //透明度的变化,还可以位移啥的
    ObjectAnimator animator = ObjectAnimator.ofFloat(iv, "alpha", 1f, 0f, 1f);
    animator.setDuration(5000);
    animator.start();
    ------------------------------
    ObjectAnimator animator = ObjectAnimator.ofFloat(iv, "rotation", 0f, 180f);
    animator.setDuration(5000);
    animator.start();

官方一张图:

image.png

ValueAnimator 是属性动画很重要的一个类,其中 ObjectAnimator 继承于 ValueAnimator ..

问题:那 ValueAnimator 到底是怎样实现初始值平滑过渡到结束值的呢?

这个是由 TypeEvaluator 和 TimeInterpolator 共同决定的。

具体来说:TypeEvaluator 决定了从初始值过度到结束值过度的方式。TimeInterpolator 决定了动画从初始值过渡到结束值的节奏。

有个例子很恰当:你每天上班去公司,是选择地铁出行还是公交,还是骑行,选择从出发地到目的地的一种抵达方式,这个是 TypeEvaluator, 假如你选择骑行,是均速骑行到目的地还是先加速后减速,还是一开始慢后来快,这个就是 TimeInterpolator 决定的。

用TypeEvaluator 确定运动轨迹

一般来说,要定义运动轨迹,需要实现 TypeEvaluator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PointEvaluator implements TypeEvaluator {

/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (x1 - x0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values 动画当前进行的进度
* @param startValue The start value.开始值
* @param endValue The end value.结束值
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.线性
*/
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
//自定义轨迹路线,假如我们做一个线性运动
return new Point((int)(startValue.getX()+endValue.getX()*fraction),
(int)(startValue.getY()+endValue.getY()*fraction));
}
}

使用的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Point startP = new Point(0,0);
Point endP = new Point(500,500);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), startP, endP);
valueAnimator.setDuration(5000);
valueAnimator.setRepeatCount(10);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point currentPoint = (Point) animation.getAnimatedValue();
iv.setX(currentPoint.getX());
iv.setY(currentPoint.getY());
current.setX(130+currentPoint.getX());
current.setY(currentPoint.getY());

current.setText("");
current.setText("x="+currentPoint.getX()+" y="+currentPoint.getY());

}
});
valueAnimator.start();

这样我们就完成了动画轨迹的定义。

例如我们来一个最简单的线性运动:

one.jpg

two.jpg

TimeInterpolator

前面说过,这个是用来控制动画从开始到结束的节奏的。Android 自己提供了几个自带的 Interpolator,当然同时也可以自定义实现。

image.png

断点续传了解

Veröffentlicht am 2019-01-14 |

所谓断点续传,也就是要从文件已经下载的地方开始继续下载。所以在客户端浏览器传给 wen 服务器的时候要多加一条信息即:从哪里开始的问题。

假设:我们从服务器下载一个文件,需要从 2000070 这个字节后开始下载,之前已经下载过了,注意查看以下代码:

1
2
3
4
5
6
7
get /down.zip http/1.0

user-agent: netfox

range: bytes=2000070-//新增加字段,range,这一行的意思就是告诉服务器我这个文件从2000070字节开始传输,前面的字节就不用传了。

accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

服务器收到这条信息,就开始返回信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
206 //注意此时服务器的返回码不再是200而是206

content-length=106786028

content-range=bytes 2000070-106786027/106786028 //这行也是服务器新增加字段,返回的字节是从2000070-106786027 之间的字节,之前的字节就不传了。

date=mon, 30 apr 2001 12:55:20 gmt

etag=w/"02ca57e173c11:95b"

content-type=application/octet-stream

server=microsoft-iis/5.0

last-modified=mon, 30 apr 2001 12:55:20 gmt

以上差不多就是断点续传需要知道的知识和大致的原理。

  • 问题

    用什么方法实现提交 range:bytes=2000070- ?

使用最原始的 socket 肯定可以完成,不过很费事,用java的net包中提供了设置的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
URL url = new URL("http://www.baidu.com");
try {
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//通过设置requestProperty
httpURLConnection.setRequestProperty("rande","bytes=2000070");
//这样获取的输入流就是从2000070这个字节后开始的
InputStream input = httpURLConnection.getInputStream();

} catch (IOException e) {
e.printStackTrace();
}
} catch (MalformedURLException e) {
e.printStackTrace();
}

接下来:获取到流了,那么如何从流保存到文件中去呢?

保存文件的方法,采用的是 io 包中的 randomacessfile 类。如下代码:

1
2
3
4
5
6
7
//上面文件已经获取到 inputStream--input
RandomAccessFile saveFile = new RandomAccessFile("down.zip","rw");//down.zip下载文件名称
saveFile.seek(2000070);//定位文件指针到这个 2000070 位置
byte[] b= new byte[1024];
int read;
while((read = input.read(b,0,1024))>0){
saveFile.write(b,0,read);

基本大概就是这样。

Serializable与 Parcelable 的区别

Veröffentlicht am 2019-01-10 |

序列化的两种方式,一种是 java 提供的 Serilizable 和 Android 自身提供的 Parcelable.

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 public class Person implements Serializable {
//默认方式最好设置为1L,因为 java sdk 会自动进行hash计算,并生成唯一的
//UID值。手动设置serialVersionUID的好处是当前class如果改变了成员变量,
//比如增加或删除之后,这个UID是不改变的,反序列化不会失效。
private static final long serialVersionUID = 1L;
....
}
//Parcable 类似
public class UserInfo implements Parcelable {
private String name;
private String lastName;
...
public static final Creator<UserInfo> CREATOR = new Creator<UserInfo>() {
@Override
public UserInfo createFromParcel(Parcel in) {
return new UserInfo(in);
}

@Override
public UserInfo[] newArray(int size) {
return new UserInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(lastName);
}
}

区别

  • 存储媒介的不同
存储媒介 特点
Serializable 使用的IO读写存储在硬盘上,序列化过程使用了反射技术,并且期间产生临时对象(?),从而引起频繁的GC。 代码少
Parcelable 使用的IO读写在内存中,内存的读写速度肯定优于硬盘读写速度,所以Parcelable的性能上优于Serializable. 代码写起来比较多

具体到开发中用哪个,个人觉着要考虑要传递对象的大小,如果对象比较大,手机内存比较小,可能会报出 TransactionTooLargeException: The Binder transaction failed because it was too large . 此时就要考虑使用 Parcelable 了,如果对象不是特别大,使用 Serializable 还是挺合适的,毕竟实现比较简单。

IntentService理解总结

Veröffentlicht am 2019-01-09 |

平常用的不是特别多,但是要知道内部原理,看看源码总结一下

首先为什么会有这个东西?

我们知道服务中 service 中的代码是运行在主线程中的,不能做耗时的操作,于是那如果想做耗时操作怎么做呢?就出来 IntentService 这么个东西,用来处理后台服务中的耗时操作并且可以在任务完成之后,自动停止服务。

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyIntentService extends IntentService {

public MyIntentService() {
super("MyIntentService");
}

//处理异步任务
@Override
protected void onHandleIntent(Intent intent) {
// 打印当前线程的id
Log.d("MyIntentService", "Thread id is " + Thread.currentThread(). getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}
//开启服务
Intent intentService = new Intent(this, MyIntentService.class);
startService(intentService);

通过打印当前线程的 id 我们可以知道当前线程不是在主线程中,执行完成后,会自动执行 onDestroy() 方法。以上属于 IntentService 的基本用法。

有什么好处呢?

  • 省去在 Service 中手动开线程的麻烦
  • 当操作任务完成后自动停止服务

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* IntentService is a base class for {@link Service}s that handle asynchronous
* requests (expressed as {@link Intent}s) on demand. Clients send requests
* through {@link android.content.Context#startService(Intent)} calls; the
* service is started as needed, handles each Intent in turn using a worker
* thread, and stops itself when it runs out of work.
*
* <p>This "work queue processor" pattern is commonly used to offload tasks
* from an application's main thread. The IntentService class exists to
* simplify this pattern and take care of the mechanics. To use it, extend
* IntentService and implement {@link #onHandleIntent(Intent)}. IntentService
* will receive the Intents, launch a worker thread, and stop the service as
* appropriate.
使用它,需要继承IntentService,并且实现 onHandleIntent方法,IntentService会收到这些Intent,开启一个工作线程,在合适的时间会自动停止服务
*
* <p>All requests are handled on a single worker thread -- they may take as
* long as necessary (and will not block the application's main loop), but
* only one request will be processed at a time.
所有的请求被处理在一个单一的工作线程中,不会阻塞主线程,但是在同一时间指挥处理一个请求
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For a detailed discussion about how to create services, read the
* <a href="{@docRoot}guide/components/services.html">Services</a> developer
* guide.</p>
* </div>
*官网文档链接
* @see android.os.AsyncTask
*/
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;

private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
//异步处理任务
onHandleIntent((Intent)msg.obj);
//停止自身服务
//stopSelf(): 会立马结束服务

//stopSelf(int startId): 等待所有消息都处理完后才终止服务
stopSelf(msg.arg1);
}
}

/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}

/**
* Sets intent redelivery preferences. Usually called from the constructor
* with your preferred semantics.
*
* <p>If enabled is true,
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_REDELIVER_INTENT}, so if this process dies before
* {@link #onHandleIntent(Intent)} returns, the process will be restarted
* and the intent redelivered. If multiple Intents have been sent, only
* the most recent one is guaranteed to be redelivered.
*
* <p>If enabled is false (the default),
* {@link #onStartCommand(Intent, int, int)} will return
* {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
* dies along with it.
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}

@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.

super.onCreate();
//创建了一个HandlerThread
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//取出HandlerThread线程的 looper ,由于 `HandlerThread` 又是一个异步线程,当我们把
//Looper 传递给 ServiceHandler时,使得 ServiceHandler也变成了一个异步执行的线程
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

@Override
public void onDestroy() {
mServiceLooper.quit();
}

/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
* @see android.app.Service#onBind
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}

/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
* This may be null if the service is being restarted after
* its process has gone away; see
* {@link android.app.Service#onStartCommand}
* for details.
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}

总结

IntentService.java 总结来说就是:一个 HandlerThread + ServiceHandler , 以队列的形式处理异步操作的类。

IntentService 是继承与 Service 的,在 onCreate() 时,创建了 HandlerThread 实例( HandlerThread 又是啥呢,它是 Thread 子类,内部拿到一个当前线程的 Looper, Looper 一直轮询消息,获得消息,处理消息),而 IntentService 内部有一个ServiceHandler,ServiceHandler 的创建的 Looper 是拿的是 HandlerThread 的 Looper , IntentService 在 onStart() 通过发送 Message, 在处理 Message 调用的是 onHandleIntent() 方法。

IOC

Veröffentlicht am 2019-01-04 |

以下内容是我最近在学习 Spring Boot Spring MVC 过程中,针对 ioc 控制反转所了解到的内容。

实践步骤

  • 使用 Spring Boot, Spring 如何实现 IOC
  • 使用 maven

java oop : 每次都是自己 new 对象,不够方便,核心原因是产生了代码的偶合。

目标:希望容器给我对象,直接获得对象;

IOC: 控制反转,表示把对象的控制权交给容器

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 @Bean
public Student getStudent(){
Student stu = new Student();
stu.setName("糖葫芦");
stu.setAge("18");
return stu;
}

在另外一个类 SpringBootIocApplicationTests.java 中:
@Autowired
Student student;

@Test
public void contextLoads() {
//这里可以直接拿到
System.out.println("student:"+student.getName());
//student:糖葫芦
}

Maven 项目

如果不用 Spring Boot ,创建 Maven 项目进行实现:

  • 使用 ide 创建 maven 项目

    image.png

注意勾选 create from..

要实现控制反转,需要我们导入 Spring Context 配置,在 maven 官网:

image.png

搜索 Spring, 底下有很多版本,导入你想导入的版本,点击进去,看到:

image.png

将红色框部分,拷贝到 项目的 pom.xml 文件中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<project

......
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
</dependencies>

</project>

然后我们还需要手动设置配置文件:在 resources 文件夹下,创建一个 xml ,我们可以这么创建 xml,可以帮助我们自带一些配置,如下:

image.png

applicationContext.xml :

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="student" class="Student"><!--将这个bean加入到spring的ioc容器-->
<property name="name" value="糖葫芦"></property><!--给bean的pname属性赋值-->
<property name="age" value="18"></property>
</bean>
</beans>

最后,如何获取呢?通过 ApplicationContext 获取:

1
2
3
4
5
6
7
8
9
public static void main(String[] args){

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//传入xml 中定义的bean 的 id
Student student = (Student) context.getBean("student");

System.out.println("student::"+student.getName()+ "::::age::"+student.getAge());

}

这样我们就可以拿到我们在 xml 中定义的 bean 的内容了。

组件化随笔

Veröffentlicht am 2018-12-25 |

随着技术越来越发展,代码越来越臃肿,经常会牵一发而动全身,发生bug的概率也比较高,组件化一定程度上成了我们必不可少的一个部分,使用组件化能够:

  • 提高代码的复用性
  • 降低组件之间的耦合性,一定程度上减少bug的产生

设计组件化:

  • 分层设计,app壳工程,业务组件,功能组件,基础组件。
  • 业务组件为了防止相互引用出现死循环,每一个业务组件设计成两个module, 一个 export module,一个 implement module. 互相依赖 export modult 即可。

image.png

Context

Veröffentlicht am 2018-12-25 |

Context

1
public abstract class Context {

之间的继承关系:

image.png

Context 作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//可以点击studio旁边的 Structure 结构示意图

/**
* Interface to global information about an application environment. This is
* an abstract class whose implementation is provided by
* the Android system. It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {

// 四大组件相关
public abstract void startActivity(@RequiresPermission Intent intent);
public abstract void sendBroadcast(@RequiresPermission Intent intent);
public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
IntentFilter filter);
public abstract void unregisterReceiver(BroadcastReceiver receiver);
public abstract ComponentName startService(Intent service);
public abstract boolean stopService(Intent service);
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
public abstract void unbindService(@NonNull ServiceConnection conn);
public abstract ContentResolver getContentResolver();

// 获取系统/应用资源
public abstract AssetManager getAssets();
public abstract Resources getResources();
public abstract PackageManager getPackageManager();
public abstract Context getApplicationContext();
public abstract ClassLoader getClassLoader();
public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }

public final String getString(@StringRes int resId) { ... }
public final int getColor(@ColorRes int id) { ... }
public final Drawable getDrawable(@DrawableRes int id) { ... }
public abstract Resources.Theme getTheme();
public abstract void setTheme(@StyleRes int resid);
public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }

// 获取应用相关信息
public abstract ApplicationInfo getApplicationInfo();
public abstract String getPackageName();
public abstract Looper getMainLooper();
public abstract int checkPermission(@NonNull String permission, int pid, int uid);

// 文件相关
public abstract File getSharedPreferencesPath(String name);
public abstract File getDataDir();
public abstract boolean deleteFile(String name);
public abstract File getExternalFilesDir(@Nullable String type);
public abstract File getCacheDir();
...
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
public abstract boolean deleteSharedPreferences(String name);

// 数据库相关
public abstract SQLiteDatabase openOrCreateDatabase(...);
public abstract boolean deleteDatabase(String name);
public abstract File getDatabasePath(String name);
...

// 其它
public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
...
}

public interface ComponentCallbacks {
void onConfigurationChanged(Configuration newConfig);
void onLowMemory();
}

Context 就相当于 Application 的大管家,主要负责:

  • 四大组件,启动 Activity , 广播,服务等
  • 获取系统资源
  • 文件操作相关
  • 数据库操作相关

ContextWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context. Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
Context mBase;

public ContextWrapper(Context base) {
mBase = base;
}

/**
* Set the base context for this ContextWrapper. All calls will then be
* delegated to the base context. Throws
* IllegalStateException if a base context has already been set.
*
* @param base The new base context for this wrapper.
*/
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}

/**
* @return the base context as set by the constructor or setBaseContext
*/
public Context getBaseContext() {
return mBase;
}
}

ContextWrapper 实际上就是 Context 的代理类,所有的操作都是 mBase 完成。另外,Activity, Service 的 getBaseContext 返回的就是这个 mBase.

ContextThemeWrapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/**
* A context wrapper that allows you to modify or replace the theme of the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
private LayoutInflater mInflater;
private Configuration mOverrideConfiguration;
private Resources mResources;

/**
* Creates a new context wrapper with no theme and no base context.
* <p class="note">
* <strong>Note:</strong> A base context <strong>must</strong> be attached
* using {@link #attachBaseContext(Context)} before calling any other
* method on the newly constructed context wrapper.
*/
public ContextThemeWrapper() {
super(null);
}

/**
* Creates a new context wrapper with the specified theme.
* <p>
* The specified theme will be applied on top of the base context's theme.
* Any attributes not explicitly defined in the theme identified by
* <var>themeResId</var> will retain their original values.
*
* @param base the base context
* @param themeResId the resource ID of the theme to be applied on top of
* the base context's theme
*/
public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
super(base);
mThemeResource = themeResId;
}

/**
* Creates a new context wrapper with the specified theme.
* <p>
* Unlike {@link #ContextThemeWrapper(Context, int)}, the theme passed to
* this constructor will completely replace the base context's theme.
*
* @param base the base context
* @param theme the theme against which resources should be inflated
*/
public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
mTheme = theme;
}

@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}

/**
* Call to set an "override configuration" on this context -- this is
* a configuration that replies one or more values of the standard
* configuration that is applied to the context. See
* {@link Context#createConfigurationContext(Configuration)} for more
* information.
*
* <p>This method can only be called once, and must be called before any
* calls to {@link #getResources()} or {@link #getAssets()} are made.
*/
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (mResources != null) {
throw new IllegalStateException(
"getResources() or getAssets() has already been called");
}
if (mOverrideConfiguration != null) {
throw new IllegalStateException("Override configuration has already been set");
}
mOverrideConfiguration = new Configuration(overrideConfiguration);
}

/**
* Used by ActivityThread to apply the overridden configuration to onConfigurationChange
* callbacks.
* @hide
*/
public Configuration getOverrideConfiguration() {
return mOverrideConfiguration;
}

@Override
public AssetManager getAssets() {
// Ensure we're returning assets with the correct configuration.
return getResourcesInternal().getAssets();
}

@Override
public Resources getResources() {
return getResourcesInternal();
}

private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}

@Override
public void setTheme(int resid) {
if (mThemeResource != resid) {
mThemeResource = resid;
initializeTheme();
}
}

/** @hide */
@Override
public int getThemeResId() {
return mThemeResource;
}

@Override
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}

mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();

return mTheme;
}

@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}

/**
* Called by {@link #setTheme} and {@link #getTheme} to apply a theme
* resource to the current Theme object. May be overridden to change the
* default (simple) behavior. This method will not be called in multiple
* threads simultaneously.
*
* @param theme the theme being modified
* @param resId the style resource being applied to <var>theme</var>
* @param first {@code true} if this is the first time a style is being
* applied to <var>theme</var>
*/
protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
theme.applyStyle(resId, true);
}

private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
final Resources.Theme theme = getBaseContext().getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
}

结合注释及源码,可以发现,相比 ContextWrapper, ContextThemeWrapper 有自己的另外的Resource以及 Theme 成员,并且可以传入配置信息以初始化自己的 Resource 及 Theme.

ContextThemeWrapper 和它的mBase 成员在 Resource 以及 Theme 相关的行为上是不同的。

ContextImpl

ContextImpl 和 ContextThemeWrapper 最大的区别就是没有一个 Configuration. 其它的行为大致一样。 另外 ContextImpl 可以用于创建 Activity Service 的 mBase 成员,这个 mBase context 除了参数不同,它们的 Resource 也不同, 需要注意的是, createActivityContext等方法中 setResource 是 mBase 自己调用的, Activity, service 以及 Application 本身并没有执行 setResource.

总结

  • ContextWrapper, ContextThemeWrapper 都是 Context 的代理类,二者的区别在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以传入自己配置初始化
  • ContextImpl 是 Context 主要实现类, Activity, Service 和 Application 的 Base contex都是由他创建的,即 ContextWrapper 代理的就是 ContextImpl 对象本身

RecyclerView刷新机制

Veröffentlicht am 2018-12-24 |

掘金博客记录

  • adapter.notifyDataSetChanaged() 引起的刷新

    我们假设 recycleView 的初始状态是没有数据的,然后往数据源中加入数据后,调用

    adapter.notifyDataSetChanged() 来引起RecyclerView 的刷新:

    1
    2
    data.addAll(data)
    adapter.notifyDataSetChanged()

    adapter.notifyDataSetChanged() 时,会引起 recycleView 重新布局(requestLayout).因此从 onLayout() 方法开始:

    1
    2
    3
    4
    5
    6
    7
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
    }

    这个方法直接调用了 dispatchLayout():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    void dispatchLayout() {
    if (mAdapter == null) {
    Log.e(TAG, "No adapter attached; skipping layout");
    // leave the state in START
    return;
    }
    if (mLayout == null) {
    Log.e(TAG, "No layout manager attached; skipping layout");
    // leave the state in START
    return;
    }
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
    mLayout.setExactMeasureSpecsFrom(this);
    dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
    || mLayout.getHeight() != getHeight()) {
    // First 2 steps are done in onMeasure but looks like we have to run again due to
    // changed size.
    mLayout.setExactMeasureSpecsFrom(this);
    dispatchLayoutStep2();
    } else {
    // always make sure we sync them (to ensure mode is exact)
    mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
    }

    缩写为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void dispatchLayout() {
    ...
    if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
    dispatchLayoutStep2();
    } else if (数据变化 || 布局变化) {
    dispatchLayoutStep2();
    }
    dispatchLayoutStep3();
    }

    上面是裁掉了一些代码,可以看到整个布局过程总共分为3步,下面3步对应的方法:

    1
    2
    3
    STEP_START---->dispatchLayoutStep1()
    STEP_LAYOUT---->dispatchLayoutStep2()
    STEP_ANIMATIONS---->dispatchLayoutStep2(),dispatchLayoutStep3()

    第一步:STEP_START

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * The first step of a layout where we;
    * - process adapter updates 更新adapter
    * - decide which animation should run 确定哪个动画应该运行
    * - save information about current views 保存当前views的信息
    * - If necessary, run predictive layout and save its information
    */
    private void dispatchLayoutStep1() {
    .....
    }

    第二步:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    确定view的真正状态
    * The second layout step where we do the actual layout of the views for the final state.
    这步可能会执行多次如果necessary
    * This step might be run multiple times if necessary (e.g. measure).
    */
    private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    //设置好初始状态
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout 调用布局管理器布局
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;
    mPendingSavedState = null;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    mState.mLayoutStep = State.STEP_ANIMATIONS;//接下来执行布局的第三步
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    }

    这里有一个 mState, 它是 一个 RecyclerView.State 对象。顾名思义它是用来保存 RecyclerView 状态的一个对象,主要是用在 LayoutManager Adapter 组件之间共享 recyclerView 状态的。可以看到这个方法将布局交给了 mLayout. 就是指的是 LineaLayoutManager. 因此接下来看下:LinearLayoutManager.onLayoutChildren():

  • LinearLayoutManager.onLayoutChildren()

    这个方法比较长,就不展示具体源码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // layout algorithm: 布局算法
    1.检查children 和其它变量,找到锚坐标和锚
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    // item position. item位置
    2.开始填充,从底部开始
    // 2) fill towards start, stacking from bottom
    3.填充
    // 3) fill towards end, stacking from top
    4.滚动满足要求像从底部填充一样
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state
    • 确定锚点View

      锚点View的确定通过: updateAnchorInfoForLayout

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // calculate anchor position and coordinate
      updateAnchorInfoForLayout(recycler, state, mAnchorInfo);

      private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
      RecyclerView.State state, AnchorInfo anchorInfo) {
      ....
      View referenceChild = anchorInfo.mLayoutFromEnd
      //如果是从end(尾部)位置开始布局,那就找最接近end的那个位置的view作为锚点View
      ? findReferenceChildClosestToEnd(recycler, state)
      ////如果是从start(尾部)位置开始布局,那就找最接近start的那个位置的view作为锚点View
      : findReferenceChildClosestToStart(recycler, state);
      }

AnchorInfo 最重要的两个属性是; mCoordinate 和 mPosition. 找到锚点 View 后就会通过anchorInfo.assignFromView() 方法来设置这两个属性:

1
2
3
4
5
6
7
8
9
10
public void assignFromView(View child, int position) {
if (mLayoutFromEnd) {
mCoordinate = mOrientationHelper.getDecoratedEnd(child)
+ mOrientationHelper.getTotalSpaceChange();
} else {
mCoordinate = mOrientationHelper.getDecoratedStart(child);
}

mPosition = position;
}
1
2
mCoordinate: 就是锚点View的 Y(x) 坐标去掉 RecycleView 的 padding.
mPosition: 其实就是 锚点View的位置

当确定好 AnchorInfo 后,需要根据 AnchorInfo 来确定 RecycleView 当前可用于布局的空间,然后来摆放子View。以布局方向为 start to end 正常方向为例,这里的锚点View其实就是 RecyclerView最顶部的 View:

1
2
3
4
5
6
7
8
9
10
// fill towards end  (1)
updateLayoutStateToFillEnd(mAnchorInfo); //确定AnchorView到RecyclerView的底部的布局可用空间
...
fill(recycler, mLayoutState, state, false); //填充view, 从 AnchorView 到RecyclerView的底部
endOffset = mLayoutState.mOffset;

// fill towards start (2)
updateLayoutStateToFillStart(mAnchorInfo); //确定AnchorView到RecyclerView的顶部的布局可用空间
...
fill(recycler, mLayoutState, state, false); //填充view,从 AnchorView 到RecyclerView的顶部

上面我标注了(1)(2),1次布局是由这两部分组成的,具体如下图:

1545640614074

fill towards end

确定可用布局空间

在 fill 之前,需要先确定 从锚点View 到 RecyclerView 底部有多少可用空间。是通过 updateLayoutStateToFillEnd 方法:

1
2
3
4
5
6
7
8
9
10
updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);

void updateLayoutStateToFillEnd(int itemPosition, int offset) {
mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
...
mLayoutState.mCurrentPosition = itemPosition;
mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
mLayoutState.mOffset = offset;
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
}

mLayoutState 是 LinearLayoutmanager 用来保存布局状态的一个对象。 mLayoutState.mAvailable 就是用来表示 有多少空间可用布局。mOrientationHelper.getEndAfterPadding() - offset 其实大致可以理解为RecycleView 的高度。所以这里可用布局空间 mLayoutState.mAvailable 就是 ReycleView 的高度。

摆放子View

接下来继续看 LinearLayoutManager.fill() 方法,这个方法是布局的核心方法,是用来向 RecycleView 中添加子View的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;//可用高度就是RecyclerView的高度
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
//保存布局一个child view后的结果
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//有剩余空间的话,就一直添加 childView
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//布局子View的核心方法
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
//一次layoutchunk 消耗了多少空间
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
回收可用空间....

return start - layoutState.mAvailable;
}

这个方法的核心就是调用 layoutChunk() 来不断消耗layoutState.mAvailable 直到消耗完毕,继续看下 layoutChunk() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //这个方法会向 recycler view 要一个holder
...
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { //根据布局方向,添加到不同的位置
addView(view); //添加到 RecyclerView 中
} else {
addView(view, 0);
}
measureChildWithMargins(view, 0, 0); //调用view的measure

...measure后确定布局参数 left/top/right/bottom

layoutDecoratedWithMargins(view, left, top, right, bottom); //调用view的layout
...
}

到这里其实就完成了上面的 fill towards end:

1
2
3
updateLayoutStateToFillEnd(mAnchorInfo)// 确定布局可用空间

fill(recycler,mLayoutState,state,false)// 填充View

fill towards start 就是从 锚点View 向 RecycleView 顶部来摆放子View,具体逻辑类似 fill towards end.

RecyclerView 滑动时的刷新逻辑

在不加载新的数据情况下, RecycleView 在滑动时是如何展示 子View的,即下面这种状态:

1545643914261

下面就来分析一个 3,4 和 12,13是如何展示的。

RecycleView 在 OnTouchEvent 对滑动事件做了监听,然后派发到 scrollStep() 方法:

HandlerThread源码

Veröffentlicht am 2018-12-20 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/开启一个线程内部有一个looper, 这个looper可以用来创建handler. 切记一定要调用start方法.
public class HandlerThread extends Thread {
.....
}
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

其实我们初始化和启动了一个线程,既然是线程那就看 run 方法, run 方法中创建了 Looper 对象,然后设置线程等级,开启循环 loop() .

进程保活大法

Veröffentlicht am 2018-12-19 |

收集整理android 进程保活方法

1像素Activity

注册监听屏幕开启和屏幕关闭时的广播,当屏幕关闭时,开启1像素的 Activity, 当屏幕开启时,关闭1像素的 Activity.

  • 查看进程等级

    可以在studio 终端进行命令行, pid 进程id

    1
    2
    3
    adb shell
    su
    cat proc/{pid}/oom_adj

    OnePxActivity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public class OnePxActivity extends AppCompatActivity {

    public static void launch(Context context){
    Intent intent = new Intent(context,OnePxActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Log.i("qq","OnPxActivity onCreate.........");
    Window window = getWindow();
    window.setGravity(Gravity.START | Gravity.TOP);

    WindowManager.LayoutParams params = window.getAttributes();
    params.x = 0;
    params.y = 0;
    params.width = 1;
    params.height = 1;
    window.setAttributes(params);

    KeepAliveManager.getInstance().setKeepAliveManager(this);
    }

    @Override
    protected void onDestroy() {
    super.onDestroy();
    Log.i("qq","OnPxActivity onDestroy.........");
    }
    }

    广播 : 监听开启/关闭的广播

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class KeepAliveReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

    if(intent.getAction().equals(Intent.ACTION_SCREEN_ON)){
    //屏幕点亮
    Log.i("qq","收到屏幕开启广播");
    KeepAliveManager.getInstance().finishOnePxActivity(context);

    }else if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)){
    //屏幕熄灭, 启动activity
    Log.i("qq","收到屏幕关闭广播");
    KeepAliveManager.getInstance().startOnePxActivity(context);
    }
    }
    }

    辅助类:KeepAliveManager

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class KeepAliveManager {

    private KeepAliveManager(){}
    private static KeepAliveManager mInstance = new KeepAliveManager();

    private WeakReference<OnePxActivity> mReference;

    public void setKeepAliveManager(OnePxActivity activity){
    this.mReference = new WeakReference<>(activity);
    }
    public static KeepAliveManager getInstance(){
    return mInstance;
    }

    public void startOnePxActivity(Context context){
    OnePxActivity.launch(context);
    }

    public void finishOnePxActivity(Context context){
    if(null != mReference && mReference.get() != null){
    mReference.get().finish();
    }
    }
    private KeepAliveReceiver mReceiver;
    public void registerReceiver(Context context){
    this.mReceiver = new KeepAliveReceiver();
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_SCREEN_OFF);
    filter.addAction(Intent.ACTION_SCREEN_ON);
    context.registerReceiver(mReceiver,filter);
    }

    public void unregisterReceiver(Context context){
    if(null != mReceiver){
    context.unregisterReceiver(mReceiver);
    }
    }
    }

    MainActivity 使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class MainActivity extends AppCompatActivity {

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

    //第一种方式
    KeepAliveManager.getInstance().registerReceiver(this);
    }

    @Override
    protected void onDestroy() {
    super.onDestroy();

    KeepAliveManager.getInstance().unregisterReceiver(this);
    }
    }

亲身实践了,发现确实oom_adj 变小,被杀死的概率比较低了,当屏幕变暗的时候。

前台服务

ForegroundService.java :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ForgroundService extends Service {

private final int SERVICE_ID = 1;
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(Build.VERSION.SDK_INT < 18){
//设置成前台服务,并且不显示通知栏消息
startForeground(SERVICE_ID,new Notification());
}else if(Build.VERSION.SDK_INT < 26){
startForeground(SERVICE_ID,new Notification());
startService(new Intent(this,InnerService.class));
}else{//android 8.0

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if(manager != null){
NotificationChannel channel = new NotificationChannel("channel","name",NotificationManager.IMPORTANCE_NONE);
manager.createNotificationChannel(channel);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"channel");
//设置成前台服务,Android9.0 会有通知栏消息,需要添加新的权限
//<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
startForeground(SERVICE_ID,builder.build());
}
}

return super.onStartCommand(intent, flags, startId);
}

class InnerService extends Service{

@Override
public IBinder onBind(Intent intent) {

return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(SERVICE_ID,new Notification());
stopForeground(true);
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
}
}

双进程守护

后续补上…

1234…8

QQabby

79 Artikel
63 Tags
© 2020 QQabby
Erstellt mit Hexo
|
Theme — NexT.Pisces v5.1.3