Android进阶之路Android开发探索android 开发程序员

安卓代码、图片、布局、网络和电量优化

2018-05-06  本文已影响728人  仕明同学

写在前面的话,前段时间写了一篇文章 二十三种设计模式,写的不详细,因为如果要写的很详细,估计一年半载都写不完,完全都是按照自己理解,每个设计模式就画了一个简单的图,同时完成了一个小Demo,哪知道这篇文章成了我在简书点赞最高的一篇文章,实在有点受宠若惊,谢谢各位大佬点赞!!!

这篇文章分为五个部分代码优化、图片优化、布局优化、网络优化、电量优化,尽量每个方法都写了小的Demo!

image.png

Demo下载的地址

F0xJ.png

代码优化:不要做多余的工作,尽量避免次数过多的内存的分配,(需要对api有一定的熟悉)

数据集合的使用:建议最佳的做法是可能使用ArrayList作为首选,只要你需要使用额外的功能的时候,或者当程序性能由于经常从表的中间进行插入和删除而变差的时候,才会去选择LinkedList。HashMap性能上于HashTable相当,因为HashMap和HashTable在底层的存储和查找机制是一样的,但是TreeMap通常比HashMap要慢。HashSet总体上的性能比TreeSet好,特别实在添加和查询元素的时候,而这两个操作也是最重要的操作。TreeSet存在的唯一的原因是它可以维持元素的排序的状态,所以当需要一个排好序的Set,才使用TreeSet。因为其内部的结果欧支持排序,并且因为迭代是我们更有可能执行的操作,所以,用TreeSet迭代通常比用HashSet要快。

  /**
         * 建议最佳的做法是可能使用ArrayList作为首选,只要你需要使用额外的功能的时候,或者当程序性能由于经常从表的中间进行
         * 插入和删除而变差的时候,才会去选择LinkedList
         */
        //数据结构的选择,对于ArrayList,插入的操作特别高昂,并且其代价将随着列表的尺寸的增加而增加
        ArrayList list=new ArrayList();
        //需要执行大量的随机的访问,这个不是一个好的选择,如果是使用迭代器在列表中插入新的数据,使用这个,比较低廉(插入和移除的代价比较低廉)
        LinkedList linkedList=new LinkedList();
        //HashMap性能上于HashTable相当,因为HashMap和HashTable在底层的存储和查找机制是一样的,但是TreeMap通常比HashMap要慢
        HashMap<String,String> hashMap=new HashMap<>();
        /**
         * HashSet总体上的性能比TreeSet好,特别实在添加和查询元素的时候,而这两个操作也是最重要的操作。TreeSet存在的唯一的原因是它
         * 可以维持元素的排序的状态,所以当需要一个排好序的Set,才使用TreeSet。因为其内部的结果欧支持排序,并且因为迭代是我们更有可能
         * 执行的操作,所以,用TreeSet迭代通常比用HashSet要快
         */
        HashSet<String>  hashSet=new HashSet<>();

        TreeSet<String> treeSet=new TreeSet<>();

SparseArray是Android特有的稀疏数组的实现,他是Integer和Object的为例进行的一个映射用于代替 HsahMap<Integer,<E>>,提高性能。

内部核心的实现(二分查找)

 /**
     * 二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。
     * 但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
     * @param array
     * @param size
     * @param value
     * @return
     */
    //二分查找
    static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            /**
             * >>>与>>唯一的不同是它无论原来的最左边是什么数,统统都用0填充。
             * —比如你的例子,byte是8位的,-1表示为byte型是11111111(补码表示法)
             * b>>>4就是无符号右移4位,即00001111,这样结果就是15。
             * 这里相当移动一位,除以二
             */
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;  // value found
            }
        }
        //按位取反(~)运算符 ,没有找到,这个数据
        return ~lo;  // value not present
    }

SpareArray 家族有以下的四类

          //SpareArray 家族有以下的四类
        //用于替换    HashMap<Integer,boolean>
        SparseBooleanArray sparseBooleanArray=new SparseBooleanArray();
        sparseBooleanArray.append(1,false);
        //用于替换    HashMap<Integer,Interger>
        SparseIntArray SparseIntArray=new SparseIntArray();
        SparseIntArray.append(1,1);
        //用于替换    HashMap<Integer,boolean>

        @SuppressLint({"NewApi", "LocalSuppress"})
        SparseLongArray SparseLongArray=new SparseLongArray();
        SparseLongArray.append(1,1111000L);
        //用于替换    HashMap<Integer,boolean>
        SparseArray<String> SparseArray11=new SparseArray<String>();
        SparseArray11.append(1,"dd");

