通过 IFTTT 自动下载 Instagram 图片到 Google Drive

在 Instagram 关闭 API 之前可以通过 IFTTT 获取别人的更新 Photos, 但是 Instagram 收紧了 API 政策。既拿不到别人更新的信息流,同样也自己Like 别人照片的信息也拿不到了,原来 IFTTT 有两个 Recipes:

  • 一个为自动下载个人 Liked 别人的 Photo 到 Dropbox
  • 另一个为自动下载其他 ID 的更新 Photo

然而这两个 Recipes 都被 IFTTT 删去了,我甚至在 StackOverflow 上问过这件事情,只是几个月过去了,也没有任何实质性的方案。

直到这些天,突然脑袋一道闪光,再此之前,我了解了方法可以导出 Instagram 到 RSS,然后看到了别人自动将 Imgur 中的图片上传到 Google Drive ,使用的是 RSS 导出图片,有人写了一脚本传入 URL,就可以提取 URL 中的图片链接。

正是基于这两个方案,我想到了使用 Instagram to RSS to IFTTT to Google Drive 的方案,然后经过尝试,不需要一行代码的情况下,我实现了自动转存的方案。省去了自己写脚本的时间,同样这个方案也自动适配的Instagram 的网页,并不会因为网页结构的变化而导致失败。只要 RSS 有效,那么就会一直生效。

Instagram to RSS

要做到这个事情,就需要借助 RSS bridge 这个项目,这个项目也是当时我在寻找 InoReader RSS 的时候发现的,关于导出微博、知乎、微信的订阅到 RSS 可以以后在展开详谈。回到 RSS bridge ,这个项目本身就是利用爬虫将网站更新内容生成 RSS,本身支持的网站还是很多的,Flickr、GooglePlus、Twitter、Youtube、Pinterest 等等,当然包括 Instagram。

而我使用了 https://bridge.suumitsu.eu/ 这个网站提供的服务,这个网站架设了 RSS bridge 的服务,当然如果有条件的话自己架设也是很不错的选择,只要一直维护就可以。在网页上选择 Instagram 然后填入 Instagram 的用户 ID ,然后获取 Atom 的源即可。拿到这个 Feed URL,在下一步使用。

if RSS to Google Drive

拿到 Feed URL 之后,到 IFTTT,使用 这个 新建一个 Recipe

分析一下刚刚拿到的 URL

https://bridge.suumitsu.eu/?action=display&bridge=Instagram&u=instagram&format=Atom

其中 u 参数后面跟随着的就是 Instagram 的用户 ID,改变 u 后面的参数为需要自动保存的 ID,然后 Save 即可。


2016-09-30 经验总结 , Instagram , Google Drive , RSS , IFTTT

Android 常见错误

INSTALL_FAILED_NO_MATCHING_ABIS 的解决办法

出现时机

INSTALL_FAILED_NO_MATCHING_ABIS 的解决办法,在 Android 模拟器上安装 apk 的时候出现

解决办法

是由于使用了 native libraries ,该 native libraries 不支持当前的cpu的体系结构。

INSTALL_FAILED_NO_MATCHING_ABIS is when you are trying to install an app that has native libraries and it doesn't have a native library for your cpu architecture. For example if you compiled an app for armv7 and are trying to install it on an emulator that uses the Intel architecture instead it will not work.

现在安卓模拟器的CPU/ABI一般有三种类型,INTEL X86,ARM,MIPS,

如果选择用INTEL X86出现 INSTALL_FAILED_NO_MATCHING_ABIS 的错误,那就改用ARM的

参考:http://stackoverflow.com/questions/24572052/install-failed-no-matching-abis-when-install-apk

Apache HTTP Client

Android 6.0 中移除 了 Apache HTTP client

程序包org.apache.http.client.methods不存在

android {
    useLibrary 'org.apache.http.legacy'
}

2016-09-29 Android , AndroidDev

android 6 runtime permission

在 target API 23 之前,应用申请权限为一次性给予,开发者需要在 Manifest 中使用 users-permission 来申请权限,而用户则是在安装应用时一次性赋予应用所有申请的权限。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

而在 Android API Level 23 也就是 Android 6.0 以后权限的问题被进一步细化,开发者可以在运行时申请权限(Runtime permissions),而此时众多的Android 权限也被细分为 Normal 普通权限 和 Dangerous 危险权限,普通权限和 6.0 以前一样在 Manifest 中申请,并且在安装应用时一次性赋予,而危险权限的申请则需要额外的注意。否则可能会引发异常

java.lang.SecurityException: Permission Denial

所有的权限列表在官方文档 可以查到,每一个权限都标明了 Protection level: normal or dangerous.

运行时获取权限

申请照相权限例子:

/**
 * Requests the Camera permission.
 * If the permission has been denied previously, a SnackBar will prompt the user to grant the
 * permission, otherwise it is requested directly.
 */
