安卓性能分析

磁盘

工具:STRICTMODE
问题:主线程 I/O

问题1

存在未关闭的Closable对象

StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
StrictMode: java.lang.Throwable: Explicit termination method 'close' not called
StrictMode:    at dalvik.system.CloseGuard.open(CloseGuard.java:180)
StrictMode:    at android.database.CursorWindow.<init>(CursorWindow.java:111)
StrictMode:    at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
StrictMode:    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:138)
StrictMode:    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)
StrictMode:    at com.yulore.basic.provider.db.handler.CityProDBHandler.a(CityProDBHandler.java:84)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler.querySync(AbsDBHandler.java:160)
StrictMode:    at com.yulore.basic.provider.db.handler.CityProDBHandler.querySync(CityProDBHandler.java:61)
StrictMode:    at com.yulore.basic.location.CityDataBizManager.getCityByName(CityDataBizManager.java:154)
StrictMode:    at com.yulore.basic.location.a$a.onReceiveLocation(BDLocationImpl.java:83)
StrictMode:    at com.baidu.location.LocationClient.callListeners(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient.onNewLocation(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient.access$2900(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient$a.handleMessage(Unknown Source)
StrictMode:    at android.os.Handler.dispatchMessage(Handler.java:102)
StrictMode:    at android.os.Looper.loop(Looper.java:154)
StrictMode:    at android.app.ActivityThread.main(ActivityThread.java:6186)
StrictMode:    at java.lang.reflect.Method.invoke(Native Method)
StrictMode:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
StrictMode:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
StrictMode: Finalizing a Cursor that has not been deactivated or closed. database = /data/user/0/com.huawei.yellowpagetest/databases/YULORE_DB, table = city, query = SELECT province.*, city.* FROM city left outer join province on pro_id = province_id WHERE city_name like ? AND city.pro_id = province.province_id
StrictMode: android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
StrictMode:    at android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:98)
StrictMode:    at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:50)
StrictMode:    at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1318)
StrictMode:    at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1165)
StrictMode:    at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1036)
StrictMode:    at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1242)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:76)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:68)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:63)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:58)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:53)
StrictMode:    at com.yulore.basic.provider.db.d.a(DatabaseDAOProxy.java:43)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler.querySync(AbsDBHandler.java:160)
StrictMode:    at com.yulore.basic.provider.db.handler.CityProDBHandler.querySync(CityProDBHandler.java:61)
StrictMode:    at com.yulore.basic.location.CityDataBizManager.getCityByName(CityDataBizManager.java:154)
StrictMode:    at com.yulore.basic.location.a$a.onReceiveLocation(BDLocationImpl.java:83)
StrictMode:    at com.baidu.location.LocationClient.callListeners(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient.onNewLocation(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient.access$2900(Unknown Source)
StrictMode:    at com.baidu.location.LocationClient$a.handleMessage(Unknown Source)
StrictMode:    at android.os.Handler.dispatchMessage(Handler.java:102)
StrictMode:    at android.os.Looper.loop(Looper.java:154)
StrictMode:    at android.app.ActivityThread.main(ActivityThread.java:6186)
StrictMode:    at java.lang.reflect.Method.invoke(Native Method)
StrictMode:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
StrictMode:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

定位到 CityProDBHandler.java

@Override
protected List<City> parseResult(Cursor cursor) {
    if (cursor == null || cursor.getCount() == 0) {
        return null;
    }
    List<City> cityList = new ArrayList<>();
    try {
        String cityId = null;
        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            String tmpCityId = cursor.getString(cursor.getColumnIndex(ProvinceCityContract.City.CITY_ID));
            if (TextUtils.isEmpty(cityId) || !cityId.equals(tmpCityId)) {
                cityId = tmpCityId;
                City city = cursorToBean(cursor, null);
                cityList.add(city);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return cityList;
}

问题2

存在未关闭的Closable对象

StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.
StrictMode: java.lang.Throwable: Explicit termination method 'close' not called
StrictMode:    at dalvik.system.CloseGuard.open(CloseGuard.java:180)
StrictMode:    at android.database.CursorWindow.<init>(CursorWindow.java:111)
StrictMode:    at android.database.AbstractWindowedCursor.clearOrCreateWindow(AbstractWindowedCursor.java:198)
StrictMode:    at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:138)
StrictMode:    at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)
StrictMode:    at com.yulore.basic.provider.db.handler.FootMarkHandler.c(FootMarkHandler.java:81)
StrictMode:    at com.yulore.basic.provider.db.handler.FootMarkHandler.insertExec(FootMarkHandler.java:27)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler.insertSync(AbsDBHandler.java:205)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler$InsertTask.run(AbsDBHandler.java:427)
StrictMode:    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
StrictMode:    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
StrictMode:    at java.lang.Thread.run(Thread.java:761)
StrictMode: Finalizing a Cursor that has not been deactivated or closed. database = /data/user/0/com.huawei.yellowpagetest/databases/YULORE_DB, table = footmark, query = SELECT footmark.* FROM footmark WHERE title = ?
StrictMode: android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
StrictMode:    at android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:98)
StrictMode:    at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:50)
StrictMode:    at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1318)
StrictMode:    at android.database.sqlite.SQLiteDatabase.queryWithFactory(SQLiteDatabase.java:1165)
StrictMode:    at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1036)
StrictMode:    at android.database.sqlite.SQLiteDatabase.query(SQLiteDatabase.java:1242)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:76)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:68)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:63)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:58)
StrictMode:    at com.yulore.basic.provider.db.c.a(DatabaseDAO.java:53)
StrictMode:    at com.yulore.basic.provider.db.d.a(DatabaseDAOProxy.java:43)
StrictMode:    at com.yulore.basic.provider.db.handler.FootMarkHandler.a(FootMarkHandler.java:41)
StrictMode:    at com.yulore.basic.provider.db.handler.FootMarkHandler.c(FootMarkHandler.java:80)
StrictMode:    at com.yulore.basic.provider.db.handler.FootMarkHandler.insertExec(FootMarkHandler.java:27)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler.insertSync(AbsDBHandler.java:205)
StrictMode:    at com.yulore.basic.provider.db.handler.AbsDBHandler$InsertTask.run(AbsDBHandler.java:427)
StrictMode:    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
StrictMode:    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
StrictMode:    at java.lang.Thread.run(Thread.java:761)