SpareArray中的设计模式:原型模式:这里有使用到了的,原型模式内存中复制数据的,不会调用到类的构造的方法,而且访问的权限对原型模式无效

    SparseArray<String> clone = sparseArray.clone();

Handler正确的使用姿势(☺☺☺)
下面的代码是很多人都会这样写,这样会造成内存泄漏
原因:Handler是和Looper以及MessageQueue一起工作的,在安卓中,一个 应用启动了,系统会默认创建一个主线程服务的Looper对象 ,该Looper对象处理主线程的所有的Message消息,他的生命周期贯穿整个应用。在主线程中使用的Handler的都会默认的绑定到这个looper的对象,咋主线程中创建handler的时候,它会立即关联主线程Looper对象的MessageQueue,这时发送到的MessageQueue 中的Message对象都会持有这个Handler的对象的引用,这样Looper处理消息时Handler的handlerMessage的方法,因此,如果Message还没有处理完成,那么handler的对象不会立即被垃圾回收

   /*-------------old ide 已经告诉我们这里可能内存泄露-------------------*/
    @SuppressLint("HandlerLeak")
    private final Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

//        mHandler.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                // TODO: 2018/4/28  用户即使退出了应用的话,这里也是会执行的 ,通过日记的观察
//                //这里有可能用户退出了Activity
//                System.out.println("shiming mHandler --todo");
//            }
//        },5000);

如何避免,有两点的可以尝试


    InnerHandler innerHandler = new InnerHandler(this);
        innerHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //这里这要 退出了 就不会执行了
                System.out.println("shiming innerHandler --todo");

            }
        },5000);
  public class InnerHandler extends Handler{
        //弱应用,在另外一个地方会讲到
        private final WeakReference<HandlerActivity> mActivityWeakReference;

        public InnerHandler(HandlerActivity activity){
            mActivityWeakReference=new WeakReference<HandlerActivity>(activity);
        }
    }

Context正确的姿势

   //Context的种类
        //Application 全局唯一的Context实例
        Application application = getApplication();
        Context applicationContext = application.getApplicationContext();
        //不同的Activity,得到这个Context,是独立的,不会进行复用
        Context baseContext = this.getBaseContext();

        MyBroadcaseRecriver myBroadcaseRecriver = new MyBroadcaseRecriver();

        //ContentProvider 中的Context

        /**
         *如果创建单利必须需要使用到context对象
         */
        //这样不会内存泄露,不用改动单利类中代码
        SingleInstance.getSingleInstance(getApplication().getApplicationContext());
class SingleInstance {
    private static SingleInstance sSingleInstance;
    private final Context mContext;