private void requestCameraPermission() {
    Log.i(TAG, "CAMERA permission has NOT been granted. Requesting permission.");

    // BEGIN_INCLUDE(camera_permission_request)
    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.CAMERA)) {
        // Provide an additional rationale to the user if the permission was not granted
        // and the user would benefit from additional context for the use of the permission.
        // For example if the user has previously denied the permission.
        Log.i(TAG,
                "Displaying camera permission rationale to provide additional context.");
            Snackbar.make(mLayout, R.string.permission_camera_rationale,
                    Snackbar.LENGTH_INDEFINITE)
                    .setAction(R.string.ok, new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                                    new String[]{Manifest.permission.CAMERA},
                                    REQUEST_CAMERA);
                        }
                    })
                    .show();
    } else {

        // Camera permission has not been granted yet. Request it directly.
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA},
                REQUEST_CAMERA);
    }
    // END_INCLUDE(camera_permission_request)
}

重要方法

检查权限

ContextCompat 中

public static int checkSelfPermission (Context context, String permission)

检查是否拥有权限,如果有返回 PackageManager PERMISSION_GRANTED, 如果没有则返回 PERMISSION_DENIED

在 ActivityCompat 中

public static boolean shouldShowRequestPermissionRationale (Activity activity, String permission)

在 UI 中弹出对话框申请权限,仅仅只有当当前功能需要权限的时候才需要申请。需要参数,目标 activity ,和需要申请的权限,返回是否需要弹出对话框。该方法监测是否需要申请权限。

申请权限

public static void requestPermissions (Activity activity, String[] permissions, int requestCode)

申请权限的权限需要在 manifest 中定义,权限需要是危险权限 #PROTECTION_DANGEROUS dangerous

普通权限 PROTECTION_NORMAL 会在安装时一次性授予, 同样 PROTECTION_SIGNATURE 权限也会在安装时授予。

定义 signature 权限时,不仅需要添加权限说明,还需要相同的签名。

如果app不拥有申请的权限,在用户接受或者拒绝之后,会收到一个回调,说明是否授予了权限,需要实现接口。

public abstract void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults)

因为申请权限不能保证被授予,所以无论在有没有权限的情况下都要保证app能够运行。

requestPermissions 方法会开始一个 activity 来让用户选择是否授予权限,因此程序自身 Activity 可能会 paused 或者 resumed。进一步,授予某些权限可能会导致重启应用,这种情况下系统会重新生成 activity stack ,之后再调用 onRequestPermissionsResult 。

回调方法 onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 的第一个参数时 requestPermissions 方法传入的,第二个参数是申请的权限,Never null,第三个参数是是否授予权限的结果,也就是 PERMISSION_GRANTED 或者 PERMISSION_DENIED,Never null。

权限介绍

Android 的权限分成四个类别

  • normal 普通级别
  • dangerous 危险级别
  • signature 签名级别
  • signatureOrSystem 系统/签名级别

前两个权限直接定义时候即可,6.0以后 dangerous 可以在运行时申请。后两个权限为高级权限,拥有 platform 级别认证才可以申请,应用在没有权限情况下做受限操作,应用会被系统杀掉。

在 Manifest 中使用 来定义权限, 使用 来申请权限。申请的权限需要被系统或者其他应用定义,否则视为无效申请。

users Permission

申请普通权限可以使用 简单语法如下:

<uses-permission android:name="string"
        android:maxSdkVersion="integer" />

前一个参数简单,后以参数 maxSDKVersion 表示,如果应用从某一个版本开始不需要特定的权限,可以设置该属性。表示高于该 API Level 之后就不授予该权限。

如下定义

<uses-permission-sdk-23 android:name="string"
        android:maxSdkVersion="integer" />

表示只有当app运行在 API Level 23 或以上时才申请权限,以下不申请权限。

自定义权限

<permission android:description="string resource"
            android:icon="drawable resource"
            android:label="string resource"
            android:name="string"
            android:permissionGroup="string"
            android:protectionLevel=["normal" | "dangerous" |
                                     "signature" | "signatureOrSystem"] />
  • android:description:对权限的描述,比lable更加的详细,介绍该权限的相关使用情况,比如当用户被询问是否给其他应用该权限时。注意描述应该使用的是string资源,而不是直接使用string串。 android:icon:用来标识该权限的一个图标。
  • android:label:权限的一个给用户展示的简短描述。方便的来说,这个可以直接使用一个string字串来表示,但是如果要发布的话,还是应该使用string资源来表示。
  • android:name:权限的唯一名字,由于独立性,一般都是使用包名加权限名,该属性是必须的,其他的可选,未写的系统会指定默认值。
  • android:permissionGroup: 权限所属权限组的名称,并且需要在这个或其他应用中使用标签提前声明该名称,如果没有声明,该权限就不属于该组。
  • android:protectionLevel:权限的等级