定位 FootMarkHandler.java

@Override
protected long insertExec(Footmark footmark) {
    Cursor cursor = queryExec(footmark.getName());
    if (cursor != null && cursor.getCount() != 0) {
        return updateExec(footmark);
    }
    return mDatabaseDAOProxy.insert(footmark);
}

问题3

对SharedPreferences写入操作,建议优先调用apply而非commit

StrictMode: StrictMode policy violation; ~duration=21 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=65567 violation=2
StrictMode:    at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1293)
StrictMode:    at libcore.io.BlockGuardOs.stat(BlockGuardOs.java:298)
StrictMode:    at android.system.Os.stat(Os.java:501)
StrictMode:    at android.app.SharedPreferencesImpl.writeToFile(SharedPreferencesImpl.java:649)
StrictMode:    at android.app.SharedPreferencesImpl.-wrap2(SharedPreferencesImpl.java)
StrictMode:    at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:535)
StrictMode:    at android.app.SharedPreferencesImpl.enqueueDiskWrite(SharedPreferencesImpl.java:554)
StrictMode:    at android.app.SharedPreferencesImpl.-wrap0(SharedPreferencesImpl.java)
StrictMode:    at android.app.SharedPreferencesImpl$EditorImpl.commit(SharedPreferencesImpl.java:476)
StrictMode:    at com.yulore.basic.utils.SharedPreferencesUtil.putString(SharedPreferencesUtil.java:107)
StrictMode:    at com.yulore.basic.utils.SystemUtil.getIMEI(SystemUtil.java:82)
StrictMode:    at com.yulore.basic.YuloreEngine.register(YuloreEngine.java:48)
StrictMode:    at com.yulore.framework.FrameworkApplication.onCreate(FrameworkApplication.java:31)
StrictMode:    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1025)
StrictMode:    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5448)
StrictMode:    at android.app.ActivityThread.-wrap2(ActivityThread.java)
StrictMode:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1564)
StrictMode:    at android.os.Handler.dispatchMessage(Handler.java:102)
StrictMode:    at android.os.Looper.loop(Looper.java:154)
StrictMode:    at android.app.ActivityThread.main(ActivityThread.java:6186)
StrictMode:    at java.lang.reflect.Method.invoke(Native Method)
StrictMode:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
StrictMode:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

YuloreEngine 中在主线程获取 IMEI,最终调用至
SharedPrefrencesUtil.java

public void putString(String key, String value) {
    SharedPreferences.Editor editor = this.sp.edit();
    editor.putString(key, value);
    editor.commit();
}

问题4

主线程中出现文件读写违例,建议使用工作线程(必要时结合Handler)完成