    private SingleInstance(Context context){
        mContext = context;
    }
//    因为每次调用实例都需要判断同步锁,很多项目包括很多人都是用这种的
//    双重判断校验的方法,这种的方法看似很完美的解决了效率的问题,但是它
//    在并发量不多,安全性不太高的情况下能完美的运行,但是,
//    在jvm编译的过程中会出现指令重排的优化过程,这就会导致singleton实际上
//    没有被初始化,就分配了内存空间,也就是说singleton!=null但是又没有被初始化,
//    这就会导致返回的singletonthird返回的是不完整的
    public static SingleInstance getSingleInstance(Context context){
        if (sSingleInstance==null){
            synchronized (SingleInstance.class){
                if (sSingleInstance==null)   {
                    // TODO: 2018/4/28 注意外面传入的conext对象是否,是哪个 
                    sSingleInstance= new SingleInstance(context);
                    //第二种是改动代码,使用application 中的context变量
                    sSingleInstance= new SingleInstance(context.getApplicationContext());
                    
                }
            }

        }
        return sSingleInstance;
    }
}
         * 和java一样,Android也是基于垃圾回收(GC)机制实现内存的自动的回收,垃圾回收的算法“标记-清除(Mark-Sweep)”
         * “标记压缩(Mark-Compact)“复制算法(Copying)以及引用计数算法(Reference-Counting),安卓的虚拟机(Dalvik还是Art),
         * 都是使用标记清除算法”
         *

        mTextView1.setText(des1);

        /**
         * 在Android中,内存泄露是指不再使用的对象依然占有内存,或者是他们占用的内存没有得到释放,
         * 从而导致内存空间不断的减少,由于可用的空间比较少,发生内存泄露会使得内存更加的紧张,
         * 甚至最终由于内存耗尽而发生的OOM,导致应用的崩溃
         */

        mTextView2.setText(des2);

        String des3="强引用:Java中里面最广泛的使用的一种,也是对象默认的引用类型,如果又一个对象具有强引用,那么垃圾回收器是不会对它进行回收操作的,当内存的空间不足的时候,Java虚拟机将会抛出OutOfMemoryError错误,这时应用将会被终止运行";

        mTextView3.setText(des3);
        String des4="软引用:一个对象如果只有一个软引用,那么当内存空间充足是,垃圾回收器不会对他进行回收操作,只有当内存空间不足的时候,这个对象才会被回收,软引用可以用来实现内存敏感的高速缓存,如果配合引用队列(ReferenceQueue使用,当软引用指向对象被垃圾回收器回收后,java会把这个软引用加入到与之关联的引用队列中)";

        Object obj=new Object();

        SoftReference<Object> sr = new SoftReference<>(obj);//这里使用了软引用...
        /*
         *在这个期间,有可能会出现内存不足的情况发生,那么GC就会直接把所有的软引用全部清除..并释放内存空间
         *如果内存空间足够的话,那么就GC就不会进行工作...
         *GC的工作取决于内存的大小,以及其内部的算法,,,,
         */
        if(sr!=null){
            //如果软引用还存在,那么直接就可以获取这个对象的相关数据...这样就实现了cache...
            obj = sr.get();

        }else{
            //如果已经不存在,表示GC已经将其回收,我们需要重新实例化对象,获取数据信息...
            obj = new Object();
            sr = new SoftReference<>(obj);
        }
        mTextView4.setText(des4);
        String des5="弱引用:弱引用是比软引用更弱的一种的引用的类型,只有弱引用指向的对象的生命周期更短,当垃圾回收器扫描到只有具有弱引用的对象的时候,不敢当前空间是否不足,都会对弱引用对象进行回收,当然弱引用也可以和一个队列配合着使用";

        Object obj1 = new Object();
        WeakReference<Object> weakProductA = new WeakReference<>(obj1);


        mTextView5.setText(des5);
        String des6="虚引用:和软引用和弱引用不同,虚引用并不会对所指向的对象生命周期产生任何影响,也就是对象还是会按照它原来的方式别垃圾回收期回收,虚引用本质上只是有一个标记作用,主要用来跟踪对象被垃圾回收的活动,虚引用必须和引用队列配合使用,当对象被垃圾回收时,如果存在虚引用,那么Java虚拟机会将这个虚引用加入到与之关联的引用队列中";

        mTextView6.setText(des6);
        /**
         * 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
         * 虚引用主要用来跟踪对象被垃圾回收器回收的活动
         */
        // TODO: 2018/5/2 程序可以通过判断引用队列中是否已经加入了虚引用,
        // 来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,
        // 那么就可以在所引用的对象的内存被回收之前采取必要的行动。
        ReferenceQueue queue = new ReferenceQueue ();
        PhantomReference pr = new PhantomReference<Object>(obj1, queue);


        String des7="引用队列:ReferenceQueue一般是作为WeakReference SoftReference 的构造的函数参数传入的,在WeakReference 或者是 softReference 的指向的对象被垃圾回收后,ReferenceQueue就是用来保存这个已经被回收的Reference";
        mTextView7.setText(des7);

  /**
     * list中使用大量的bitmap,这种情况的话,我自己感觉使用的比较少
     */
    public class MemoryCache {
        //将HashMap封装成一个线程安全的集合,并且使用软引用的方式防止OOM(内存不足)...
        //由于在ListView中会加载大量的图片.那么为了有效的防止OOM导致程序终止的情况...
        private Map<String,SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());

        public Bitmap get(String id){
            if(!cache.containsKey(id))
                return null;
            SoftReference<Bitmap>ref=cache.get(id);
            return ref.get();
        }

        public void put(String id,Bitmap bitmap){
            cache.put(id, new SoftReference<Bitmap>(bitmap));
        }
        public void clear(){
            cache.clear();
        }
    }

一个简单的Demo

/**
 * author: Created by shiming on 2018/5/2 14:50
 * mailbox:lamshiming@sina.com
 */
public class EmployeeCache {
    static private EmployeeCache cache;// 一个Cache实例
    private Hashtable<String, EmployeeRef> employeeRefs;// 用于Chche内容的存储
    private ReferenceQueue<Employee> q;// 垃圾Reference的队列