reference


2016-09-27 Android , AndroidDev

Android Snackbar 使用

Snackbar 提供操作的轻量级反馈。显示在手机底部或者大屏幕的左下,Snackbar显示在所有界面的最上层,并且只显示一次。

Snackbar 可以包含一个操作,使用 setAction(CharSequence, android.view.View.onClickListener) 设置。 Snackbar 可以通过 setCallback(Callback) 来设置显示和消失的回调 Snackbar.Callback

显示时间长短的常量

  • int LENGTH_INDEFINITE 没有操作不消失
  • int LENGTH_LONG 显示长时间
  • int LENGTH_SHORT 显示短时间

Android Support Library (22.2.1) 起才支持 LENGTH_INDEFINITE。如果使用该属性, Snackbar 会一直显示,直到调用 dismiss() 或者下一个 Snackbar 出现。

make 方法的第一个参数表示 Snackbar 会寻找该 View 来hold Snackbar 的View。第二个参数为需要显示的字符串。第三个参数为显示时间,使用以上三个常量。

Snackbar.make(mLayout, R.string.permission_camera_rationale,
        Snackbar.LENGTH_INDEFINITE)
        .setAction(R.string.ok, new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.CAMERA},
                        REQUEST_CAMERA);
            }
        })
        .setCallback(new Snackbar.Callback() {
            @Override
            public void onDismissed(Snackbar snackbar, int event) {
                super.onDismissed(snackbar, event);
                Toast.makeText(getApplicationContext(), "onDismissed", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onShown(Snackbar snackbar) {
                super.onShown(snackbar);
                Toast.makeText(getApplicationContext(), "onShown", Toast.LENGTH_LONG).show();
            }
        })
        .show();

综上,Snackbar 作为带响应的通知来说能带来不错的体验。相较于 Toast 来说,Snackbar 能够提供一种操作,对于修改内容来说,提供短时间内的撤销操作应该是不错的。其他能够想到的一些操作,比如撤销邮件的发送,撤销消息的发送,等等。


2016-09-26 Android , AndroidDev

每天学习一个命令:tar 压缩和解压文件

tar 本质上只是一个打包命令,可以将多个文件或者文件夹打包到一个 tar 文件中,结合其他的压缩程序再将打包后的档案文件压缩。所以看到 .tar.gz, .tar.bz2, .tar.xz 等等文件其实是 tar 文件之后进行 Gzip, Bzip2, XZ 压缩之后的文件。

命令格式

tar [-] A --catenate --concatenate | c --create | d --diff --compare |
     --delete | r --append | t --list | --test-label | u --update | x
     --extract --get [options] [pathname ...]

tar 命令常用参数

-c      创建 archive
-x      解压文件
-f ARCHIVE      使用该 ARCHIVE
-v      输出 verbose 日志
-t      测试压缩文件内容

-z, --gzip, --gunzip,  gzip 格式
-j  支持 bzip2 格式

使用实例

常见的压缩和解压用法

tar -cvf filename.tar /folder    # 仅打包不压缩
tar -xvf filename.tar            # 解压包

压缩与解压 gzip:

tar -zcvf filename.tar.gz /folder # gzip 压缩
tar -zxvf filename.tar.gz         # 当前目录下解压文件

压缩解压 bzip2 / bz2

tar -jcvf filename.tar.bz2 /folder # bzip2 压缩
tar -jxvf filename.tar.bz2 -C /path # 解压

压缩解压 tar.xz

tar -Jcvf filename.tar.xz /folder  # xz 压缩
tar -Jxvf filename.tar.xz          # 解压

解释

  • -c 表示创建
  • -x 表示解压
  • -t 表示查看压缩包内容

      注意 c/x/t 三个参数不能同时使用
    
  • -v 表示打印出日志
  • -j 表示 bzip2 压缩方法
  • -J 表示 xz 压缩方法
  • -z 表示 gzip 压缩方法
  • -f ARCHIVE 后面接文件,-f 后面需要直接接压缩包名

经过上面的解释,可以习惯上可以记忆成 压缩格式 (z/j/J) + 压缩 / 解压 / 查看 (c/x/t) + v + f 文件名

压缩时排除绝对路径

有的时候在打包文件的时候会跟随着很长的路径,如果不想要这个很长的路径可以使用 -C 参数来将目录 change to directory

比如想要备份 Docker volume 目录 /var/lib/docker/volumes/chevereto_chevereto_content/,如果:

tar -zcvf backup_content.tar.gz /var/lib/docker/volumes/chevereto_chevereto_content/

这样打包,最后的压缩包内容会将整个相对目录也打包进去,可以使用

tar -zcvf backup_content.tar.gz -C /var/lib/docker/volumes/chevereto_chevereto_content/ .

然后打包的结果 tar 中就只有 chevereto_chevereto_content 目录下的内容。

解压时展开压缩包的目录

比如说在使用 -C 来解压目录的时候,在压缩包内所有的文件内容都在 path-1.4 这样的带版本的文件夹内,如果要想要解压到 path 这样的目录,可以使用:

tar xzvf path-1.4.tar.gz -C path --strip-components=1

注意 path 目录需要存在,否则会报错。

列出压缩包内的文件

tar -ztvf filename.tar.gz     # 列出 tar.gz 下文件
tar -zxvf filename.tar.gz folder/filename   # 仅仅解压某个文件

tar -zcvpf fileetc.tar.gz /etc   # 将 /etc/ 内所有文件备份,并保存其权限 -p

保留文件原始属性

tar -zcvpf file.tar.gz /etc

这里多了一个 -p 参数,保留原始属性时使用,比如打包时不想改变文件的权限等等。

打包时排除文件或文件夹

比如说在打包 /etc/home 目录到 file.tar.gz 文件中时排除 /path 目录

tar --exclude /path -zcvf file.tar.gz /etc /home

这里注意 --exclude 参数

打包比特定时间更新的文件

使用 -N 参数来打包更新的文件

tar -N "2016/01/01" -zcvf download.tar.gz /home/einverne/Download

比如只打包指定目录下文件日期新于 20160101 的文件。

解压到指定目录

使用 -C 参数来指定解压到的目录

tar -zxvf filename.tar.gz -C /path/to/

使用 -C 参数将压缩包内容解压到目录 /path/to/filename

不解压直接查看压缩包内容

tar -tf archive.tar.gz

跨机器压缩传输

上面提到的命令都需要将压缩文件存储到本地,那么如果有一种情况,本地空间有限,无法容纳压缩包的内容,想要实时通过压缩,然后传输到另一台机器,可以使用:1

tar czvf - /source | ssh username@remote.host "cd /destination; tar xzvf -"

跨机器打包

比如要在 A 机器将目录 /www/backup 备份到 B 机器的 /home/einverne/Backup 目录,并压缩:

tar zcvf - /www/backup/ | ssh your_username@ip_of_hostname "cat > /home/einverne/Backup/aapanel.tgz"

Gzip Bzip2 vs XZ

Gzip, Bzip2 和 XZ 是 UNIX 系统下常见的压缩工具。 xz 是一个使用 LZMA 压缩算法的无损数据压缩文件格式,xz 文件格式的压缩率更高。

这里 有篇文章对比了三个工具的压缩率,压缩时间等等

xz 文件

如果不使用上面提及的一步压缩和解压方式,可以拆看先解压,再拆包

xz -d file.tar.xz
tar -xvf file.tar

创建同理

tar -cvf file.tar /file
xz -z file.tar

2016-09-25 tar , linux , archive , extract , command

Android Notification

Android Support v4 包中的 NotificationCompat.Builder ,在 Android 3.0 API Level 11 中才有 Notification.Builder。

创建通知

您可以在 NotificationCompat.Builder 对象中为通知指定 UI 信息和操作。要创建通知,请调用 NotificationCompat.Builder.build(),它将返回包含您的具体规范的 Notification 对象。要发出通知,请通过调用 NotificationManager.notify() 将 Notification 对象传递给系统。

必需的通知内容

Notification 对象必须包含以下内容:

  • 小图标,由 setSmallIcon() 设置
  • 标题,由 setContentTitle() 设置
  • 详细文本,由 setContentText() 设置

setContentIntent() 设置用户点击通知之后的动作。

NotificationCompat.Builder 在构造时自动设置时间为 System.currentTimeMillis() ,设置 Audio stream 为 STREAM_DEFAULT

NotificationCompat.Builder 中可选的其他参数有

  • setAutoCancel() 设置通知是否自动消失
  • setLargeIcon(Bitmap icon) 设置大图标
  • setTicker() 设置通知第一次到达时在status bar 上显示的文字,在Android L版本之后不再显示
  • setLights() 设置通知呼吸灯的颜色以及频率
  • setDeleteIntent() 设置用户直接在通知列表删除通知时的动作
  • setAction() 设置通知中的动作

重要类

NotificationCompat.Builder 不必多说,其他重要的类有 NotificationCompat.Action 通知动作需要包含一个图标,一个标签,一个 PendingIntent 。通知动作在 Android 4.1 之前不会显示。

如果要在通知栏显示复杂View,需要设定 RemoteView,使用 setContent(RemoteViews views) 方法

NotificationCompat.Style 用来展现更丰富的通知内容的样式,直接子类有

  • NotificationCompat.BigPictureStyle
  • NotificationCompat.BigTextStyle
  • NotificationCompat.InboxStyle
  • NotificationCompat.MediaStyle

BigPictureStyle 可以显示大图,如果通知附带一张大图片,可以使用该样式。通常 Android 截屏之后显示的通知就是。

Notification notif = new Notification.Builder(mContext)
     .setContentTitle("New photo from " + sender.toString())
     .setContentText(subject)
     .setSmallIcon(R.drawable.new_post)
     .setLargeIcon(aBitmap)
     .setStyle(new Notification.BigPictureStyle()
         .bigPicture(aBigBitmap))
     .build();

InboxStyle 可以产生多行文本的通知,至多可以显示5个字符串。

Notification noti = new Notification.Builder()
     .setContentTitle("5 New mails from " + sender.toString())
     .setContentText(subject)
     .setSmallIcon(R.drawable.new_mail)
     .setLargeIcon(aBitmap)
     .setStyle(new Notification.InboxStyle()
         .addLine(str1)
         .addLine(str2)
         .setContentTitle("")
         .setSummaryText("+3 more"))
     .build();

还有可以显示很多文字的 BigTextStyle,和比较复杂的 MediaStyle。

Android 4.4 以下的通知

android notification

android notification expand

Android 5.0 及以上的通知

android notification 5

reference


2016-09-25 Android , AndroidDev

Android monkey test

Android UI monkey 测试

伪随机用户事件,发送到模拟器或者设备,用来对应用程序进行压力测试。

功能:

  • 设定事件数
  • 操作限定到某一个特定 package
  • 事件类型和频率
  • 调试选项

报错:

  • 如果应用 crash 或者遇到 unhandled exception , monkey 会停止并上报错误
  • 如果应用产生 not responding error , monkey 也会停止并上报

基本使用

基本语法:

adb shell monkey [options] <event-count>

下面的例子是测试在特定包上,发送500随机事件

adb shell monkey -p your.package.name -v 500

一些有用的选项:

Option 描述
-v -vv -vvv 三档等级,越来越详细
--throttle 事件和事件之间延迟
--pct-touch 点击事件,单个点按下抬起,后接百分比
--pct-motion 滑动事件,某一点按下,随机移动距离,抬起
--pct-trackball 模拟轨迹球,包含随机的移动,可能伴随着点击
--pct-nav 外部输入,上下左右操作(没有使用过,但似乎游戏可用)
--pct-syskeys 调整系统事件,包括Home,back ,音量键等等
-p 允许的 package name
-c 指定允许monkey跑的 category,下面有例子
--ignore-crashes 通常monkey 遇到crash 会停止,此选项忽略crash 直到指定次数跑完
--ignore-timeouts 忽略 ANR
--ignore-security-exceptions 忽略Permission error 或者其他 unhandled exception

测试特定Activity

Manifest 文件中定义 category:

<activity android:name="MonkeyActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.MONKEY" />
    </intent-filter>
</activity>

使用命令:

adb shell monkey -p my.package -c android.intent.category.MONKEY -v 500

防止通知栏下拉

在 Android 5.0 Lollipop 及以上系统中可以使用 screen pinning.

  • 在 settings>security>screen pinning
  • 点击 程序切换按钮 multitasking
  • 点击图标上的绿色图钉, pin icon

此时再运行则不会跳出应用。

停止 MonkeyTest

使用以下命令停止 monkey Test :

adb shell ps | awk '/com\.android\.commands\.monkey/ { system("adb shell kill " $2) }'

monkeyrunner

features 功能:

  • 多设备控制,同时在多台设备测试
  • 功能测试
  • 回归测试
  • 可扩展自动化

monkeyrunner 使用 Jython

reference


2016-09-22 Andorid , AndroidDev , monkey

每天学习一个命令:dpkg-reconfigure 命令重新配置软件包

dpkg-reconfigure 命令是 Debian 系 Linux 中用来重新配置软件包的命令,运行该命令可以重新配置软件包第一次安装后的配置问题。

使用方式

sudo dpkg-reconfigure [package name]

举例

常用的配置,比如配置语言

sudo dpkg-reconfigure locales

配置 display manager

sudo dpkg-reconfigure lightdm
sudo dpkg-reconfigure mdm

配置时区

sudo dpkg-reconfigure tzdata

2016-09-21 linux , dpkg-reconfigure , debian , ubuntu , linux-mint

Gradle 重复导入错误

记录一下纠结了两天的问题。

问题

项目中遇到如下错误:

Error:Execution failed for task ':mobile:packageAllDebugClassesForMultiDex'.
> java.util.zip.ZipException: duplicate entry:android/support/annotation/AnyRes.class

这个问题是因为项目中引入了 support-annotations 包,但是导入的其他 jar 包中包含了这个 package,重复导致了 duplicate entry 错误。 在 app 的 build.gradle 中加入:

android{
    ...
    configurations {
        all*.exclude group: 'com.android.support', module: 'support-annotations'
    }
}

总结

项目中尽量只导入一个库,在引用其他库时注意查看该库的依赖版本,在导入 support v4 包或者其他 v7 包时尽量考虑只在主项目中导入一次,在任何 library 中减少使用。 这个问题的由来,应该也是项目历史原因,在我接手时,项目 build.gradle 中 buildTypes 是这样的:

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }
    debug {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }
}