StrictMode: StrictMode policy violation; ~duration=24 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=65567 violation=2
StrictMode:    at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1293)
StrictMode:    at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:249)
StrictMode:    at java.io.File.exists(File.java:780)
StrictMode:    at android.app.ContextImpl.getDataDir(ContextImpl.java:1938)
StrictMode:    at android.app.ContextImpl.getCacheDir(ContextImpl.java:582)
StrictMode:    at android.content.ContextWrapper.getCacheDir(ContextWrapper.java:253)
StrictMode:    at com.yulore.volley.toolbox.Volley.newRequestQueue(Volley.java:43)
StrictMode:    at com.yulore.volley.toolbox.Volley.newRequestQueue(Volley.java:78)
StrictMode:    at com.yulore.yellowpage.base.integration.VolleyUrlLoader$Factory.getInternalQueue(VolleyUrlLoader.java:32)
StrictMode:    at com.yulore.yellowpage.base.integration.VolleyUrlLoader$Factory.<init>(VolleyUrlLoader.java:43)
StrictMode:    at com.yulore.yellowpage.base.integration.VolleyGlideModule.registerComponents(VolleyGlideModule.java:41)
StrictMode:    at com.bumptech.glide.Glide.get(Glide.java:157)
StrictMode:    at com.yulore.yellowpage.YulorePageEngine.register(YulorePageEngine.java:30)
StrictMode:    at com.yulore.framework.FrameworkApplication.onCreate(FrameworkApplication.java:32)
StrictMode:    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1025)
StrictMode:    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5448)
StrictMode:    at android.app.ActivityThread.-wrap2(ActivityThread.java)
StrictMode:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1564)
StrictMode:    at android.os.Handler.dispatchMessage(Handler.java:102)
StrictMode:    at android.os.Looper.loop(Looper.java:154)
StrictMode:    at android.app.ActivityThread.main(ActivityThread.java:6186)
StrictMode:    at java.lang.reflect.Method.invoke(Native Method)
StrictMode:    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
StrictMode:    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

YulorePageEngine 中在主线程配置 Glide,最终调用至
Volley.java

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

    String userAgent = "volley/0";
    try {
        String packageName = context.getPackageName();
        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
        userAgent = packageName + "/" + info.versionCode;
    } catch (NameNotFoundException e) {
    }

    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            // Prior to Gingerbread, HttpUrlConnection was unreliable.
            // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }

    Network network = new BasicNetwork(stack);

    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();

    return queue;
}

内存

工具:LeakCanary
问题:Activity 内存泄漏

问题1

LeakCanary 捕捉到 SearchListActivity 内存泄漏,概率高。

Screenshot_20171127-170017-w540

问题2

Activity 中放入线程池中的任务如果不能及时执行完毕,在 Activity 销毁时很容易导致内存泄漏。

比如 DetailActivity 在 onDestroy 时就针对该情况作了处理:

@Override
protected void onDestroy() {
    super.onDestroy();
    RequestManager.cancelAll(TAG);
    if (mQueryInfoTask != null) {
        ThreadManager.getInstance().cancel(mQueryInfoTask);
    }
}

但是其他的 Activity 中并未做处理,包括:

  1. CategoryListActivity 中请求分类数据的任务
    private void requestData() {
        int cityId = mSharedPreferencesUtil.getInt(Constant.LOCATION_CITY_ID, 0);
        mCategoryListManager.queryData(categoryLink, cityId, 0.0, 0.0, mCategoryListener);
    }
    
  2. SelectCityActivity 中获取并处理城市数据的 AsyncTask
    new AsyncTask<Void, Void, Void>() {
    
        @Override
        protected Void doInBackground(Void... params) {
    
            //加载定位固定项
            SortCityModel locationCity = new SortCityModel();
            locationCity.setCityName(mLocationCityTitle);
            locationCity.setSortLetters(SideBar.STAR_STR);
            locationCity.setPinyinFull(mLocationCityTitle);
            locationCity.setCityId(-1);
            //加载热门固定项
            SortCityModel hotCity = new SortCityModel();
            hotCity.setCityName(mHotCityTitle);
            hotCity.setSortLetters(SideBar.POUND_STR);
            hotCity.setPinyinFull(mHotCityTitle);
            hotCity.setCityId(-1);
    
            mAllCityList.add(locationCity);
            mAllCityList.add(hotCity);
            //加载数据库所有城市列表
            List<City> allCityList = mCityManager.getAllCityListSync();
            List<City> hotCityList = new ArrayList<>();
            if (allCityList == null || allCityList.size() == 0) {
                return null;
            }
            //加载热门城市列表
            for (City city : allCityList) {
                int hot = city.getHot();
                if (hot == CITY_HOT) {
                    hotCityList.add(city);
                }
            }
            List<SortCityModel> tmpAllCityList = filledData(allCityList);
            Collections.sort(tmpAllCityList, mPinyiComparator);
            mAllCityList.addAll(tmpAllCityList);
            if (hotCityList != null && hotCityList.size() > 0) {
                mHotCityList = filledData(hotCityList);
                Collections.sort(mHotCityList, mPinyiComparator);
            }
            return null;
        }
    
        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            mLoadView.setVisibility(View.GONE);
            if (mAllCityList != null && mAllCityList.size() > 0) {
                mCityListAdapter = new CityListAdapter(mContext, mAllCityList, mHotCityList);
                mCityListAdapter.setOnHotCityListItemClickListener(SelectCityActivity.this);
                mCityListView.setAdapter(mCityListAdapter);
                mCityListView.setOnScrollListener(SelectCityActivity.this);
            } else {
                Toast.makeText(mContext, R.string.select_city_content_empty, Toast.LENGTH_SHORT).show();
            }
        }
    }.execute();
    
  3. HomeFragment 中刷新数据的任务
    @Override
    public void onRefresh(PullToRefreshBase<ScrollView> refreshView) {
        RequestManager.addRequest(new HomeDataRequest(mActivity, 0, listener), TAG);
    }
    /*
    * 设置联网后activity刷新数据
    * */
    public void onRefresh(){
        RequestManager.addRequest(new HomeDataRequest(mActivity, 0, listener), TAG);
    }
    
  4. NearlyListActivity 中获取附近页面数据的任务
    private void requestNearlyData() {
        Logger.d(TAG,"requestNearlyData");
        if (NetworkUtil.isNetConnected(getApplicationContext())){
            NearbyListManager.getInstance().queryDataFromOnline(mURL, mCityId, mStart, mSize, mLat, mLng, mDataListener);
        }else {
            mNoNetworkFailure.setVisibility(View.VISIBLE);
            mLoadingLayout.setVisibility(View.GONE);
        }
    }
    