     // 继承SoftReference,使得每一个实例都具有可识别的标识。
    // 并且该标识与其在HashMap内的key相同。
    public class EmployeeRef extends SoftReference<Employee> {
        private String _key = "";
        public EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
            super(em, q);
            _key = em.getID();
        }
    }

    // 构建一个缓存器实例
    private EmployeeCache() {
        employeeRefs = new Hashtable<String, EmployeeRef>();
        q = new ReferenceQueue<Employee>();
    }

    // 取得缓存器实例
    public static EmployeeCache getInstance() {
        if (cache == null) {
            cache = new EmployeeCache();
        }
        return cache;
    }

    // 以软引用的方式对一个Employee对象的实例进行引用并保存该引用
    private void cacheEmployee(Employee em) {
        cleanCache();// 清除垃圾引用
        EmployeeRef ref = new EmployeeRef(em, q);
        employeeRefs.put(em.getID(), ref);
    }

    // 依据所指定的ID号,重新获取相应Employee对象的实例
    public Employee getEmployee(String ID) {
        Employee em = null;
       // 缓存中是否有该Employee实例的软引用,如果有,从软引用中取得。
        if (employeeRefs.containsKey(ID)) {
            EmployeeRef ref = (EmployeeRef) employeeRefs.get(ID);
            em = (Employee) ref.get();
        }
       // 如果没有软引用,或者从软引用中得到的实例是null,重新构建一个实例,
       // 并保存对这个新建实例的软引用
        if (em == null) {
            em = new Employee(ID);
            System.out.println("Retrieve From EmployeeInfoCenter. ID=" + ID);
            this.cacheEmployee(em);
        }
        return em;
    }

    // 清除那些所软引用的Employee对象已经被回收的EmployeeRef对象
    private void cleanCache() {
        EmployeeRef ref = null;
        while ((ref = (EmployeeRef) q.poll()) != null) {
            employeeRefs.remove(ref._key);
        }
    }

    // 清除Cache内的全部内容
    public void clearCache() {
        cleanCache();
        employeeRefs.clear();
        //告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的
        System.gc();
        //强制调用已经失去引用的对象的finalize方法
        System.runFinalization();
    }

    /**
     * 当垃圾收集器认为没有指向对象实例的引用时,会在销毁该对象之前调用finalize()方法。
     * 该方法最常见的作用是确保释放实例占用的全部资源。java并不保证定时为对象实例调用该方法,
     * 甚至不保证方法会被调用,所以该方法不应该用于正常内存处理。
     * @throws Throwable
     */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }
}


/**
 * author: Created by shiming on 2018/5/2 14:49
 * mailbox:lamshiming@sina.com
 */

public class Employee {
    private String id;// 雇员的标识号码
    private String name;// 雇员姓名
    private String department;// 该雇员所在部门
    private String Phone;// 该雇员联系电话
    private int salary;// 该雇员薪资
    private String origin;// 该雇员信息的来源

    // 构造方法
    public Employee(String id) {
        this.id = id;
        getDataFromlnfoCenter();
    }
    // 到数据库中取得雇员信息
    private void getDataFromlnfoCenter() {
// 和数据库建立连接井查询该雇员的信息,将查询结果赋值
// 给name,department,plone,salary等变量
// 同时将origin赋值为"From DataBase"
    }

    public String getID() {
        return id;
    }
}

其他需要注意到的地方:

关于JIT:

图片优化

四种图片格式

   /**
     * 在安卓应用开发中能够使用编解码格式的只有三种 JEPG PNG WEBP
     */
    public enum CompressFormat {
        JPEG    (0),
        PNG     (1),
        WEBP    (2);//安卓4.0后开始支持

        CompressFormat(int nativeInt) {
            this.nativeInt = nativeInt;
        }
        final int nativeInt;
    }

推荐几种图片处理网站

如果ui设计师工作量不饱和的话,可以推荐, 尽量使用 .9.png 点9图 小黑点表示 可拉伸区域,黑边表示纵向显示内容的范围

布局优化:如果创建的层级结构比较复杂,View树嵌套的层次比较深,那么将会使得页面的响应的时间变长,导致运行的时候越来越慢

  <include android:layout_height="50dp"
      android:layout_width="match_parent"
      layout="@layout/layout_include_merge"
      />


<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView android:gravity="center"
        android:text="merge 标签 在某些场景下可以减少布局的层次,由于所有的"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</merge>

注意事项:如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点

   <ViewStub
        android:padding="10dp"
        android:background="@color/colorPrimary"
        android:layout_gravity="center"
        android:inflatedId="@+id/find_view_stub"
        android:id="@+id/view_stub"
        android:layout="@layout/view_stub_imageview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />



<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="10dp"
    android:src="@drawable/ic_launcher_background"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:text="如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!--如果这个根布局是个View,比如说是个ImagView,那么找出来的id为null,得必须注意这一点-->
    <ImageView
        android:layout_marginTop="20dp"
        android:id="@+id/imageview"
        android:padding="10dp"
        android:src="@drawable/ic_launcher_background"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

调用todo: 2018/5/4 為啥為null 原因是布局文件中根布局只有View,没有ViewGroup,ViewStub.inflate() 的方法和 setVisibility 方法是差不多,因为 setVisibility方法会(看源码)走这个inflate的方法