这里竟然 debug 和 release 使用了同样的 minifyEnabled 配置,默认情况下 minifyEnabled 应该是 false,设置成 true 之后,debug 下无法调试,无法设置断点。查看 git 历史这个问题竟然从项目初始就这样,真无法想象他们是怎么调试的。然后将 minifyEnabled 设置成 false 之后就产生了 multiDex 错误,然后是上面的错误。 buildTypes 下 minifyEnabled 选项, 设置为 true 情况下, debug 无法设置断点及调试 1, 但是这个选线在 release 下非常有用,可以减少包的大小,缩减无用代码(shrink unless codes)。

修改后的配置:

buildTypes {
   release {
       minifyEnabled true
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
   }
   debug {
       minifyEnabled false
       debuggable true
   }
}

multiDex 的错误倒是还好解决 2,设置:

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.0"

         defaultConfig {
             minSdkVersion 14 //lower than 14 doesn't support multidex
             targetSdkVersion 22

             // Enabling multidex support.
             multiDexEnabled true
         }
}

dependencies {
    compile 'com.android.support:multidex:1.0.1'
}

然后在 Application 类中设置:

public class YouApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

}

正是这个错误促使我看了 Gradle 中依赖的语法,总结如下:

Dependencies, Android Libraries and Multi-project setup