问题3

Activity 中匿名 Handler 类对其的引用可能导致的内存泄漏,建议修改为静态内部类,通过 WeakReference 来引用外部 Activity。

TeleprompterFragment.java 中

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case LOCAL_TIP:
                String key = (String) msg.obj;
                if (mKeyword != null && mKeyword.equals(key)) {
                    if (mKeywordTipsAdapter == null) {
                        List<String> list = new ArrayList<>();
                        list.add(key);
                        mKeywordTipsAdapter = new KeywordTipsAdapter(mActivity);
                        mTipsListView.setAdapter(mKeywordTipsAdapter);
                    } else {
                        mKeywordTipsAdapter.replaceByOne(key);
                    }
                    setHotAndRecordGone();
//                    if (shopAdapter != null){
//                        shopAdapter.clear();
//                    }
                }
                break;
        }
    }
};

问题4

对于系统服务来说它们的生命周期一般是跟随 APP 的完整生命周期,所以它们如果对外有引用,按照 Java 的声明周期延长法则,这些外部对象也都会被延长生命周期,进而产生内存泄漏。因此在使用系统服务时尽量避免使用界面的 Context。

比如:

// OpenWebFragment.java
mActivity.getSystemService(Context.LOCATION_SERVICE);

// 建议修改为
mActivity.getApplicationContext().getSystemService(Context.LOCATION_SERVICE)

网络

工具:Charles(抓包工具)
问题:无效流量消耗

问题1

CategoryListActivity & MoreCatListActivity

  1. Activity 中的数据为热门分类,应该是有预置的 logo,但在有网络的情况下会优先联网加载,没有充分利用离线资源;
  2. 每次进入都会到网上请求该分类的数据,但是这些数据更新很少,应该设置更新间隔或利用缓存,不要每次都下载数据,浪费用户流量。

问题2

DetailActivity

  1. 每次进入都会请求 Service 数据,请确认是否需要这部分数据,如果不需要请屏蔽该请求,如需要请考虑是否可以设置缓存。

问题3

SearchListActivity

  1. 请求热门搜索词的请求中会返回大量无关数据

-w508

如上图,有用的数据是 freqsearch 中的内容只有7个,而无用的 category 中的内容却有 292 个,且获取到的 category 数据被舍弃掉了。
应该请服务器不要返回 category 中的数据。

问题4

统计功能

在搜索结果页和详情页等地方会进行一些数据统计并上传,但是上传都失败了。如下图:

-w914

其他

权限


  1. apk 申请了很多无用权限,例如闪光灯、振动、通话等权限;

资源


  1. apk 中引入的第三发支付 SDK (微信、支付宝)共 374K,如无需要,请删除 jar 包、相关的 Java 类及 AndroidManifest.xml 中的配置;
  2. libdiff.so 文件共 485K,用于增量更新,该功能仅适用于黑白名单、归属地等文件,不包含热线文件,请考虑是否需要保留;
  3. gradle 中引入的依赖库,有部分是不需要的,比如:recyclerview、cardview、zxing 等,请删除。

发表评论