赞
踩
- <!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
-
- <item name="android:windowFullscreen">true</item>
- <item name="android:windowIsTranslucent">true</item>
-
- </style>
3.启动:整理 初始化依赖关系,我是使用 startup库,主要注意 ContentProvider 的优化(没实践)
Google有提供了优化建议:优化构建速度 | Android 开发者 | Android Developers
因为创建一个对象就意味着垃圾回收器需要回收一个对象,都回耗费时间,
①拼接字符串时,那么可以优先考虑使用StringBuffer或者StringBuilder来进行拼接,而不是加号连接符,因为使用加号连接符会创建多余的对象,拼接的字符串越长,加号连接符的性能越低。
②在没有特殊原因的情况下,尽量使用基本数据类来代替封装数据类型,int比Integer要更加高效,其它数据类型也是一样
- public class ToastUtil {
-
- private static ToastUtil toastUtil;
- private Context context;
-
- private ToastUtil(Context context) {
- this.context = context.getApplicationContext();//注意
- }
-
- public static synchronized ToastUtil getInstance(Context context) {
- if (toastUtil == null) {
- toastUtil = new ToastUtil(context);
- }
- return toastUtil;
- }
}
如果你并不需要访问一个对象中的某些字段,只是想调用它的某个方法来去完成一项通用的功能,那么可以将这个方法设置成静态方法,这会让调用的速度提升15%-20%,同时也不用为了调用这个方法而去专门创建对象了,这样还满足了上面的一条原则。另外这也是一种好的编程习惯,因为我们可以放心地调用静态方法,而不用担心调用这个方法后是否会改变对象的状态(静态方法内无法访问非静态字段)。
注意,这种优化方式只对基本数据类型以及String类型的常量有效,对于其它数据类型的常量是无效的。不过,对于任何常量都是用static final的关键字来进行声明仍然是一种非常好的习惯。
对于ArrayList这种集合,自己手写的循环要比增强型for循环更快,而其他的集合就没有这种情况
一般的for循环写法
- int sum = 0;
-
- Counter[] localArray = mArray;
-
- int len = localArray.length;
-
- for (int i = 0; i < len; ++i) {
-
- sum += localArray[i].mCount;
-
- }
不要写成这样:for (int i = 0; i < mArray.length; ++i)因为这样在每次循环时都会执行 mArray.length
如说String类当中提供的好多API都是拥有极高的效率的,像indexOf()方法和一些其它相关的API
系统中提供的System.arraycopy()方法
- public class Calculate {
-
-
- private int one = 1;
-
-
- private int two = 2;
-
-
- public int getOne() {
-
- return one;
-
- }
-
-
- public int getTwo() {
-
- return two;
-
- }
-
-
- public int getSum() {
-
- //return getOne() + getTwo();//尽量不要这样写
- return one+two
- }
-
-
- }
非静态内部类他会持有他外部类的引用,当外部类关闭时会造成内存泄漏,所以要写成静态内部类
资源未关闭造成的内存泄漏
我们还应当清楚我们所使用语言的内存开支和消耗情况,并且在整个软件的设计和开发当中都应该将这些信息考虑在内。可能有一些看起来无关痛痒的写法,结果却会导致很大一部分的内存开支,例如:
如:可以将findViewById()省去的框架等
ProGuard相信大家都不会陌生,很多人都会使用这个工具来混淆代码,但是除了混淆之外,它还具有压缩和优化代码的功能。ProGuard会对我们的代码进行检索,删除一些无用的代码,并且会对类、字段、方法等进行重命名,重命名之后的类、字段和方法名都会比原来简短很多,这样的话也就对内存的占用变得更少了。
内存抖动是由于短时间内有大量对象进出Young Generiation区导致的,它伴随着频繁的GC。通常存在内存抖动时,我们可以在Android Studio的Monitors中看到如下场景:
内存抖动图
下面是避免发生内存抖动的几点建议:
onDraw()
方法会被频繁调用,所以在这里面不应该频繁的创建对象。总之,因为内存抖动是由于大量对象在短时间内被配置而引起的,所以我们要做的就是谨慎对待那些可能会大量创建对象的情况。
如果父控件有颜色,也是自己需要的颜色,那么就不必在子控件加背景颜色
如果每个自控件的颜色不太一样,而且可以完全覆盖父控件,那么就不需要再父控件上加背景颜色
尽量减少不必要的嵌套
能用LinearLayout和FrameLayout,就不要用RelativeLayout,因为RelativeLayout控件相对比较复杂,测绘也想要耗时。
使用include和merge增加复用,减少层级
ViewStub按需加载,更加轻便
复杂界面可选择ConstraintLayout,可有效减少层级(没多用过)
<include>标签可以允许在一个布局当中引入另外一个布局,如公共的头布局
<include>标签当中的属性会覆盖layout属性,如给include标签若指定了ID属性,而你的layout也定义了ID,则你的layout的ID会被覆盖。如果findViewById()查找layout的Id来查找子控件,子控件会报空指针
需要注意的是,如果我们想要在<include>标签当中覆写layout属性,必须要将layout_width和layout_height这两个属性也进行覆写,否则覆写效果将不会生效。
<merge>标签是作为<include>标签的一种辅助扩展来使用的,它的主要作用是为了防止在引用布局文件时产生多余的布局嵌套。
使用场景:<merge/>多用于替换FrameLayout或者当一个布局包含另一个时,<merge/>标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,引入了一个垂直布局的include,这时如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI表现。这时可以使用<merge/>标签优化。
如:
- <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-
- <Button
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:text="@string/add"/>
-
-
- <Button
-
- android:layout_width="fill_parent"
-
- android:layout_height="wrap_content"
-
- android:text="@string/delete"/>
-
-
- </merge>
仅在需要时加载布局,ViewStub是View的子类,它不可见且大小为0,可以延迟加载布局资源
.ViewStub只能Inflate一次,之后ViewStub对象会被置为空。 而且只能用来Inflate一个布局文件,而不是某个具体的View
使用场景:各种不常用的布局像进度条、显示错误消息等可以使用<ViewStub />标签,以减少内存使用量,加快渲染速度
注意:①还不支持 <merge /> 标签②:如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点
写法:
- ViewStub viewStub = (ViewStub) findViewById(R.id.view_stub);
-
- if (viewStub != null) {
-
- View inflatedView = viewStub.inflate();
-
- editExtra1 = (EditText) inflatedView.findViewById(R.id.edit_extra1);
-
- //或者是下面的形式加载
-
- //myViewStub.setVisibility(View.VISIBLE);
-
- }
产生卡顿的根本原因是UI线程不能够及时的进行渲染,导致UI的反馈不能按照用户的预期,连续、一致的呈现。
onDraw中不要创建新的局部对象
onDraw方法中不要做耗时的任务,也不做过多的循环操作,特别是嵌套循环,虽然每次循环耗时很小,但是大量的循环势必霸占CPU的时间片,从而造成View的绘制过程不流畅。
卡顿主要是 systrace工具 使用,字节码插桩,看图表,查看方法耗时
四种图片格式
JPEG
是一种广泛使用的有损压缩图像标准格式,它不支持透明和多帧动画,一般摄影的作品是JEPG格式的,通过控制压缩比,可以调整图片的大小
PNG
是一种无损压缩的图片格式,他支持完整的透明通道,从图片处理的领域来讲,JEPG只有RGB三个通道,而PNG有ARGB四个通道,因此PNG图片占用空间一般比较大,会无形的增加app的大小,在做app瘦身时一般都要对PNG图片进行梳理以减小其占用的体积
GIF
* 是一种古老的图片的格式,诞生于1987年,随着初代互联网流行开来,他的特别是支持多帧动画,表情图,
Webp
google于2010年发布,支持有损和无损、支持完整的透明通道、也支持多帧动画,目前主流的APP都已经使用了Webp,淘宝,微信,即保证了图片的大小和质量
在安卓应用开发中能够使用编解码格式的只有三种 JEPG PNG WEBP
推荐几种图片处理网站
无损压缩ImageOptin,在不牺牲图片质量的前提下,即减下来PNG图片占用的空间,又提高了图片的加载速度 https://imageoptim.com/api
有损压缩ImageAlpha,图片大小得到极大的缩小,如果需要使用的话,一定要ui设计师看能否使用 https://pngmini.com/
有损压缩TinyPNG 比较知名的png压缩的工具,也需要ui设计师看能够使用不 https://tinypng.com/
PNG/JPEG 转化为 wepb :智图 :http://zhitu.isux.us/
如果ui设计师工作量不饱和的话,可以推荐, 尽量使用 .9.png 点9图 小黑点表示 可拉伸区域,黑边表示纵向显示内容的范围
三种压缩方式:
1.对图片质量进行压缩
2.对图片尺寸进行压缩
3.使用libjpeg.so库进行压缩
对图片质量进行压缩
- public static Bitmap compressImage(Bitmap bitmap){
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
- bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
- int options = 100;
- //循环判断如果压缩后图片是否大于50kb,大于继续压缩
- while ( baos.toByteArray().length / 1024>50) {
- //清空baos
- baos.reset();
- bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
- options -= 10;//每次都减少10
- }
- //把压缩后的数据baos存放到ByteArrayInputStream中
- ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
- //把ByteArrayInputStream数据生成图片
- Bitmap newBitmap = BitmapFactory.decodeStream(isBm, null, null);
- return newBitmap;
- }
对图片尺寸进行压缩
- /**
- * 按图片尺寸压缩 参数是bitmap
- * @param bitmap
- * @param pixelW
- * @param pixelH
- * @return
- */
- public static Bitmap compressImageFromBitmap(Bitmap bitmap, int pixelW, int pixelH) {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
- if( os.toByteArray().length / 1024>512) {//判断如果图片大于0.5M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
- os.reset();
- bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os);//这里压缩50%,把压缩后的数据存放到baos中
- }
- ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inJustDecodeBounds = true;
- options.inPreferredConfig = Bitmap.Config.RGB_565;
- BitmapFactory.decodeStream(is, null, options);
- options.inJustDecodeBounds = false;
- options.inSampleSize = computeSampleSize(options , pixelH > pixelW ? pixelW : pixelH ,pixelW * pixelH );
- is = new ByteArrayInputStream(os.toByteArray());
- Bitmap newBitmap = BitmapFactory.decodeStream(is, null, options);
- return newBitmap;
- }
-
-
- /**
- * 动态计算出图片的inSampleSize
- * @param options
- * @param minSideLength
- * @param maxNumOfPixels
- * @return
- */
- public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
- int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);
- int roundedSize;
- if (initialSize <= 8) {
- roundedSize = 1;
- while (roundedSize < initialSize) {
- roundedSize <<= 1;
- }
- } else {
- roundedSize = (initialSize + 7) / 8 * 8;
- }
- return roundedSize;
- }
-
- private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
- double w = options.outWidth;
- double h = options.outHeight;
- int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
- int upperBound = (minSideLength == -1) ? 128 :(int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));
- if (upperBound < lowerBound) {
- return lowerBound;
- }
- if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
- return 1;
- } else if (minSideLength == -1) {
- return lowerBound;
- } else {
- return upperBound;
- }
- }
使用libjpeg.so库进行压缩
可以参考这篇Android性能优化系列之Bitmap图片优化:
https://blog.csdn.net/u012124438/article/details/66087785)
移动端对额App几乎都是联网的,网络延迟等会对App的性能产生较大的影响,网络优化可以节约网络流量和电量
上网过程的说明流程:对于普通的上网,系统是这样做的:浏览器本身就是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应域名的对应的Ip地址,通过IP地址找到对应Ip对应的服务器,要求建立TCP连接,等浏览器发送完HTTP Request包后,服务器接受到请求包之后才开始处理请求包,服务器调用自身服务,返回Http Response (响应包):客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。具体的文章在这里
Web工作的方式
https://www.jianshu.com/p/84aa55a8a7eb
DNS域名的系统,主要的功能根据应用请求所用的域名URL去网络上面映射表中查相对应的IP地址,这个过程有可能会消耗上百毫秒,而且可能存在着DNS劫持的危险,可以替换为Ip直接连接的方式来代替域名访问的方法,从而达到更快的网络请求,但是使用Ip地址不够灵活,当后台变换了Ip地址的话,会出现访问不了,前段的App需要发包,解决方法是增加Ip地址动态更新的能力,或者是在IP地址访问失败了,切换到域名的访问.
Demo--->ping 一个地址,不正确的话,切换到备用的地址
- boolean ping = ping("wwww.baidu.com");
- /**
- * 测试主域名是否可用
- *
- * @param ip
- * @return
- */
- private final int PING_TIME_OUT = 1000; // ping 超时时间
- private boolean ping(String ip) {
- try {
- Integer status = executeCommandIp( ip, PING_TIME_OUT );
- if ( status != null && status == 0 ) {
- return true;
- } else {
- return false;
- }
- } catch (IOException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (TimeoutException e) {
- e.printStackTrace();
- }
- return false;
- }
- /**
- * 执行域名是否可通
- * @param command
- * @param timeout
- * @return
- * @throws IOException
- * @throws InterruptedException
- * @throws TimeoutException
- */
- private int executeCommandIp( final String command, final long timeout )
- throws IOException, InterruptedException, TimeoutException {
- Process process = Runtime.getRuntime().exec(
- "ping -c 1 -w 100 " + command);
- mWorker = new PingWorker(process);
- mWorker.start();
- try {
- mWorker.join(timeout);
- if (mWorker.exit != null) {
- return mWorker.exit;
- } else {
- //throw new TimeoutException();
- return -1;
- }
- } catch (InterruptedException ex) {
- mWorker.interrupt();
- Thread.currentThread().interrupt();
- throw ex;
- } finally {
- process.destroy();
- }
- }
- class PingWorker extends Thread {
- private final Process process;
- private Integer exit;
- private String ip;
-
- public PingWorker(Process process) {
- this.process = process;
- }
-
- @Override
- public void run() {
- try {
- exit = process.waitFor();
- if (exit == 0) {
- BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()));
- String str = new String();
- StringBuffer ipInfo = new StringBuffer();
-
- //读出所有信息并显示
- while((str=buf.readLine())!=null) {
- ipInfo.append(str);
- }
- /*
- PING sni1st.dtwscache.ourwebcdn.com (14.215.228.4) 56(84) bytes of data.64 bytes from 14.215.228.4: icmp_seq=1 ttl=57 time=16.6 ms--- sni1st.dtwscache.ourwebcdn.com ping statistics ---1 packets transmitted, 1 received, 0% packet loss, time 0msrtt min/avg/max/mdev = 16.656/16.656/16.656/0.000 ms
- */
- System.out.println("shiming ipInfo----->"+ipInfo);
- Pattern mPattern = Pattern.compile("\\((.*?)\\)");
- Matcher matcher = mPattern.matcher(ipInfo.toString());
- if ( matcher.find() ) {
- ip = matcher.group( 1 );
- }
- }
- else {
- ip = " process.waitFor()==="+exit;
- }
- }
- catch (IOException e) {
- e.printStackTrace();
- ip="java.io.IOException: Stream closed";
- return;
- }
- catch (InterruptedException e) {
- ip="java.io.InterruptedException: Stream closed";
- return;
- }
- }
- }
合并网络请求,一次完整的Http请求,首先进行的是DNS查找,通过TCP三次握手,从而建立连接,如果是https请求的话,还要经过TLS握手成功后才可以进行连接,对于网络请求,减少接口,能够合并的网络请求就尽量合并
SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。
HTTPS和HTTP的区别主要为以下四点:
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
预先获取数据能够将网络请求集中在一次,这样其他时间段手机就可以切换到空闲的时间,从而避免经常性的唤醒,从而节约用电
避免轮询:如果说每个一段时间需要向服务器发起主动的网络请求,其实不建议在app端做这样的操作,可以使用推送,如果说在不得已的情况下,也要避免使用Thread.sleep()函数来循环等待,建议使用系统的AlarmManager来实现定时轮询,AlarmManager 可以保证在系统休眠的时候,CPU也可以得到休息,在下一次需要发起网络请求的时候才唤醒
尽量避免网络请求失败时候,无限制的循环重试连接,参考 :https://www.jianshu.com/p/141ee58eb143 中有提到过
- //基于Rxjava 和 RxAndroid Retorfit
- o.subscribeOn(Schedulers.io())
- .retryWhen(new RetryWhenHandler(1, 5))
- .doOnSubscribe(new Action0() {
- @Override
- public void call() {
- s.onBegin();
- }
- })
- .subscribeOn(AndroidSchedulers.mainThread())
- .unsubscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(s);
离线缓存,对于图片或者文件,内存缓存+磁盘缓存+网络缓存,一般我们本地需要做的是二级缓存,当缓存中存在图片或者是文件,直接从缓存中读取,不会走网络,下载图片,在Android中使用LruCache实现内存缓存,DiskLruCache实现本地缓存
- /**
- * 图片缓存的核心类
- */
- private LruCache<String, Bitmap> mLruCache;
- // 缓存大小
- private static final int CACHE_MAX_SIZE = 1024;
-
- /**
- * LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
- */
- private void lruCacheDemo() {
- // 获取应用程序最大可用内存
- int maxMemory = (int) Runtime.getRuntime().maxMemory();
- //设置LruCache缓存的大小,一般为当前进程可用容量的1/8。
- int cacheSize = maxMemory / 8;
- mLruCache = new LruCache<String, Bitmap>(cacheSize) {
- //重写sizeOf方法,计算出要缓存的每张图片的大小
- //这个方法要特别注意,跟我们实例化 LruCache 的 maxSize 要呼应,怎么做到呼应呢,比如 maxSize 的大小为缓存的个数,这里就是 return 1就 ok,如果是内存的大小,如果5M,这个就不能是个数 了,这是应该是每个缓存 value 的 size 大小,如果是 Bitmap,这应该是 bitmap.getByteCount();
- @Override
- protected int sizeOf(String key, Bitmap value) {
- return value.getRowBytes() * value.getHeight();
- }
- 这里用户可以重写它,实现数据和内存回收操作
- @Override
- protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
- if (oldValue != newValue) {
- oldValue.recycle();
- }
- }
- };
- }
- /**
- * 从LruCache中获取一张图片,如果不存在就返回null。
- */
- private Bitmap getBitmapFromLruCache(String key) {
- return mLruCache.get(key);
- }
- /**
- * 往LruCache中添加一张图片
- *
- * @param key
- * @param bitmap
- */
- private void addBitmapToLruCache(String key, Bitmap bitmap) {
- if (getBitmapFromLruCache(key) == null) {
- if (bitmap != null)
- mLruCache.put(key, bitmap);
- }
- }
压缩数据的大小:可以对发送服务端数据进行zip压缩,同时可以使用更优的数据传输格式,例如二进制的代替Json格式,这个比较牛逼,估计运用的很少,使用webp格式代替图片格式
不同的网络环境使用不同的超时策略,常见的网络格式有 2g、3g、4g、wifi,实时的更新当前的网络状态,通过监听来获取最新的网络类型,并动态调整网络超时的时间
- private void netWorkDemo() {
- TextView netWork = findViewById(R.id.net_work);
- boolean networkConnected = NetworkUtils.isNetworkConnected(this);
- int networkType = NetworkUtils.getNetworkType(this);
-
- System.out.println("shiming 是否联网了"+networkConnected);
- switch (networkType){
- case TYPE_UNKNOWN:
- System.out.println("shiming 联网的类型---无网络连接");
- netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---无网络连接");
- break;
- case TYPE_2G:
- System.out.println("shiming 联网的类型---2G");
- netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---2G");
- break;
- case TYPE_3G:
- System.out.println("shiming 联网的类型---TYPE_3G");
- netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_3G");
- break;
- case TYPE_4G:
- System.out.println("shiming 联网的类型---TYPE_4G");
- netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_4G");
- break;
- case TYPE_WIFI:
- System.out.println("shiming 联网的类型---TYPE_WIFI");
- netWork.setText("是否联网了---》"+networkConnected+" 联网的类型---TYPE_WIFI");
- break;
- }
- }
CDN的全称是Content Delivery Network,即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet网络拥挤的状况,提高用户访问网站的响应速度。
①数据传输 蓝牙传输,Wi-Fi传输 移动网络传输 后台数据的管理:根据业务需求,接口尽量避免无效数据的传输 数据传输的频度问题:通过经验值或者是数据统计的方法确定好数据传输的频度,避免冗余重复的数据传输,数据传输过程中要压缩数据的大小,合并网络请求,避免轮询
②位置服务 正确的使用位置复位,是应用耗电的一个关键
Android提供了三种定位
③ AlarmManager 也是比较耗电的,通常情况下需要保证两次唤醒操作的时间间隔不要太短了,在不需要使用唤醒功能的情况下,尽早的取消唤醒功能
参考:
Android APP 性能优化的一些思考
https://www.cnblogs.com/cr330326/p/8011523.html
谷歌官方
http://developer.android.com/topic/performance/
Android性能优化系列之Bitmap图片优化
https://blog.csdn.net/u012124438/article/details/66087785
Android 内存泄漏总结
https://yq.aliyun.com/articles/3009
性能优化工具篇总结性能优化工具篇总结 - 简书
应用开发进阶必经之路之性能优化 http://www.apkbus.com/blog-822721-78922.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。