if (null!=mViewStub.getParent()){
                   /*
                   android:inflatedId 的值是Java代码中调用ViewStub的 inflate()或者是serVisibility方法返回的Id,这个id就是被填充的View的Id
                    */
                   /**
                    * ViewStub.inflate() 的方法和 setVisibility 方法是差不多,因为 setVisibility方法会(看源码)走这个inflate的方法
                    */
//                    View inflate = mViewStub.inflate();
                   mViewStub.setVisibility(View.VISIBLE);
                   //inflate--->android.support.v7.widget.AppCompatImageView{de7e3a2 V.ED..... ......I. 0,0-0,0 #7f07003e app:id/find_view_stub}
//                    System.out.println("shiming inflate--->"+inflate);
                   final View find_view_stub = findViewById(R.id.find_view_stub);
                   System.out.println("shiming ----"+find_view_stub);


                   View iamgeivew11 = find_view_stub.findViewById(R.id.imageview);
                   //himing ---- iamgeivew11null
                   // TODO: 2018/5/4 為啥為null  原因是布局文件中根布局只有View,没有ViewGroup
                   System.out.println("shiming ---- iamgeivew11"+iamgeivew11);

               }else{
                   Toast.makeText(LayoutOptimizationActivity.this,"已经inflate了",Toast.LENGTH_LONG).show();
                   final View viewById = findViewById(R.id.find_view_stub);
                   View iamgeivew = findViewById(R.id.imageview);
                   //已经inflate了android.support.v7.widget.AppCompatImageView{4637833 V.ED..... ........ 348,294-732,678 #7f07003e app:id/find_view_stub}
                   System.out.println("shiming l----已经inflate了"+viewById);//
                   System.out.println("shiming l----已经inflate了iamgeivew"+iamgeivew);//已经inflate了iamgeivew==null
                   View iamgeivew11 = viewById.findViewById(R.id.imageview);
                   //已经inflate了 iamgeivew11null
                   System.out.println("shiming l----已经inflate了 iamgeivew11"+iamgeivew11);
               }
           }
<LinearLayout
        android:layout_width="match_parent"
        android:layout_height="150dp">
    <TextView
        android:text="我是文字"
        android:drawableBottom="@mipmap/ic_launcher_round"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
   <TextView
       android:text="我是title2"
       android:drawableEnd="@mipmap/ic_launcher_round"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:drawableRight="@mipmap/ic_launcher_round" />
    <TextView
        android:text="我是文字33"
        android:drawableLeft="@mipmap/ic_launcher_round"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:drawableStart="@mipmap/ic_launcher_round" />

    <TextView
        android:drawableTop="@mipmap/ic_launcher_round"
        android:text="我是文字3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    </LinearLayout>

image.png

网络优化. 移动端对额App几乎都是联网的,网络延迟等会对App的性能产生较大的影响,网络优化可以节约网络流量和电量

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();
        }
    }

PingWorker 类

 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;
            }
        }
    }

HTTPS和HTTP的区别主要为以下四点:


  //基于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);
   /**
     * 图片缓存的核心类
     */
    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);
        }
    }
 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;
        }
    }

NetworkUtils 类

package com.shiming.performanceoptimization.network_optimization;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.telephony.TelephonyManager;



/**
 * author: Created by shiming on 2018/4/28 10:52
 * mailbox:lamshiming@sina.com
 * des:网络连接工具类
 */

public class NetworkUtils {
    private static final String SUBTYPE_TD_SCDMA = "SCDMA";
    private static final String SUBTYPE_WCDMA = "WCDMA";
    private static final String SUBTYPE_CDMA2000 = "CDMA2000";