Gradle 项目可能依赖其他组件,包括外部的二进制文件,或者另一个 Gradle 项目。

本地依赖

下面代码可以添加本地 libs 目录下所有 jar 包:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}


android {
    ...
}

如果想要配置本地 jar 包,可以依照下面顺序:

  • 将 jar 包放入 libs 目录
  • 右击 jar 包,选择 “Add as library”
  • 确认 compile files('libs/xxx.jar')build.gradle 文件中,或者 compile fileTree(dir: 'libs', include: '*.jar')
  • rebuild

远程依赖

Gradle 支持从远端 Maven 或者 Ivy repositories 自动拉取依赖。首先远端 repository 需要加入列表,其次需要定义具体依赖。

repositories {
     jcenter()
}


dependencies {
    compile 'com.google.guava:guava:18.0'
}


android {
    ...
}

Note:

  • jcenter() 是远端库的 URL 缩写, Gradle 支持本地和远程库。
  • Gradle 如果发现依赖需要使用其他依赖会自动 pull 其他依赖。

多项目配置

通常用子目录来配置 libraries ,例如

MyProject/
 + app/
 + libraries/
    + lib1/
    + lib2/

三个项目, Gradle 通过如下指定:

:app
:libraries:lib1
:libraries:lib2

每一个项目都有自己的 build.gradle 文件,另外在跟目录下有 settings.gradle 来描述工程:

