阅读《阿里巴巴Android开发手册1.0.1》笔记

背景

2018春节余味尚未消,阿里巴巴为移动开发者们准备了一份迟到的新年礼物——《阿里巴巴Android开发手册》1.0.1版本。

在此写下我的阅读笔记,记录下自己平时没有注意的一些问题,规范自己。

正文

1.【强制】Activity 间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity 检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。

public void viewUrl(String url, String mimeType) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(Uri.parse(url), mimeType);
    if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ ONLY) != null) {
        startActivity(intent);
    } else {
        // 找不到指定的 Activity
    }
}

2.【强制】避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作, 应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。

说明:

由于该方法是在主线程执行,如果执行耗时操作会导致 UI 不流畅。可以使用IntentService 、 创 建 HandlerThread 或 者 调 用 Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 线程执行 onReceive 方法。BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可能会被系统杀死。

3.【 推 荐 】 添 加 Fragment 时 , 确 保 FragmentTransaction#commit() 在 Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。 不要随意使用 FragmentTransaction#commitAllowingStateLoss()来代替,任何 commitAllowingStateLoss()的使用必须经过 code review,确保无负面影响。

说明:

Activity 可能因为各种原因被销毁,Android 支持页面被销毁前通过Activity#onSaveInstanceState() 保 存 自 己 的 状 态 。 但 如 果FragmentTransaction.commit()发生在 Activity 状态保存之后,就会导致 Activity 重 建、恢复状态时无法还原页面状态,从而可能出错。为了避免给用户造成不好的体验,系统会抛出 IllegalStateExceptionStateLoss 异常。推荐的做法是在 Activity 的onPostResume() 或 onResumeFragments() ( 对 FragmentActivity ) 里 执 行 FragmentTransaction.commit(),如有必要也可在 onCreate()里执行。不要随意改用FragmentTransaction.commitAllowingStateLoss() 或 者 直 接 使 用 try-catch 避 免 crash,这不是问题的根本解决之道,当且仅当你确认 Activity 重建、恢复状态时,本次 commit 丢失不会造成影响时才可这么做。

4.【推荐】不要在 Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的 销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在 Activity#onPause()/onStop()中结合 isFinishing()的判断来执行。

5.【推荐】总是使用显式Intent启动或者绑定Service,且不要为服务声明IntentFilter, 保证应用的安全性。如果确实需要使用隐式调用,则可为 Service 提供 Intent Filter 并从 Intent 中排除相应的组件名称,但必须搭配使用 Intent#setPackage()方法设置 Intent 的指定包名,这样可以充分消除目标服务的不确定性。

6.【推荐】对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册 和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。

说明:

对于使用 Context#sendBroadcast()等方法发送全局广播的代码进行提示。如果该广播仅用于应用内,则可以使用 LocalBroadcastManager 来避免广播泄漏以及广播被拦截等安全问题,同时相对全局广播本地广播的更高效。

7.【推荐】当前 Activity 的 onPause 方法执行结束后才会创建(onCreate)或恢复 (onRestart)别的 Activity,所以在 onPause 方法中不适合做耗时较长的工作,这 会影响到页面之间的跳转效率。

8.【推荐】文本大小使用单位 dp,View 大小使用单位 dp。对于 TextView,如果在文 字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问 题。

说明:

之所以文本大小也推荐使用 dp 而非 sp,因为 sp 是 Android 早期推荐使用的,但其 实 sp 不仅和 dp 一样受屏幕密度的影响,还受到系统设置里字体大小的影响,所以使用 dp 对于应用开发会更加保证 UI 的一致性和还原度。

9.【推荐】使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示 Toast 时不能取消上一次 Toast 消息的情况。即使需要连续弹出 Toast,也应避免直 接调用 Toast#makeText。

10.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方 式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:

Executors 返回的线程池对象的弊端如下:

  1. FixedThreadPool 和 SingleThreadPool : 允 许 的 请 求 队 列 长 度 为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;

  2. CachedThreadPool 和 ScheduledThreadPool : 允 许 的 创 建 线 程 数 量 为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

正例:

int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); 

int KEEP_ALIVE_TIME = 1;

TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>(); 

ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());

反例:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

11.【推荐】ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时 线程能被释放.

12. 【推荐】禁止在多进程之间用 SharedPreferences 共享数据,虽然可以 (MODE_MULTI_PROCESS),但官方已不推荐。

13. 【强制】任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。

说明:

Android 应用提供内部和外部存储,分别用于存放应用自身数据以及应用产生的用 户数据。可以通过相关 API 接口获取对应的目录,进行文件操作。

  1. android.os.Environment#getExternalStorageDirectory()

  2. android.os.Environment#getExternalStoragePublicDirectory()

  3. android.content.Context#getFilesDir()

  4. android.content.Context#getCacheDir

正例:

