简单的来说就是 直接启动 和 绑定启动 两种方式。
// 直接启动
Context.startService()
// 直接启动后需要手动调用停止服务才会停止
Context.stopService()
Service.selfStop()
// 绑定启动
Context.bindService()
两种启动方式的生命周期不同,下面詳細说明下生命周期。
从上图可以看出,无论是哪种启动方式,都会调用 onCreate 方法,服务第一次创建时会调用该方法,以后再调用 startService
也不会调用 onCreate
方法,这里面适合做初始化操作。当处于激活状态时,由 startService
启动 Service 需要自己停止或者外部客户端停止服务;由 bindService
启动的服务,当所连接的客户端都调用 unbindService
后停止。
根据不同的使用场景选择合适的启动方式,例如音乐播放器需要后台播放以及 widget 控制播放这时候就要使用 startService
启动服务,在用户退出应用的时候结束服务。再比如,某个 Service 的作用范围只在某个 activity 或几个 activity中时,使用bindService方式会比较合适。
在 onStartCommand 中返回 START_STICKY
这种方式并不能保证 Service 不被杀死,只是提高 Service 被杀死后快速重启概率。
提高 Service 优先级
设置为前台服务
在service中设置常驻通知栏,这样服务就可以在后台常驻,但是通知栏会显示一个通知。
双进程守护
为后台常驻 Service 设置守护进程,相互监听对方的状态,当监测到对方被杀死后立即重启对方 Service 达到守护 service 的目的。
上述方法中通常情况下只能在原生 Android 中实现,国内修改过的系统为了让系统有更好的续航通常都会阻止应用自启动,因此上述方法基本失效。
关于上述方法失效问题很多人会换一种方式解决这个问题,通过第三方推送sdk启动应用,例如 友盟、极光等。具体操作方法就是在自定义推送中启动我们应用的后台服务从而启动应用。具体使用中发现也只能在部分系统中生效,flyme,miui杜绝了这种全家桶式的自启动方式。
终极解决办法,通过微信服务号向用户推送关键信息并通过浏览器打开应用。微信服务号的推送不能滥用,滥用微信会停止服务号的推送功能。
其他相关文章:
Android Service两种启动方式详解(总结版)
Service的两种启动方式
普通 Service 在未指定进程的情况下和主线程运行在同一进程,并且也在主线程中,因此在这样的 Service 中做过多耗时操作也会阻塞UI线程。
这种情况下如果我们不想出现跨进程的情况,我们就不能指定 Service 在独立的进程中,我们可以在 Service 中创建 Thread 来处理这些耗时的操作,Android官方给我们提供了一种便利的方式,我们只需要继承IntentService即可,IntentService 会自动我们的操作创建 Thread 并执行。具体实现如下:
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(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.
*
* 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.
*
*
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 thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
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 使用了 Handler 来处理发送过来的任务,调用 startService
后首先会进入 onStartCommand
接着我们看到 onStart
方法中将 intent 封装成 Message 丢给 handler ,handler 中的 handleMessage 方法调用 onHandleIntent,我们只需要在 onHandleIntent 完成我们要做的操作即可。 我们看到 onHandleIntent 有一个自定义的注解 @WorkerThread
, 添加了该注解的方法系统会自动为我们创建一个线程然后再执行 onHandleIntent 方法。
因此,IntentService 中的消息是依次执行的,如果有很多任务并发执行,这些任务都会放在消息队列中,等待前一个任务执行完成后才能执行下一个任务。
相关文章:
Android中IntentService实现原理详解
IntentService的原理和实例分析
静态广播需要在 androidManifest.xml 文件中申明组件,否则无法接收广播;
动态广播需要在运行的时候动态注册,有很多系统广播只能用动态注册的方式使用。
图就不画了,博客上找了几张,上图:
上面这张图说明了Handler中的消息循环机制:
老生常谈了,Handler 通过 sendMessage 向消息队列中发送消息,Looper 循环从 MessageQueue 中取出消息,然后要求 Handler dispatchMessage ,最后回调到 handleMessage 中。从结构上来看简单的就是这么描述。
上图中很明确的说明了,Looper是属于某个一线程的,在android中我们知道要在某个线程中创建 Handler 首先需要初始化 Looper,否则就会报错。下面我们说几条结论,稍后通过源码证明:
// ActivityThread.java
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("" );
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主线程中我们未做 Looper 初始化是因为在 main 函数中系统已经帮我们做了初始化;
通常情况下我们是这样创建 Handler 的:
new Handler();
构造函数中没有参数,我们看下最终的构造函数如下:
// Handler.java
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到 mLooper 是通过 Looper.myLooper(); 获得的,继续往下看:
// Looper.java
public final class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
...
}
}
myLooper 返回的是 ThreadLocal 中保存的线程贡献变量。
**```ThreadLocal```是用于保存线程共享变量的类,对它进行 set 时候各个线程的变量不会相互影响,```Thread.ThreadLocalMap```会给每个线程保存一个线程共享变量。 ```ThreadLocal```是切换线程的关键,```ThreadLocal```中保存的是当前线程的 ```Looper```,```Looper```中包含一个线程的消息队列 ```MessageQueue```, ```MessageQueue```中的 ```Message```中又持有 Hanmdler 的引用,当 ```Looper```取到 ```Message```后,可通过 ```Message```关联的 ```Handler```直接调用相关函数,由于是 ```Looper```调用的 ```Handler```,这时候操作 ```Handler```的线程就是 ```Looper```所在的线程了,这样就做到了线程切换。**我们再看 prepare()
函数中做了判断,如果 sThreadLocal
有值则直接抛出异常,这里限制一个线程只能有一个 Looper
。
下面是我纯 Java 环境下实现的模拟 Handler 简单原理:
模拟Handler实现
相关文章:
Handler是如何实现线程之间的切换的
常用布局:
FrameLayout``````LinearLayout``````RelativeLayout
什么情况下使用 RelativeLayout
:
Google 官方文档有这么一段描述:
A RelativeLayout is a very powerful utility for designing a user interface because it can eliminate nested
view groups and keep your layout hierarchy flat, which improves performance. If you find yourself using
several nested LinearLayout groups, you may be able to replace them with a single RelativeLayout.
也就是说为了提高布局性能,当我们过多的布局嵌套的时候我们尝试使用一个 RelativeLayout 来解决多级嵌套问题,从而提高布局性能。
提高性能布局:
ConstraintLayout
ConstraintLayout
也是为了解决布局嵌套问题,它比 RelativeLayout
更加灵活,使用也稍微复杂一些。
相关文章:
RelativeLayout和LinearLayout性能比较
实战篇ConstraintLayout的崛起之路
数据库升级时版本号最好是连续的,方便使用循环升级,下面我们来看一段代码:
public class DBHelper extends SQLiteOpenHelper {
private static final int VERSION = 3;
// private static final String CREATE_RECORDING = "CREATE TABLE IF NOT EXISTS recording(" +
// "ringing_time,wait_time,call_time,myphone,othrephone,localfilepath,remotefilepath," +
// "nickname,type,incoming,username,add_time,task_type,test);"; // version 1
// private static final String CREATE_RECORDING = "CREATE TABLE IF NOT EXISTS recording(" +
// "ringing_time,wait_time,call_time,myphone,othrephone,localfilepath,remotefilepath," +
// "nickname,type,incoming,username,add_time,task_type,test,task_id);"; // version 2
private static final String CREATE_RECORDING = "CREATE TABLE IF NOT EXISTS recording(" +
"ringing_time,wait_time,call_time,myphone,othrephone,localfilepath,remotefilepath," +
"nickname,type,incoming,username,add_time,task_type,test," +
"task_id,close_type,is_connected,is_accepted);"; // version 3
public DBHelper(Context context) {
super(context, "mllrecordingwizard.db", null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_RECORDING);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String sql;
for (int i = oldVersion; i < newVersion; i++) {
switch (i) {
case 1:
sql = "ALTER TABLE recording ADD COLUMN task_id;";
db.execSQL(sql);
break;
case 2:
sql = "ALTER TABLE recording ADD COLUMN close_type;";
db.execSQL(sql);
sql = "ALTER TABLE recording ADD COLUMN is_connected;";
db.execSQL(sql);
sql = "ALTER TABLE recording ADD COLUMN is_accepted;";
db.execSQL(sql);
break;
}
}
}
public SQLiteDatabase getSQLiteDatabase() {
return this.getWritableDatabase();
}
}
上例中数据库初始版本号是1,升级到最新版本号是3。为了能够有迹可循,最好保存每个版本数据库表的设计,如上面的注释。
onCreate
方法我们在 onCreate
中创建表即可;onCreate
只会调用 onUpgrade
方法,我们需要在 onUpgrade
方法中执行升级操作即可。ANR(Application Not responding)。Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR。具体来说,ANR会在以下几种情况中出现:
分析ANR问题,需要结合Log以及trace文件。具体分析流程,可参照以下两篇文章:
https://www.jianshu.com/p/fa962a5fd939
https://blog.csdn.net/droyon/article/details/51099826
ListView
使用适配器时需要手动复用 view ,为非必要操作,数据多了以后很容易早晨内存溢出情况; RecyclerView
通过代码的形式约束,控件自身实现 view 的复用,避免了问题的出现。RecyclerView
支持布局管理器,布局方式灵活。RecyclerView
两级缓存RecyclerView
item 动画灵活RecyclerView
adapter 更新更加灵活,可单独更新某个item,不用更新整个列表。讲解 RecyclerView
如何进行缓存的文章很多了,分析源码也很到位,图文并茂。这里不再重复造轮子,这里说一下为什么使用多级缓存:
设置多级缓存意义:
一级缓存 scrap
容量最小,只保存最先出屏幕的 ViewHolder ,需要显示是也是首先从一级缓存中取出 ViewHodler
二级缓存 cacheView
二级缓存缓存一级缓存放不下的 ViewHolder
三级缓存 RecyclerViewPool
三级缓存,对象池缓存,多个 RecyclerView 共享缓存。
相关文章:
一级缓存、二级缓存和三级缓存有什么区别
RecyclerView 源码分析(三) - RecyclerView的缓存机制
RecyclerView缓存原理
Standard
在同一个任务栈中可以有多个实例,每次调用 startActivity 都会新建一个实例添加到栈中
SingleTop
栈顶复用,若不在栈顶创建一个新的 Activity
SingleTask
栈内复用,若栈中已经存在这个 Activity ,那么将其上方的其他 Activity 弹出栈并将其 放到栈顶。相当于栈内单例模式。
SingleInstance
进程单例模型,系统中唯一单例存在,单独一个任务栈。
相关文章:
Android Touch事件传递机制全面解析(从WMS到View树)
剖析Activity、Window、ViewRootImpl和View之间的关系
计算机网络-运输层
相关文章:
OkHttp源码解析
源码地址: https://github.com/square/okhttp
Retrofit 的核心就是 Java 的动态代理和动态注解,只要理解了这两个基本原来就是掌握了。
// Retrofit.java
/**
* Create an implementation of the API endpoints defined by the {@code service} interface.
*
* The relative path for a given method is obtained from an annotation on the method describing
* the request type. The built-in methods are {@link retrofit2.http.GET GET},
* {@link retrofit2.http.PUT PUT}, {@link retrofit2.http.POST POST}, {@link retrofit2.http.PATCH
* PATCH}, {@link retrofit2.http.HEAD HEAD}, {@link retrofit2.http.DELETE DELETE} and
* {@link retrofit2.http.OPTIONS OPTIONS}. You can use a custom HTTP method with
* {@link HTTP @HTTP}. For a dynamic URL, omit the path on the annotation and annotate the first
* parameter with {@link Url @Url}.
*
* Method parameters can be used to replace parts of the URL by annotating them with
* {@link retrofit2.http.Path @Path}. Replacement sections are denoted by an identifier
* surrounded by curly braces (e.g., "{foo}"). To add items to the query string of a URL use
* {@link retrofit2.http.Query @Query}.
*
* The body of a request is denoted by the {@link retrofit2.http.Body @Body} annotation. The
* object will be converted to request representation by one of the {@link Converter.Factory}
* instances. A {@link RequestBody} can also be used for a raw representation.
*
* Alternative request body formats are supported by method annotations and corresponding
* parameter annotations:
*
* - {@link retrofit2.http.FormUrlEncoded @FormUrlEncoded} - Form-encoded data with key-value
* pairs specified by the {@link retrofit2.http.Field @Field} parameter annotation.
*
- {@link retrofit2.http.Multipart @Multipart} - RFC 2388-compliant multipart data with
* parts specified by the {@link retrofit2.http.Part @Part} parameter annotation.
*
*
* Additional static headers can be added for an endpoint using the
* {@link retrofit2.http.Headers @Headers} method annotation. For per-request control over a
* header annotate a parameter with {@link Header @Header}.
*
* By default, methods return a {@link Call} which represents the HTTP request. The generic
* parameter of the call is the response body type and will be converted by one of the
* {@link Converter.Factory} instances. {@link ResponseBody} can also be used for a raw
* representation. {@link Void} can be used if you do not care about the body contents.
*
* For example:
*
* public interface CategoryService {
* @POST("category/{cat}/")
* Call<List<Item>> categoryList(@Path("cat") String a, @Query("page") int b);
* }
*
*/
@SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
new InvocationHandler() {
private final Platform platform = Platform.get();
private final Object[] emptyArgs = new Object[0];
@Override public @Nullable Object invoke(Object proxy, Method method,
@Nullable Object[] args) throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
}
});
}
代理对象的方法执行时会回调 InvocationHandler#invoke 方法,框架会针对不同方法上的注解动态解析并生成不同的 okhttp 请求。
源码地址: https://github.com/square/retrofit
相关文章:
java动态代理、Proxy与InvocationHandler
Retrofit源码分析(超详细)
源码地址: https://github.com/bumptech/glide
源码地址: https://github.com/facebook/fresco
源码地址: https://github.com/square/Picasso
三个框架功能、性能对比分析: https://juejin.im/entry/5928e9212f301e0057d6bb93
https://github.com/greenrobot/EventBus
优点:
缺点:
简单的EventBus实现 OwnEventBus:
https://github.com/onlynight/OwnEventBus
/**
* Created by wyndam on 2017/12/27.
* simple event bus demo
*/
public class EventBus {
/**
* subscribers
*/
private List<WeakReference<Object>> subscribers;
private static EventBus instance;
public static EventBus getInstance() {
if (instance == null) {
instance = new EventBus();
}
return instance;
}
private EventBus() {
this.subscribers = new ArrayList<>();
}
/**
* register subscriber to event bus
* @param subscriber event listener
*/
public void registerSubscriber(Object subscriber) {
if (subscriber != null) {
subscribers.add(new WeakReference<>(subscriber));
}
}
/**
* unregister subscriber from event bus
* @param subscriber event listener
*/
public void unregisterSubscriber(Object subscriber) {
if (subscriber != null) {
Iterator<WeakReference<Object>> iterator = subscribers.iterator();
Object obj = null;
while (iterator.hasNext()) {
obj = iterator.next();
if (obj == subscriber) {
subscribers.remove(obj);
obj = null;
break;
}
}
}
}
/**
* post event from other component
* @param event event data object
*/
public void post(Object event) {
if (event instanceof String) {
LogUtils.D(event);
}
dispatchEvent(event);
}
/**
* dispatch event inner
* @param event event data object
*/
private void dispatchEvent(Object event) {
Iterator<WeakReference<Object>> iterator = subscribers.iterator();
WeakReference<Object> obj;
while (iterator.hasNext()) {
obj = iterator.next();
try {
Method method = obj.get().getClass().getMethod("onEvent", Object.class);
method.invoke(obj.get(), event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
关键就是这个 WeakReference
列表,注册的监听对象都保存在这个列表中,当对象销毁后 WeakReference
无法继续持有对象引用不会造成内存泄漏问题。然后通过反射的方式找到对象中监听回调的函数,每次 post 消息后回调监听函数。
相关文章:
EventBus 原理解析
相关文章:
Java内存问题 及 LeakCanary 原理分析
LeakCanary原理解析
在没有特殊申明的情况下我们应用的所有组件都是在同一个进程下的,只有我们特殊申明应用才能开启多进程。
多进程的好处:
多进程的坏处:
androidManifest.xml
中设置组件时,添加一个 android:process
属性,这个属性是指定组件运行的进程名称。
Intent
通过Intent直接传递参数给其他进程组件
AIDL Android Interface Define Language
我们可以使用 android 为我们提供的 AIDL 来实现进程间的通信。
AIDL实际上是为我们进程通信的做了简化,我们只需要申明接口即可,然后编写 C/S 两端即可实现通信。实际上我们也可以不通过 AIDL 完成进程间通信,通过继承 Binder 自己编写中间的过程。
AIDL实际上就是简化Binder通信的工具,Binder通信机制实际是使用进程共享内存实现的进程间通信。
ContentProvider
四大组件之一,为了方便应用间共享数据,底层也是 Binder 实现。
Messenger
AIDL实现
Socket
通过网络编程实现跨进程通信,Socket本来就是异步模型,无论是和远程进程还是本地进程,都是进程通信。
内存泄露的场景有哪些?内存泄漏分析工具使用方法?
常见的内存泄露有:
而对于内存泄露的检测,常用的工具有 LeakCanary 、 MAT(Memory Analyer Tools) 、Android Studio 自带的 Profiler 。
使用 Memory Profiler 时,您应对应用代码施加压力并尝试强制内存泄漏。 在应用中引发内存泄漏的一种方式是,先让其运行一段时间,然后再检查堆。 泄漏在堆中可能逐渐汇聚到分配顶部。 不过,泄漏越小,您越需要运行更长时间的应用才能看到泄漏。
您还可以通过以下方式之一触发内存泄漏:
将设备从纵向旋转为横向,然后在不同的 Activity
状态下反复操作多次。 旋转设备经常会导致应用泄漏 Activity、Context
或 View
对象,因为系统会重新创建 Activity
,而如果您的应用在其他地方保持对这些对象之一的引用,系统将无法对其进行垃圾回收。
处于不同的 Activity
状态时,在您的应用与另一个应用之间切换(导航到主屏幕,然后返回到您的应用)。
提示: 您还可以使用 monkeyrunner 测试框架执行上述步骤。
相关文章:
内存泄漏—Android Studio 3.0 + MAT
Android官方 AS 内存分析文档
如何实现启动优化,有什么工具可以使用?
相关文章:
Android启动速度优化
Android性能优化 – Systrace工具
App优化 Systrace
Android性能分析工具Systrace和TraceView的使用
Systrace的工作原理及例子解读
常用的设计模式有哪些?是否了解责任链模式?
单例模式,观察者模式,工厂模式,建造者模式,构造者模式,适配器模式,职责链模式等
相关文章:
Android 线程池原理及使用
相关文章:
初识JVM及jvm运行时数据区和jvm内存模型的区别
Android面试一天一题(Day 44:实战美团–Java内存模型)
Android App优化之内存优化(序)
GC那些事儿–Android内存优化第一弹
Android是如何管理App内存的–Android内存优化第二弹
Tools, 出来接活了–Android内存优化第三弹
内存泄露实例分析 – Android内存优化第四弹
SQLite 非线程安全,SQLiteDatabase 提供了java锁操作,但是也不支持多线程写操作,给数据库写操作加上同步锁可解决同步写问题;终结解决方案就是只有一个可写的数据库连接,所有写操作读取这个可写数据库即可。
相关文章:
Android多线程操作sqlite(Sqlite解决database locked问题)
Android SQLite是线程安全的吗?
相关文章:
Android App优化之网络优化
相关文章:
Binder机制的原理
相关文章:
剖析Activity、Window、ViewRootImpl和View之间的关系
Android App优化, 要怎么做?
Java的内存回收机制详解
大厂OPPO面试— Android 开发技术面总结
OppoAndroid面试小记
OPPO Android开发技术面总结
Android 高级架构开发工程师常见的面试题(腾讯,百度,平安,OPPO,招商银行)