MyProject/
 | settings.gradle
 + app/
    | build.gradle
 + libraries/
    + lib1/
       | build.gradle
    + lib2/
       | build.gradle

settings.gradle 中的描述:

include ':app', ':libraries:lib1', ':libraries:lib2'

然后在 :app 项目中定义,依赖 library :

dependencies {
    compile project(':libraries:lib1')
}

排除导入的包

可以使用如下的语法排除 v4 包:

compile ('com.android.support:recyclerview-v7:+') {
    exclude module: 'support-v4'
}

同样也可以在:

android{
    configurations {
        'com.android.support', module: 'support-v4'
    }
}

下排除。

更加系统的学习 Android Gradle build system 可以参考官方文档,或者中文译本 3

关于 build.gradle 文件中的字段属性,可以参考 Android Plugin DSL Reference.

referencee

  1. http://stackoverflow.com/questions/31926189/android-debugging-with-minifyenabled-true 

  2. StackOverflow官网 

  3. Gradle for Android 


2016-09-18 android , androiddev , gradle , google

adb shell dumpsys 使用

adb 相关的命令在 这篇 文章中已经提及。这里主要展开 adb shell dumpsys 这个子命令。

今天在查当前运行的 Activity 时遇见这个命令。

当前 service

adb shell dumpsys,默认打印出当前系统所有 service 信息

获取屏幕信息

adb shell dumpsys display |grep DisplayDevice

获取电源管理信息

adb shell dumpsys power

电池信息

adb shell dumpsys battery

Current Battery Service state:
  AC powered: false
  USB powered: true
  Wireless powered: false
  Max charging current: 500000
  status: 5
  health: 2
  present: true
  level: 100
  scale: 100
  voltage: 4276
  temperature: 305
  technology: Li-ion

CPU 信息

adb shell dumpsys cpuinfo