    /**
     * 判断是否已连接到网络.
     *
     * @param context Context
     * @return 是否已连接到网络
     */
    public static boolean isNetworkConnected(Context context) {
        ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context
                .CONNECTIVITY_SERVICE);
        if (connectivity != null) {
            NetworkInfo info = connectivity.getActiveNetworkInfo();
            if (info != null && info.isConnected()) {
                if (info.getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取当前网络类型
     *
     * @param context Context
     * @return 当前网络类型(Unknown, 2G, 3G, 4G, WIFI)
     */
    public static int getNetworkType(Context context) {
        NetworkInfo info = ((ConnectivityManager) context.getSystemService(
                Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        if (info != null && info.isConnected()) {
            if (info.getType() == ConnectivityManager.TYPE_WIFI) {
                return NetworkType.TYPE_WIFI;
            } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) {
                switch (info.getSubtype()) {
                    case TelephonyManager.NETWORK_TYPE_GPRS:
                    case TelephonyManager.NETWORK_TYPE_EDGE:
                    case TelephonyManager.NETWORK_TYPE_CDMA:
                    case TelephonyManager.NETWORK_TYPE_1xRTT:
                    case TelephonyManager.NETWORK_TYPE_IDEN: //api<8 : replace by 11
                        return NetworkType.TYPE_2G;

                    case TelephonyManager.NETWORK_TYPE_UMTS:
                    case TelephonyManager.NETWORK_TYPE_EVDO_0:
                    case TelephonyManager.NETWORK_TYPE_EVDO_A:
                    case TelephonyManager.NETWORK_TYPE_HSDPA:
                    case TelephonyManager.NETWORK_TYPE_HSUPA:
                    case TelephonyManager.NETWORK_TYPE_HSPA:
                    case TelephonyManager.NETWORK_TYPE_EVDO_B: //api<9 : replace by 14
                    case TelephonyManager.NETWORK_TYPE_EHRPD:  //api<11 : replace by 12
                    case TelephonyManager.NETWORK_TYPE_HSPAP:  //api<13 : replace by 15
                        return NetworkType.TYPE_3G;

                    case TelephonyManager.NETWORK_TYPE_LTE:    //api<11 : replace by 13
                        return NetworkType.TYPE_4G;

                    default:
                        // http://baike.baidu.com/item/TD-SCDMA 中国移动 联通 电信 三种3G制式
                        String subtypeName = info.getSubtypeName();
                        if (SUBTYPE_TD_SCDMA.equalsIgnoreCase(subtypeName) ||
                                SUBTYPE_WCDMA.equalsIgnoreCase(subtypeName) ||
                                SUBTYPE_CDMA2000.equalsIgnoreCase(subtypeName)) {
                            return NetworkType.TYPE_3G;
                        } else {
                            return NetworkType.TYPE_UNKNOWN;
                        }
                }
            }
        }
        return NetworkType.TYPE_UNKNOWN;
    }
}

NetworkType类

package com.shiming.performanceoptimization.network_optimization;

/**
 * author: Created by shiming on 2018/4/28 10:52
 * mailbox:lamshiming@sina.com
 * des:网络连接类型常量
 */

public class NetworkType {
    /**
     * 无网络连接
     */
    public static final int TYPE_UNKNOWN = -1;
    /**
     * 2G
     */
    public static final int TYPE_2G = 0;
    /**
     * 3G
     */
    public static final int TYPE_3G = 1;
    /**
     * 4G
     */
    public static final int TYPE_4G = 2;
    /**
     * WIFI
     */
    public static final int TYPE_WIFI = 3;
}

电量优化

        /**
         *   //设置定位精确度 Criteria.ACCURACY_COARSE比较粗略,Criteria.ACCURACY_FINE则比较精细
         *         criteria.setAccuracy(Criteria.ACCURACY_FINE);
         *         //设置是否要求速度
         *         criteria.setSpeedRequired(false);
         *         // 设置是否允许运营商收费
         *         criteria.setCostAllowed(false);
         *         //设置是否需要方位信息
         *         criteria.setBearingRequired(false);
         *         //设置是否需要海拔信息
         *         criteria.setAltitudeRequired(false);
         *         // 设置对电源的需求
         *         criteria.setPowerRequirement(Criteria.POWER_LOW);
         */
        Criteria criteria = new Criteria();
        criteria.setAccuracy(Criteria.ACCURACY_FINE);
        criteria.setAltitudeRequired(false);
        criteria.setBearingRequired(false);
        criteria.setCostAllowed(true);
        criteria.setPowerRequirement(Criteria.POWER_LOW);
        String serviceName = Context.LOCATION_SERVICE;
        mLocationManager = (LocationManager) getSystemService(serviceName);
//        locationManager.setTestProviderEnabled("gps", true);
        // TODO: 2018/5/3 IllegalArgumentException 'Provider "gps" unknown"   https://www.cnblogs.com/ok-lanyan/archive/2011/10/12/2208378.html
        mLocationManager.addTestProvider(LocationManager.GPS_PROVIDER,
                "requiresNetwork" == "", "requiresSatellite" == "", "requiresCell" == "", "hasMonetaryCost" == "",
                "supportsAltitude" == "", "supportsSpeed" == "",
                "supportsBearing" == "", android.location.Criteria.POWER_LOW,
                android.location.Criteria.ACCURACY_FINE);
        mProvider = mLocationManager.getBestProvider(criteria, true);

        //获取纬度
        //获取经度
        mLlistener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                // thread is not runable, msg ignore, state:TIMED_WAITING, 这里的线程有可能ANR
                if (location != null) {
                    double lat = location.getLatitude();//获取纬度
                    double lng = location.getLongitude();//获取经度
                    System.out.println("shiming   lat+" + lat);
                    System.out.println("shiming   lng+" + lng);
                    String name = Thread.currentThread().getName();
                    mCount++;
                    System.out.println("当前线程的位置name---"+name+"i==="+mCount);
                    mTv_location.setText("位置信息是2s变动的,可以设置,我是第"+mCount+"次变动的--->"+"\n\r"+"lat===="+lat+"      lng----->"+lng);
                }
                if (mLocationManager!=null) {
                    mLocationManager.removeUpdates(this);
                    if (mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
                        mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 2000, 1000, mLlistener);
                    }else {
                        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 1000, mLlistener);
                    }
//                     TODO: 2018/5/3  这里在报错了,我把他注释掉
//                mLocationManager.setTestProviderEnabled(mProvider, false);//   java.lang.IllegalArgumentException: Provider "network" unknown
                }
            }