public File getDir(String alName) {
    File file = new File(Environment.getExternalStoragePublicDirectory(Environment.         DIRECTORY_PICTURES), alName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

反例:

public File getDir(String alName) {
    // 任何时候都不要硬编码文件路径,这不仅存在安全隐患,也让 app 更容易出现适配问题 
    File file = new File("/mnt/sdcard/Download/Album", alName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

14.【强制】当使用外部存储时,必须检查外部存储的可用性

正例:

// 读/写检查
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) { 
        return true;
    }
    return false;
    }
// 只读检查
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false; 
}

15.【强制】应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用 FileProvider。

16.【强制】如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受 信任的外部数据直接拼接在原始 SQL 语句中。

???这是个什么梗,都没说清楚???

正例:

// 使用一个可替换参数
String mSelectionClause = "var = ?"; String[] selectionArgs = {""}; selectionArgs[0] = mUserInput;

反例:

// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;

17.【强制】png 图片使用 TinyPNG 或者类似工具压缩处理,减少包体积。

18.【推荐】应根据实际展示需要,压缩图片,而不是直接显示原图。手机屏幕比较小,直接显示原图,并不会增加视觉上的收益,但是却会耗费大量宝贵的内存。

正例:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
    // 首先通过 inJustDecodeBounds=true 获得图片的尺寸
    final BitmapFactory.Options options = new BitmapFactory.Options();  options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);
    // 然后根据图片分辨率以及我们实际需要展示的大小,计算压缩率 
    options.inSampleSize =  calculateInSampleSize(options, reqWidth, reqHeight); 
    // 设置压缩率,并解码
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

19.【强制】在 Activity#onPause()或 Activity#onStop()回调中,关闭当前 activity 正在执 行的的动画。

正例:

public class MyActivity extends Activity {
        ImageView mImageView;
        Animation mAnimation;
        Button mBtn;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            mImageView = (ImageView) findViewById(R.id.ImageView01);
            mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim);

            mBtn = (Button) findViewById(R.id.Button01);
            mBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mImageView.startAnimation(mAnimation);
                }
            };
        }

        @Override
        public void onPause() {
            //页面退出,及时清理动画资源
            mImageView.clearAnimation();
        }
    }

20.【推荐】使用 RGB_565 代替 RGB_888,在不怎么降低视觉效果的前提下,减少内存占用。

说明:

android.graphics.Bitmap.Config 类中关于图片颜色的存储方式定义:

  1. ALPHA_8 代表 8 位 Alpha 位图;
  1. ARGB_4444 代表 16 位 ARGB 位图;
  1. ARGB_8888 代表 32 位 ARGB 位图;
  1. RGB_565 代表 8 位 RGB 位图。

位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存的开销,是解决 OOM 的一种方法。

但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565。

正例:

Config config = drawableSave.getOpacity() != PixelFormat.OPAQUE ?   Config.ARGB_8565 : Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);

反例:

Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);

21.【推荐】在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面,onAnimationEnd 可能会因各种异常没被回调(参考: https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle d-onanimationstart-works-fine ), 建 议 加 上 超 时 保 护 或 通 过 postDelay 替 代onAnimationEnd。

正例:

        View v = findViewById(R.id.xxxViewID);
        final FadeUpAnimation anim = new FadeUpAnimation(v);
        anim.setInterpolator(new AccelerateInterpolator());
        anim.setDuration(1000);
        anim.setFillAfter(true);
        new Handler().postDelayed(new Runnable() {
            public void run() {
                if (v != null) {
                    v.clearAnimation();
                }
            }
        }, anim.getDuration());
        v.startAnimation(anim);

22.【推荐】当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源。

正例:

        View v = findViewById(R.id.xxxViewID);
        final FadeUpAnimation anim = new FadeUpAnimation(v);
        anim.setInterpolator(new AccelerateInterpolator());
        anim.setDuration(1000);
        anim.setFillAfter(true);
        anim.setAnimationListener(new AnimationListener() {
            @Override
            public void onAnimationEnd(Animation arg0) {
            //判断一下资源是否被释放了 
            if (v != null) {
                v.clearAnimation();
            }
        });
        v.startAnimation(anim);

总结

说真的,这手册总结得挺好的,虽然内容少了点,但是才1.0.1版本,还会继续修改完善的。

我觉得上面的第8点写得不太合理:

8.【推荐】文本大小使用单位 dp,View 大小使用单位 dp。对于 TextView,如果在文 字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问 题。

说明:

之所以文本大小也推荐使用 dp 而非 sp,因为 sp 是 Android 早期推荐使用的,但其 实 sp 不仅和 dp 一样受屏幕密度的影响,还受到系统设置里字体大小的影响,所以使用 dp 对于应用开发会更加保证 UI 的一致性和还原度。

我觉得:如果用户设置了系统字体大小,那么肯定是希望系统整体字体变大或变小,而你的APP却不怎么变,这看起来一来不协调,二来没有达到用户修改系统字体大小的目的,感觉这样的做法有点破坏系统的生态,不推荐这样做。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,391评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,350评论 0 17
  • 白马蓝筹股持续调整,临近年底,市场震荡,但我们认为中期向好趋势并没有改变,价值蓝筹整体并未高估。投资者短期可...
    陈悦_Cyue阅读 179评论 0 0
  • 这几天不吵架和好了,我越来越爱你了,爱到无法自拔了快。 今儿晚上和你朋友们一起玩儿,因为有你而开心,而且不知怎地炒...
    撕裂的光线阅读 188评论 0 0
  • 如果命运是一条孤独的河流,谁会是你灵魂的摆渡人? 今天是情人节,对于我来说,没什么特别的感受。而在...
    箬茶阅读 733评论 0 3