Load: 5.76 / 6.18 / 6.54
CPU usage from 591395ms to 291331ms ago with 99% awake:
  6.9% 497/surfaceflinger: 3.2% user + 3.6% kernel
  6.5% 2688/com.google.android.googlequicksearchbox:search: 4.1% user + 2.4% kernel
  5.8% 4740/com.qiyi.video: 2.9% user + 2.8% kernel / faults: 529 minor
  3.9% 11113/com.qiyi.video:downloader: 2% user + 1.9% kernel / faults: 270 minor
  2.9% 1441/system_server: 1.9% user + 1% kernel / faults: 25775 minor 1 major
  1.5% 1883/com.android.systemui: 1.3% user + 0.2% kernel / faults: 3487 minor
  1% 28574/kworker/u8:7: 0% user + 1% kernel
  0.9% 2836/kworker/u8:2: 0% user + 0.9% kernel
  0.8% 1862/android.process.media: 0.4% user + 0.3% kernel / faults: 12370 minor 1 major
  0.5% 3330/com.android.vending: 0.3% user + 0.2% kernel / faults: 1483 minor
  0.5% 21190/kworker/u8:3: 0% user + 0.5% kernel
  0.4% 58/irq/169-cpr3: 0% user + 0.4% kernel
  0.4% 30021/kworker/u8:8: 0% user + 0.4% kernel
  0.4% 14747/com.microsoft.office.outlook: 0.3% user + 0.1% kernel / faults: 9563 minor
  0.4% 733/adbd: 0% user + 0.3% kernel / faults: 7954 minor
  0.4% 3/ksoftirqd/0: 0% user + 0.4% kernel
  0.3% 26596/org.thunderdog.challegram: 0.2% user + 0.1% kernel / faults: 160 minor
  0.3% 53/smem_native_rpm: 0% user + 0.3% kernel
  0.3% 15/ksoftirqd/1: 0% user + 0.3% kernel
  0.3% 1849/VosMCThread: 0% user + 0.3% kernel
  0.3% 2785/com.google.android.gms.persistent: 0.2% user + 0% kernel / faults: 1635 minor 2 major
  0.2% 336/cfinteractive: 0% user + 0.2% kernel
  0.1% 4090/com.hjwordgames:pushservice: 0.1% user + 0% kernel / faults: 1551 minor
  0.2% 2135/com.fanli.android.apps: 0.1% user + 0% kernel / faults: 5490 minor
  0.1% 13655/.DaemonService: 0.1% user + 0% kernel
  0.1% 25689/com.douban.frodo: 0.1% user + 0% kernel / faults: 276 minor
  0.1% 17113/com.lastpass.lpandroid: 0.1% user + 0% kernel / faults: 13 minor
  0.1% 3021/com.smzdm.client.android: 0.1% user + 0% kernel / faults: 619 minor
  0.1% 31410/kworker/0:4: 0% user + 0.1% kernel
  0.1% 5053/kworker/0:0: 0% user + 0.1% kernel
  0.1% 7/rcu_preempt: 0% user + 0.1% kernel
  0.1% 31308/kworker/1:1: 0% user + 0.1% kernel
  0.1% 3496/com.smzdm.client.android:QALSERVICE: 0% user + 0% kernel / faults: 1021 minor
  0.1% 2320/com.android.phone: 0% user + 0% kernel / faults: 90 minor
  0.1% 2162/com.catchingnow.icebox:Service: 0.1% user + 0% kernel / faults: 662 minor
  0.1% 10/rcuop/0: 0% user + 0.1% kernel
  0.1% 493/healthd: 0% user + 0.1% kernel
  0.1% 13190/kworker/0:2: 0% user + 0.1% kernel
  0% 4075/com.smzdm.client.android:core: 0% user + 0% kernel / faults: 732 minor 1 major
  0% 941/ipacm: 0% user + 0% kernel
  0% 13475/com.tencent.mm: 0% user + 0% kernel / faults: 58 minor
  0% 25826/com.douban.frodo:pushservice: 0% user + 0% kernel
  0% 16098/com.hjwordgames: 0% user + 0% kernel / faults: 822 minor 2 major
  0% 30029/com.netease.cloudmusic: 0% user + 0% kernel / faults: 604 minor
  0% 444/logd: 0% user + 0% kernel / faults: 1 minor
  0% 2668/com.smzdm.client.android:channel: 0% user + 0% kernel / faults: 496 minor
  0% 739/netd: 0% user + 0% kernel / faults: 578 minor
  0% 2039/com.catchingnow.icebox: 0% user + 0% kernel / faults: 51 minor
  0% 753/thermal-engine: 0% user + 0% kernel
  0% 3616/com.fanli.android.apps:xg_service_v3: 0% user + 0% kernel / faults: 349 minor
  0% 495/lmkd: 0% user + 0% kernel
  0% 730/jbd2/dm-0-8: 0% user + 0% kernel
  0% 742/rild: 0% user + 0% kernel
  0% 6131/com.tencent.mm:push: 0% user + 0% kernel / faults: 75 minor
  0% 9407/cn.wiz.note: 0% user + 0% kernel / faults: 64 minor
  0% 22/ksoftirqd/2: 0% user + 0% kernel
  0% 1856/sdcard: 0% user + 0% kernel
  0% 25/rcuop/2: 0% user + 0% kernel
  0% 496/servicemanager: 0% user + 0% kernel
  0% 1381/kworker/1:3: 0% user + 0% kernel
  0% 2141/com.oneplus.camera: 0% user + 0% kernel
  0% 765/cnss-daemon: 0% user + 0% kernel
  0% 32524/kworker/2:2: 0% user + 0% kernel
  0% 5673/com.netease.cloudmusic:play: 0% user + 0% kernel / faults: 196 minor
  0% 6991/com.jingdong.app.reader: 0% user + 0% kernel / faults: 567 minor
  0% 18/rcuop/1: 0% user + 0% kernel
  0% 587/sensors.qcom: 0% user + 0% kernel / faults: 62 minor
  0% 2291/com.oneplus.camera:picture: 0% user + 0% kernel
  0% 2300/com.oneplus.gallery: 0% user + 0% kernel
  0% 1096/xposed_service_app: 0% user + 0% kernel
  0% 4964/irq/21-408000.q: 0% user + 0% kernel
  0% 5109/kworker/3:1: 0% user + 0% kernel
  0% 436/kworker/0:1H: 0% user + 0% kernel
  0% 1848/wlan_logging_th: 0% user + 0% kernel
  0% 10572/cn.wiz.note:channel: 0% user + 0% kernel / faults: 502 minor
  0% 32/rcuop/3: 0% user + 0% kernel
  0% 187/hwrng: 0% user + 0% kernel
  0% 735/file-storage: 0% user + 0% kernel
  0% 2333/com.android.launcher3: 0% user + 0% kernel / faults: 8 minor
  0% 4982/perfd: 0% user + 0% kernel
  0% 8/rcu_sched: 0% user + 0% kernel
  0% 29/ksoftirqd/3: 0% user + 0% kernel
  0% 143/kswapd0: 0% user + 0% kernel
  0% 428/ueventd: 0% user + 0% kernel
  0% 5734/com.duokan.reader:pushservice: 0% user + 0% kernel / faults: 3 minor
  0% 26019/com.instagram.android:mqtt: 0% user + 0% kernel / faults: 112 minor
  0% 11/rcuos/0: 0% user + 0% kernel
  0% 760/zygote64: 0% user + 0% kernel / faults: 1215 minor
  0% 2272/com.quicinc.cne.CNEService: 0% user + 0% kernel / faults: 6 minor
  0% 3381/com.google.android.gms: 0% user + 0% kernel / faults: 393 minor
  0% 29600/kworker/3:2: 0% user + 0% kernel
  0% 451/vold: 0% user + 0% kernel
  0% 745/installd: 0% user + 0% kernel
  0% 2103/wpa_supplicant: 0% user + 0% kernel
  0% 2277/com.android.nfc: 0% user + 0% kernel
  0% 7631/com.instagram.android: 0% user + 0% kernel / faults: 3 minor
  0% 26/rcuos/2: 0% user + 0% kernel
  0% 44/msm_watchdog: 0% user + 0% kernel
  0% 494/dashd: 0% user + 0% kernel
  0% 750/cnd: 0% user + 0% kernel
  0% 2682/com.fanli.android.apps:channel: 0% user + 0% kernel
  0% 25022/kworker/2:0H: 0% user + 0% kernel
  0% 29503/kworker/1:0: 0% user + 0% kernel
 +0% 5626/kworker/2:1: 0% user + 0% kernel
 +0% 5854/kworker/3:0: 0% user + 0% kernel
 +0% 5886/kworker/1:4: 0% user + 0% kernel
 +0% 5981/kworker/0:1: 0% user + 0% kernel
 +0% 6096/com.ideashower.readitlater.pro: 0% user + 0% kernel
11% TOTAL: 5.2% user + 4.3% kernel + 0% iowait + 1.3% irq + 0.3% softirq

内存信息

adb shell dumpsys meminfo
# 指定应用内存
adb shell dumpsys meminfo package_name

Activity 信息

adb shell dumpsys activity
adb shell dumpsys activity -h

获取 package 信息

adb shell dumpsys package
adb shell dumpsys package -h
adb shell dumpsys package pacakge_name

获取通知信息

adb shell dumpsys notification

获取 wifi 信息

adb shell dumpsys wifi

电话信息

adb shell dumpsys telephony.registry

输出

  • mCallState 0 表示待机,1 表示来电未接状态,2 表示电话占线
  • mDataConnectionState 0 无数据连接,1 正在创建数据连接,2 已连接

reference


2016-09-09 adb , android , android-dev , shell

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。