            @Override
            public void onProviderDisabled(String provider) {

            }

            @Override
            public void onProviderEnabled(String provider) {

            }

            @Override
            public void onStatusChanged(String provider, int status,
                                        Bundle extras) {

            }
        };
        /**
         * minTime:用来指定间更新通知的最小的时间间隔,单位是毫秒,看日志这里是1s更新的
         * minDistance:用来指定位置更新通知的最小的距离,单位是米
         */
        mLocationManager.requestLocationUpdates(mProvider, 2000, (float) 1000.0, mLlistener);

在OnDestroy 变量手动值为null,我在测试过程中,只有在值为null的时候这个位置监听才会停止,有兴趣的小伙伴,可以好好看看值为null,底层会做什么操作

/**
     * 记得需要销毁这个监听,
     * todo 如果不手动置为null的话,其实您可以通过日记发现,这个监听还是一直在走的,所以说这里手动值为null的好处
     */
    protected void onDestroy() {
        if (mAm!=null){
            mAm.cancel(mPi);
            mAm=null;//变为null
        }
        if (null!=mTag){
            mTag.release();
            //释放唤醒锁锁
            mTag=null;
        }
        mLocationManager.removeUpdates(mLlistener);
        if (mLocationManager != null) {
            mLocationManager.removeUpdates(mLlistener);
            mLocationManager = null;//不用分配空间
        }
        if (mLlistener != null) {
            mLlistener = null;
        }
        //   mLocationManager.setTestProviderEnabled(mProvider, false);
        super.onDestroy();
    }
 @SuppressLint("WakelockTimeout")
    private void wakeLockDemo() {
//        PowerManager.PARTIAL_WAKE_LOCK;//保持CPU正常运转,但屏幕和键盘灯有可能是关闭的
//        PowerManager.SCREEN_DIM_WAKE_LOCK://保持CPU正常运转,允许屏幕点亮但可能是置灰的,键盘灯可能是关闭的
//        PowerManager.SCREEN_BRIGHT_WAKE_LOCK;//保持CPU正常的运转,允许屏幕高亮显示,键盘灯可能是关闭的
//        PowerManager.FULL_WAKE_LOCK;//保持CPU正常运转,保持屏幕高亮显示,键盘灯也保持连读
//        PowerManager.ACQUIRE_CAUSES_WAKEUP;//强制屏幕和键盘灯亮起,这种锁针对必须通知用户的操作
//        PowerManager.ON_AFTER_RELEASE;//当WakeLock被释放了,继续保持屏幕和键盘灯开启一定的时间
        PowerManager powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
        /**
         *   case PARTIAL_WAKE_LOCK:
         *             case SCREEN_DIM_WAKE_LOCK:
         *             case SCREEN_BRIGHT_WAKE_LOCK:
         *             case FULL_WAKE_LOCK:
         *             case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
         *             case DOZE_WAKE_LOCK:
         *             case DRAW_WAKE_LOCK:
         */
        mTag = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Tag");
        if (null!= mTag){
            mTag.acquire();
        }

    }

  private void alarmManager() {
        //创建Intent对象,action为ELITOR_CLOCK,附加信息为字符串“你该打酱油了”
        Intent intent = new Intent("action");
        intent.putExtra("msg","重启---App ---Le  -- 回到前台");
//        intent.setClass(ElectricQuantityOptimizationActivity.this,MainActivity.class);
        //定义一个PendingIntent对象,PendingIntent.getBroadcast包含了sendBroadcast的动作。
        //也就是发送了action 为"action"的intent
        mPi = PendingIntent.getBroadcast(this,0,intent,0);

       //AlarmManager对象,注意这里并不是new一个对象,Alarmmanager为系统级服务
        mAm = (AlarmManager)getSystemService(ALARM_SERVICE);

       //设置闹钟从当前时间开始,每隔5s执行一次PendingIntent对象pi,注意第一个参数与第二个参数的关系
        // 5秒后通过PendingIntent pi对象发送广播
        assert mAm != null;
        /**
         * 频繁的报警对电池寿命不利。至于API 22,警报管理器将覆盖近期和高频报警请求,
         * 在未来至少延迟5秒的警报,并确保重复间隔至少为60秒,如果真的需要间隔很短的话,官方建议使用handler
         * 该方法用于设置重复闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟首次执行时间,
         * 第三个参数表示闹钟两次执行的间隔时间,第三个参数表示闹钟响应动作。
         */
        mAm.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000, mPi);
        //该方法用于设置一次性闹钟,第一个参数表示闹钟类型,第二个参数表示闹钟执行时间,第三个参数表示闹钟响应动作。
        //am.set(AlarmManager.RTC_WAKEUP,100000,pi);
        /**
         * (1)int type: 闹钟的类型,常用的有5个值:AlarmManager.ELAPSED_REALTIME、 AlarmManager.ELAPSED_REALTIME_WAKEUP、AlarmManager.RTC、 AlarmManager.RTC_WAKEUP、AlarmManager.POWER_OFF_WAKEUP。AlarmManager.ELAPSED_REALTIME表示闹钟在手机睡眠状态下不可用,该状态下闹钟使用相对时间(相对于系统启动开始),状态值为3;
         * AlarmManager.ELAPSED_REALTIME_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟也使用相对时间,状态值为2;
         *
         * AlarmManager.RTC表示闹钟在睡眠状态下不可用,该状态下闹钟使用绝对时间,即当前系统时间,状态值为1;
         *
         * AlarmManager.RTC_WAKEUP表示闹钟在睡眠状态下会唤醒系统并执行提示功能,该状态下闹钟使用绝对时间,状态值为0;
         *
         * AlarmManager.POWER_OFF_WAKEUP表示闹钟在手机关机状态下也能正常进行提示功能,所以是5个状态中用的最多的状态之一,该状态下闹钟也是用绝对时间,状态值为4;不过本状态好像受SDK版本影响,某些版本并不支持;
         */
        //该方法也用于设置重复闹钟,与第二个方法相似,不过其两个闹钟执行的间隔时间不是固定的而已。
        //基本上相似,只不过这个方法优化了很多,省电
        // am.setInexactRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),1000,pi);
    }


/**
 * author: Created by shiming on 2018/5/3 14:28
 * mailbox:lamshiming@sina.com
 */
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

        String msg = intent.getStringExtra("msg");
        System.out.println("shiming "  +  msg) ;
        Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();

//        ElectricQuantityOptimizationActivity context1 = (ElectricQuantityOptimizationActivity) context;
//        context1.startActivity(intent);
    }
}

提供一个Demo,当app崩溃了,通过AlarmManager来重启App的功能

/**
 * author: Created by shiming on 2018/5/3 14:28
 * mailbox:lamshiming@sina.com
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    public static CrashHandler mAppCrashHandler;

    private Thread.UncaughtExceptionHandler mDefaultHandler;

    private MyApplication mAppContext;

    public void initCrashHandler(MyApplication application) {
        this.mAppContext = application;
        // 获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    public static CrashHandler getInstance() {
        if (mAppCrashHandler == null) {
            mAppCrashHandler = new CrashHandler();
        }
        return mAppCrashHandler;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        ex.printStackTrace();
        AlarmManager mgr = (AlarmManager) mAppContext.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(mAppContext, MainActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("crash", true);
        System.out.println("shiming -----》重启应用了哦");
        PendingIntent restartIntent = PendingIntent.getActivity(mAppContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 5000, restartIntent); // 1秒钟后重启应用
        android.os.Process.killProcess(android.os.Process.myPid());
        System.exit(0);
        System.gc();
    }
}

/**
 * author: Created by shiming on 2018/5/3 14:48
 * mailbox:lamshiming@sina.com
 */

public class MyApplication  extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        /**
         * 程序会重新启动,如果点击电量优化,App崩溃了,请给与全部权限,
         * 还要在开发者模式里面给与位置信息模拟的设置,如果崩溃了,
         * 你也可以发现app会自动的重新启动,这是AlarmManager的应用,注意看MyApplication里面的代码,tks
         */
       CrashHandler.getInstance().initCrashHandler(this);
    }
}

以上就是个人总结的基本,总结的不太全面,同时也不太详细,如果可以的话,还请给个小星星,表示鼓励,谢谢了☺☺☺

git地址PerformanceOptimizationForAndroid

上一篇下一篇

猜你喜欢

热点阅读