作者归档:wingyue

adb远程安装APK

在电脑端命令窗口输入安装命令进行安装。

温馨提示:

网上使用的很多apk安装器,或者专用的apk安装应用程序,都是将adb脚本文件和adb调试命令做了批处理打包的简化操作,原理是一样的。
使用adb命令安装apk的方法适用于对Androidi,Lnux,Java较为熟悉的同事。

安卓apk反编译、修改、重新打包、签名全过程

首先明确,反编译别人apk是一件不厚道的事情。代码是程序员辛苦工作的成果,想通过这种手段不劳而获,是不对的。这也说明,代码混淆是非常重要的。本文抱着学习的态度,研究在一些特殊的情况下如果有需要,该怎么反编译apk。

工具简介

apktool,编译和反编译apk,从apk中提取图片和布局资源

dex2jar,将可运行文件classes.dex反编译为jar源码文件

jd-gui,查看jar源码文件

反编译

apktool安装

Windows系统:

1. 首先确保系统安装有Java

2. 下载apktool.bat脚本

3. 下载最新版本的apktool.jar,并且重命名为apktool.jar

4. 将apktool.bat和apktool.jar放在同一目录下,就可以在命令行窗口使用了。

5. 其他系统请参考链接

用法

可以直接在命令行执行apktool.bat查看帮助。这里介绍两个最常用的:

反编译

apktool.bat d [-s] -f <apkPath> -o <folderPath>

注:若不选择文件夹路径直接输 :apktool.bat d -f 1.apk -o 1 将默认生成在系统目录C:\Users\Administrator;

一. 这里需要用到另外两个工具,下载dex2jar并解压。下载jd-gui,这是一个带UI的应用程序。

二. 将需要反编译的apk的后缀名改为.zip或者.rar,然后解压到一个文件夹,得到其中的classes.dex文件。

三. 将classes.dex复制到解压后的dex2jar-2.0文件夹下。从命令行进入到该目录,执行

d2j-dex2jar.bat classes.dex

会生成由classes.dex反编译得到的jar文件,classes-dex2jar.jar。

四. 然后使用jd-gui打开classes-dex2jar.jar,就可以查看源码了。

如果apk在发布的时候加过混淆处理,那么我们也只能得到混淆后的版本。想通过阅读源码来破解别人的apk,难度较大,不过有兴趣可以网上去研究。

修改代码

如果只修改apk相应的资源,那么只要在res文件夹下找到相应的文件替换。

修改代码比较麻烦,因为反编译出来的结果中只有smali文件,即Java虚拟机支持的汇编语言。

如果确实需要修改代码,就得对照smali文件和从classes.dex反编译出来的源码了,按照smali的规范来改动即可。相当于写汇编,难度较大。

五.签名apk文件:

(如何查看签名信息:将签名后的apk文件后缀名改为zip,然后将里面的META-INF文件夹解压出来:输入命令:keytool -printcert –file <签名文件RSA的路径>)

签名文件需要用到keytool.exe和jarsigner.exe,这两个文件都在Java jdk的bin目录下:

1,打开命令行输入以下命令然后回车:

keytool -genkey -alias key.keystore -keyalg RSA -validity 30000 -keystore key.keystore

-genkey 产生证书文件
-alias 产生别名
-keystore 指定密钥库的.keystore文件中
-keyalg 指定密钥的算法,这里指定为RSA(非对称密钥算法)
-validity 为证书有效天数,这里我们写的是30000天

出现如下图所示随便照着填填

2,生成出来的keystore要与apk在同一目录下(一般都默认在系统目录没有修改路径的话C:\Users\Administrator)

命令行再输入以下命令然后回车:

jarsigner -verbose -keystore key.keystore -signedjar xxx-signed.apk xxx-unsigned.apk key.keystore

xxx-signed.apk 指签名后的apk文件名
xxx-unsigned.apk 原来的apk文件名
-verbose 指定生成详细输出 
-keystore 指定数字证书存储路径

这样,就完成了对一个apk的签名过程,然后就可以安装使用了。注意如果你的手机上原来就有这个apk,需先卸载,不然无法安装。

APK反编译之一:基础知识–smali文件阅读

用反编译工具apktool得到src为的smali文件,因此需要了解下smali的语法如下转载博客:

http://blog.csdn.net/lpohvbe/article/details/7981386

APK反编译之一:基础知识

本人接触不久,有错误望请各位神牛不吝赐教,仅仅希望把自己这段时间研究的东西分享一下,如果可以帮助到有需要的童鞋万感荣幸。欢迎评论转载,但请加上转载来源谢谢!请尊重开发者劳动成果!请勿用于非法用途!

作者:lpohvbe | http://blog.csdn.net/lpohvbe/article/details/7981386

     这部分涉及的内容比较多,我会尽量从最基础开始说起,但需要读者一定的android开发基础。但注意可能讲解详细得令人作呕,请根据个人理解程度斟酌。

APK、Dalvik字节码和smali文件

APK文件

大家都应该知道APK文件其实就是一个MIME为ZIP的压缩包,我们修改ZIP后缀名方式可以看到内部的文件结构,例如修改后缀后用RAR打开鳄鱼小顽皮APK能看到的是(Google Play下载的完整版版本):

Where’s My Water.zip\

  • asset\                        <资源目录1:asset和res都是资源目录但有所区别,见下面说明>
  • lib\                             <so库存放位置,一般由NDK编译得到,常见于使用游戏引擎或JNI native调用的工程中>
  • |—armeabi\                |—<so库文件分为不同的CPU架构>
  • |—armeabi-v7a\
  • META-INF\                  <存放工程一些属性文件,例如Manifest.MF>
  • res\                           <资源目录2:asset和res都是资源目录但有所区别,见下面说明>
  • |—drawable\               |—<图片和对应的xml资源>
  • |—layout\                   |—<定义布局的xml资源>
  • |—…
  • AndroidManifest.xml     <Android工程的基础配置属性文件>
  • classes.dex                 <Java代码编译得到的Dalvik VM能直接执行的文件,下面有介绍>
  • resources.arsc             <对res目录下的资源的一个索引文件,保存了原工程中strings.xml等文件内容>

 无关紧要地注:asset和res资源目录的不同在于:

1. res目录下的资源文件在编译时会自动生成索引文件(R.java),在Java代码中用R.xxx.yyy来引用;而asset目录下的资源文件不需要生成索引,在Java代码中需要用AssetManager来访问;

2. 一般来说,除了音频和视频资源(需要放在raw或asset下),使用Java开发的Android工程使用到的资源文件都会放在res下;使用C++游戏引擎(或使用Lua binding等)的资源文件均需要放在asset下。

因为Where’s My Water是使用迪斯尼公司自家的DMO游戏引擎开发,所以游戏中用到的所有资源文件都存放在asset下,除了应用图标这些资源仍需要放在res下。

Dalvik字节码

Dalvik是google专门为Android操作系统设计的一个虚拟机,经过深度的优化。虽然Android上的程序是使用java来开发的,但是Dalvik和标准的java虚拟机JVM还是两回事。Dalvik VM是基于寄存器的,而JVM是基于栈的;Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码。Dalvik VM比JVM速度更快,占用空间更少。

通过Dalvik的字节码我们不能直接看到原来的逻辑代码,这时需要借助如Apktool或dex2jar+jd-gui工具来帮助查看。但是,注意的是最终我们修改APK需要操作的文件是.smali文件,而不是导出来的Java文件重新编译(况且这基本上不可能)。

smali文件

好了,对Dalvik有一定认识后,下面介绍重点:smali,及其语法。

简单的说,smali就是Dalvik VM内部执行的核心代码。它有自己的一套语法,下面即将介绍,如果有JNI开发经验的童鞋则能够很快明白。

      一、smali的数据类型

在smali中,数据类型和Android中的一样,只是对应的符号有变化:

  • B—byte
  • C—char
  • D—double
  • F—float
  • I—int
  • J—long
  • S—short
  • V—void
  • Z—boolean
  • [XXX—array
  • Lxxx/yyy—object

这里解析下最后两项,数组的表示方式是:在基本类型前加上前中括号“[”,例如int数组和float数组分别表示为:[I、[F;对象的表示则以L作为开头,格式是LpackageName/objectName;(注意必须有个分号跟在最后),例如String对象在smali中为:Ljava/lang/String;,其中java/lang对应java.lang包,String就是定义在该包中的一个对象。

或许有人问,既然类是用LpackageName/objectName;来表示,那类里面的内部类又如何在smali中引用呢?答案是:LpackageName/objectName$subObjectName;。也就是在内部类前加“$”符号,关于“$”符号更多的规则将在后面谈到。

     二、函数的定义

函数的定义一般为:

Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type

注意参数与参数之间没有任何分隔符,同样举几个例子就容易明白了:

     1. foo ()V

没错,这就是void foo()。

2. foo (III)Z

这个则是boolean foo(int, int, int)。

3. foo (Z[I[ILjava/lang/String;J)Ljava/lang/String;

看出来这是String foo (boolean, int[], int[], String, long了吗?

 

      三、smali文件内容具体介绍

下面开始进一步分析smali中的具体例子,取鳄鱼小顽皮中的WMWActivity.smali来分析(怎么获得请参考下一节的APK反编译之二:工具介绍,暂时先介绍smali语法),它的内容大概是这样子的:

[plain] view plaincopy

  1. .class public Lcom/disney/WMW/WMWActivity;
  2. .super Lcom/disney/common/BaseActivity;
  3. .source “WMWActivity.java”
  4. # interfaces
  5. .implements Lcom/burstly/lib/ui/IBurstlyAdListener;
  6. # annotations
  7. .annotation system Ldalvik/annotation/MemberClasses;
  8.     value = {
  9.         Lcom/disney/WMW/WMWActivity$MessageHandler;,
  10.         Lcom/disney/WMW/WMWActivity$FinishActivityArgs;
  11.     }
  12. .end annotation
  13. # static fields
  14. .field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = “installationId”
  15. //…
  16. # instance fields
  17. .field private _activityPackageName:Ljava/lang/String;
  18. //…
  19. # direct methods
  20. .method static constructor <clinit>()V
  21.     .locals 3
  22.     .prologue
  23.     //…
  24.     return-void
  25. .end method
  26. .method public constructor <init>()V
  27.     .locals 3
  28.     .prologue
  29.     //…
  30.     return-void
  31. .end method
  32. .method static synthetic access$100(Lcom/disney/WMW/WMWActivity;)V
  33.     .locals 0
  34.     .parameter “x0”
  35.     .prologue
  36.     .line 37
  37.     invoke-direct {p0}, Lcom/disney/WMW/WMWActivity;->initIap()V
  38.     return-void
  39. .end method
  40. .method static synthetic access$200(Lcom/disney/WMW/WMWActivity;)Lcom/disney/common/WMWView;
  41.     .locals 1
  42.     .parameter “x0”
  43.     .prologue
  44.     .line 37
  45.     iget-object v0, p0, Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView;
  46.     return-object v0
  47. .end method
  48. //…
  49. #virtual methods
  50. .method public captureScreen()V
  51.     .locals 4
  52.     .prologue
  53.     //…
  54.     goto :goto_0
  55. .end method
  56. .method public didScreenCaptured()V
  57.     .locals 6
  58.     .prologue
  59.     //…
  60.     goto :goto_0
  61. .end method
      看得一头雾水的话那是正常的。现在我将逐一解析,理解这些符号的含义令你在后面注入代码的时候事半功倍

       1、smali中的继承、接口、包信息

      首先看看开头的几行:
  1] .class public Lcom/disney/WMW/WMWActivity;
  2] .super Lcom/disney/common/BaseActivity;
  3] .source “WMWActivity.java”
  4]
  5] # interfaces
  6] .implements Lcom/burstly/lib/ui/IBurstlyAdListener;
  7]
  8] # annotations
  9] .annotation system Ldalvik/annotation/MemberClasses;
10]     value = {
11]        Lcom/disney/WMW/WMWActivity$MessageHandler;,
12]        Lcom/disney/WMW/WMWActivity$FinishActivityArgs;
13]    }
14] .end annotation
      1-3行定义的是基本信息:这是一个由WMWActivity.java编译得到的smali文件(第3行),它是com.disney.WMW这个package下的一个类(第1行),继承自com.disney.common.BaseActivity(第2行)。
      5-6行定义的是接口信息:这个WMWActivity实现了一个com.burstly.lib.ui这个package下(一个广告SDK)的IBurstyAdListener接口。
      8-14行定义的则是内部类:它有两个成员内部类——MessageHandler和FinishActivityArgs,内部类将在后面小节中会有提及。
      分析完smali文件开头的这些信息,我们已经能在大脑中构造出一个大概这样的Java文件:
  1. class WMWActivity extends BaseActivity implements IBurstlyAdListener{
  2.     //…
  3.     class MessageHandler {
  4.         //…
  5.     }
  6.     class FinishActivityArgs{
  7.         //…
  8.     }
  9. }
      没错,这就是本来WMWActivity.java的大概框架了,成员变量和函数信息?别急,下面正要分析。
      在继续分析之前,有些东西需要先说明一下。前面说过,Dalvik VM与JVM的最大的区别之一就是Dalvik VM是基于寄存器的。基于寄存器是什么意思呢?也就是说,在smali里的所有操作都必须经过寄存器来进行:本地寄存器用v开头数字结尾的符号来表示,如v0、v1、v2、…参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2、…特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数…而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)。本地寄存器没有限制,理论上是可以任意使用的,下面是例子:
[plain] view plaincopy

  1. const/4 v0, 0x0
  2. iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->isRunning:Z
      在上面的两句中,使用了v0本地寄存器,并把值0x0存到v0中,然后第二句用iput-boolean这个指令把v0中的值存放到com.disney.WMW.WMWActivity.isRunning这个成员变量中。即相当于:this.isRunning = false;(上面说过,在非static函数中p0代表的是“this”,在这里就是com.disney.WMW.WMWActivity实例)。关于这两句话的具体指令和含义暂可不用理会,先把Dalvik VM的机制弄明白就可以了,其实语法上和汇编语言非常相似,具体的指令会在后面逐一介绍。

        2、smali中的成员变量

      下面继续介绍有关成员变量的内容:
1 ] # static fields
2 ] .field private static final PREFS_INSTALLATION_ID:Ljava/lang/String; = “installationId”
3 ] //…
4 ]
5 ]
6 ] # instance fields
7 ] .field private _activityPackageName:Ljava/lang/String;
8 ] //…
      上面定义的static fields和instance fields均为成员变量,格式是:.field public/private [static] [final] varName:<类型>。然而static fields和instance fields还是有区别的,当然区别很明显,那就是static fields是static的,而instance则不是。根据这个区别来获取这些不同的成员变量时也有不同的指令。一般来说,获取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等,操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。没有“-object”后缀的表示操作的成员变量对象是基本数据类型,带“-object”表示操作的成员变量是对象类型,特别地,boolean类型则使用带“-boolean”的指令操作。
      (1)、获取static fields的指令类似是:
[plain] view plaincopy

  1. sget-object v0, Lcom/disney/WMW/WMWActivity;->PREFS_INSTALLATION_ID:Ljava/lang/String;
      sget-object就是用来获取变量值并保存到紧接着的参数的寄存器中,在这里,把上面出现的PREFS_INSTALLATION_ID这个String成员变量获取并放到v0这个寄存器中,注意:前面需要该变量所属的类的类型,后面需要加一个冒号和该成员变量的类型,中间是“->”表示所属关系
      (2)、获取instance fields的指令与static fields的基本一样,只是由于不是static变量,不能仅仅指出该变量所在类的类型,还需要该变量所在类的实例。看例子:
[plain] view plaincopy

  1. iget-object v0, p0, Lcom/disney/WMW/WMWActivity;->_view:Lcom/disney/common/WMWView;
      可以看到iget-object指令比sget-object多了一个参数,就是该变量所在类的实例,在这里就是p0即“this”。
      (3)、获取array的还有aget和aget-object,指令使用和上述类似,不细述。
      (4)、put指令的使用和get指令是统一的,直接看例子不解释:
[plain] view plaincopy

  1. const/4 v3, 0x0
  2. sput-object v3, Lcom/disney/WMW/WMWActivity;->globalIapHandler:Lcom/disney/config/GlobalPurchaseHandler;
      相当于:this.globalIapHandler = null;(null = 0x0)
[plain] view plaincopy

  1. .local v0, wait:Landroid/os/Message;
  2. const/4 v1, 0x2
  3. iput v1, v0, Landroid/os/Message;->what:I
      相当于:wait.what = 0x2;(wait是Message的实例)
 

        3、smali中的函数调用

smali中的函数和成员变量一样也分为两种类型,但是不同成员变量中的static和instance之分,而是direct和virtual之分。那么direct method和virtual method有什么区别呢?直白地讲,direct method就是private函数,其余的public和protected函数都属于virtual method。所以在调用函数时,有invoke-direct,invoke-virtual,另外还有invoke-static、invoke-super以及invoke-interface等几种不同的指令。当然其实还有invoke-XXX/range 指令的,这是参数多于4个的时候调用的指令,比较少见,了解下即可。

(1)、invoke-static:顾名思义就是调用static函数的,因为是static函数,所以比起其他调用少一个参数,例如:

[plain] view plaincopy

  1. invoke-static {}, Lcom/disney/WMW/UnlockHelper;->unlockCrankypack()Z

这里注意到invoke-static后面有一对大括号“{}”,其实是调用该方法的实例+参数列表,由于这个方法既不需参数也是static的,所以{}内为空,再看一个例子:

[plain] view plaincopy

  1. const-string v0, “fmodex”
  2. invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V<span style=”font-family: Verdana, sans-serif; “> </span>
      这个是调用static void System.loadLibrary(String)来加载NDK编译的so库用的方法,同样也是这里v0就是参数”fmodex”了。

(2)、invoke-super:调用父类方法用的指令,在onCreate、onDestroy等方法都能看到,略。

(3)、invoke-direct:调用private函数的,例如:

[plain] view plaincopy

  1. invoke-direct {p0}, Lcom/disney/WMW/WMWActivity;->getGlobalIapHandler()Lcom/disney/config/GlobalPurchaseHandler;

这里GlobalPurchaseHandler getGlobalIapHandler()就是定义在WMWActivity中的一个private函数,如果修改smali时错用invoke-virtual或invoke-static将在回编译后程序运行时引发一个常见的VerifyError(更多错误汇总可参照APK反编译之番外三:常见错误汇总)。

(4)、invoke-virtual:用于调用protected或public函数,同样注意修改smali时不要错用invoke-direct或invoke-static,例子:

[plain] view plaincopy

  1. sget-object v0, Lcom/disney/WMW/WMWActivity;->shareHandler:Landroid/os/Handler;
  2. invoke-virtual {v0, v3}, Landroid/os/Handler;->removeCallbacksAndMessages(Ljava/lang/Object;)V
      这里相信大家都已经明白了,主要搞清楚v0是shareHandler:Landroid/os/Handler,v3是传递给removeCallbackAndMessage方法的Ljava/lang/Object参数就可以了。
      (5)、invoke-xxxxx/range:当方法的参数多于5个时(含5个),不能直接使用以上的指令,而是在后面加上“/range”,使用方法也有所不同:
[plain] view plaincopy

  1. invoke-static/range {v0 .. v5}, Lcn/game189/sms/SMS;->checkFee(Ljava/lang/String;Landroid/app/Activity;Lcn/game189/sms/SMSListener;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z
      这个是电信SDK中的付费接口,需要传递6个参数,这时候大括号内的参数需要用省略形式,且需要连续(未求证是否需要从v0开始)。

 

有人也许注意到,刚才看到的例子都是“调用函数”这个操作而已,貌似没有取函数返回的结果的操作?

在Java代码中调用函数和返回函数结果是一条语句完成的,而在smali里则需要分开来完成,在使用上述指令后,如果调用的函数返回非void,那么还需要用到move-result(返回基本数据类型)和move-result-object(返回对象)指令:

[plain] view plaincopy

  1. const/4 v2, 0x0
  2. invoke-virtual {p0, v2}, Lcom/disney/WMW/WMWActivity;->getPreferences(I)Landroid/content/SharedPreferences;
  3. move-result-object v1
      v1保存的就是调用getPreferences(int)方法返回的SharedPreferences实例。
[plain] view plaincopy

  1. invoke-virtual {v2}, Ljava/lang/String;->length()I
  2. move-result v2

v2保存的则是调用String.length()返回的整型。

       4、smali中函数实体分析

下面开始介绍函数实体,其实没有什么特别的地方,只是在植入代码时有一点需要特别注意,举例说明:

[plain] view plaincopy

  1. .method protected onDestroy()V
  2.     .locals 0
  3.     .prologue
  4.     .line 277
  5.     invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V
  6.     .line 279
  7.     return-void
  8. .end method
      这是onDestroy()函数,它的作用大家都知道。首先看到函数内第一句:.local 0,这句话很重要,标明了你在这个函数中最少要用到的本地寄存器的个数。在这里,由于只需要调用一个父类的onDestroy()处理,所以只需要用到p0,所以使用到的本地寄存器数为0。如果不清楚这个规则,很容易在植入代码后忘记修改.local 的值,那么回编译后运行时将会得到一个VerifyError错误,而且极难发现问题所在。我正是被这个问题困扰了很多次,最后研究发现.local的值有这个规律,于是在文档查证了一下果然是这个问题。例如我往onDestroy()增加一句:this.existed = true;那么应该改为(注意修改.local的值为1——使用到了v0这一个本地寄存器):
[plain] view plaincopy

  1. .method protected onDestroy()V
  2.     .locals 1
  3.     .prologue
  4.     .line 277
  5.     const/4 v0, 0x1
  6.     iput-boolean v0, p0, Lcom/disney/WMW/WMWActivity;->exited:Z
  7.     invoke-super {p0}, Lcom/disney/common/BaseActivity;->onDestroy()V
  8.     .line 279
  9.     return-void
  10. .end method
      另外注意到.line这个标识,它是标注了该代码在原Java文件中的行数,它也很有用,想想使用eclipse开发时,遇到错误崩溃时,在catLog不是有提示哪个文件哪一行崩溃的么?Dalvik VM运行到.line XX时就将这个值存起来,如果在这一行运行时出错了,就往catLog输出这个值,这样我们就能看到具体是哪一行的问题了。jd-gui这个工具也是通过分析这些信息将smali代码还原成我们喜闻乐见的Java代码的。当然,它不是必须的,去掉也没有关系,只不过为了方便调试还是保留一下吧。
      以上一些smali语法规则可以参详这里

Android Studio配置文件路径修改

注意:新版的Android Studio可能已经无法使用下面的方法进行配置文件路径的迁移,下文仅供参考。

使用Android Studio进行Android开发已经成为趋势了,好的工具要用得称手也少不了好的调教,在Windows下更是如此。这里对Android Studio的相关配置文件的路径修改做下小结。

首先看下图:

安装运行后自动生成的几个配置文件夹

Android Studio安装好以后会在系统盘用户目录下产生这么几个文件夹:

  • .android 这个文件夹是Android SDK生成的AVD(Android Virtual Device Manager)即模拟器存放路径
  • .AndroidStudio 这个文件夹是Android Studio的配置文件夹,主要存放一些AndroidStudio设置和插件和项目的缓存信息
  • .gradle 这个文件夹是构建工具 Gradle的配置文件夹,也会存储一些项目的构建缓存信息

本来是用默认路径也没什么问题,不过在Windows环境下,什么东西都往系统盘里塞老是让人觉得不舒服,在一个这年头要个用个固态硬盘装系统,估计就剩不下多少地方了,因此把这些玩意儿扔到别处也是必要的。

注意,在进行以下修改前,建议先把之前的配置目录拷贝到新的路径,这样之前的配置就不会丢了。

1. .AndroidStudio文件夹的修改

进入Android Studio的安装目录,进入bin文件夹,用文本编辑软件打开idea.properties,去掉以下两项的注释符号#,修改对应的路径为新路径即可。

修改以上两项路径

2. .gradle文件夹的修改

这项比较简单,在Android Studio的配置选项中修改就行

修改gradle路径

3. .android文件夹的修改

这个文件夹是由Android SDK配置模拟器生成的,也是最占空间的一个。
首先,需要添加一个系统的环境变量ANDROID_SDK_HOME,如下图:

添加以上环境变量

变量名其实有些误导人,这个如果Google官方定义成AVD_HOME可能还好一些,其实应该是模拟器的默认路径。添加好环境变量后到新的路径下修改下相应的.ini文件内的路径信息,然后重启系统生效。

ini文件

完毕。

参考阅读:

  1. Android Studio 的安装和配置篇(Windows篇)
  2. How to config android studio avd path?

作者:显卡84du
链接:https://www.jianshu.com/p/7a58c5f154c5
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

旅行青蛙破解分析从内存到存档再到改包

最近朋友圈里出现了一款日本的游戏,十分火爆,于是忍不住想去破解看看。分析后发现这个游戏的破解并不难,但是可以多种思路进行,是个很好的学习样本,于是决定写一篇文章分享给初学者们。

本文分三个方向进行破解分析,分别为 内存修改,存档修改,apk修改。文章涉及的修改较为简单,主要目的是给大家提供多元的分析思路,接下来我们一个一个来进行具体分析。

所使用样本为 旅行青蛙 1.0.4版本(目前最新版本)。 链接:https://pan.baidu.com/s/1dSqHK6 密码: qmvg

目录

0x1.内存修改 → GG修改器修改数值,需root

0x2.存档修改 → 存档十六进制修改,无需root;原创apk用于修改存档,无需root

0x3.apk修改   → Unity3D游戏脚本修改,无需root

0x4.总结         → 文章整体思路和方向概况

正文

0x1.内存修改

思路:这个方式是用在已经root的手机上,也就是我们接触比较多的修改器通过搜索来确认关键数值的内存地址,然后将其修改,达到破解目的。

工具:GG修改器 / 需要ROOT权限

因为比较简单,这部分尽量简要讲。

打开GG修改器和游戏,进游戏后查看当前三叶草数量,GG修改器附加游戏进程,并搜索该数量。

    

附加后我们进行搜索,搜索37这个数值。

    

搜索结果比较多,我们需要筛选,回到游戏使用三叶草买东西,数值变化为27,然后我们搜索27来确认三叶草数量的内存地址。

    

修改最终搜索到的值为27000,回到游戏就可以看到三叶草数量已经变化。

    

其他物品及抽奖券等数量均可用该方式修改,请大家自己尝试。

这种方式非常方便,但是有个弊端就是需要我们有ROOT权限,对于目前大部分安卓手机来讲,ROOT权限的获取越来越难,接下来我们来分析不需要ROOT权限的两种修改方法。

0x2.存档修改

思路:通过存档文件分析和修改完成关键数值修改

工具:十六进制编辑器

单机游戏都会有存档,旅行青蛙当然也不例外,我们按照常规路径去找一下,发现游戏的存档都在Tabikaeru.sav文件中,路径请看图:

我们使用十六进制编辑器将其打开,编辑器可以用PC端的也可以用手机端的,自行选择。

打开后我们根据目前的三叶草数量27000进行搜索,27000的十六进制为0x6978,所以我们在十六进制文件中可以进行hex搜索,搜索 69 78 或 78 69。

(通常在十六进制中的数值都是倒序记录,比如0x6978会保存为 78 69,在旅行青蛙1.0.1版本的存档中就是这么保存的,不过在1.0.4版本的存档中,已经变为了正序,即69 78)

经过搜索我们找到了三叶草的数量,接下来我们将其修改验证一下,将69 78 修改为 FF FF,保存后放回手机中存档的文件夹中,重新启动,发现三叶草数量已经变更:

其他数值修改,比如抽奖券或者其他物品数量等,均可依照此方法进行,此处不再赘述,请大家自己尝试。另外还可以在每次数值有较明显变化后保存存档文件,进行对比分析,来找到更多物品的数值。

为了更简便的进行修改,我们做一个专用修改器apk用来在未root手机上专门完成此修改过程,源码如下 (完整project参考附件) :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package com.example.frog;
import android.content.Context;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class MainActivity extends AppCompatActivity {
    private EditText editText;
    private EditText editText2;
    private Button button;
    private InputMethodManager inputMethodManager;
    private static final String FILE_PATH = Environment.getExternalStorageDirectory() + File.separator + "Android/data/jp.co.hit_point.tabikaeru/files/Tabikaeru.sav";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = (EditText) findViewById(R.id.editText);
        editText2 = (EditText) findViewById(R.id.editText2);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (editText.getText().toString().equals("") || editText2.getText().toString().equals("")) {
                    return;
                }
                String cloverHex = String.format("%06X",  Integer.valueOf(editText.getText().toString()));
                String couponHex = String.format("%06X",  Integer.valueOf(editText2.getText().toString()));
                Log.d("123"" " + cloverHex);
                Log.d("123"" " + couponHex);
                writeToFile(cloverHex, couponHex);
            }
        });
    }
    public void writeToFile(String cloverHex, String couponHex) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        File file = new File(FILE_PATH);
        File newFile = new File(FILE_PATH);
        byte[] cloverByteArray = hexStringToByte(cloverHex);
        byte[] couponByteArray = hexStringToByte(couponHex);
        if (!file.exists()) {
            Log.d("123""未找到文件Tabikaeru.sav");
            return;
        }
        try {
            fileInputStream = new FileInputStream(file);
            byte[] arrayOfByte = new byte[fileInputStream.available()];
            Log.d("123""文件大小" + arrayOfByte.length);
            fileInputStream.read(arrayOfByte);
            if (arrayOfByte.length > 29) {
                file.delete();
                Log.d("123""删除旧文件");
                createFile(newFile);
                //三叶草
                arrayOfByte[23] = cloverByteArray[0];//Byte.valueOf(cloverHex.substring(0, 2));
                arrayOfByte[24] = cloverByteArray[1];//Byte.valueOf(cloverHex.substring(2, 4));
                arrayOfByte[25] = cloverByteArray[2];//Byte.valueOf(cloverHex.substring(4, 6));
                //抽奖券
                arrayOfByte[27] = couponByteArray[0];//Byte.valueOf(couponHex.substring(0, 2));
                arrayOfByte[28] = couponByteArray[1];//Byte.valueOf(couponHex.substring(2, 4));
                arrayOfByte[29] = couponByteArray[2];//Byte.valueOf(couponHex.substring(4, 6));
                Log.d("123"" " + arrayOfByte.length);
                for (int i = 0; i <arrayOfByte.length; i++) {
                    Log.d("123"" " + arrayOfByte[i]);
                }
                fileOutputStream = new FileOutputStream(newFile);
                fileOutputStream.write(arrayOfByte);
            }
        catch (Exception e) {
            e.printStackTrace();
        finally {
            Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show();
            hideSoftInput();
            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    public void createFile(File file){
        try{
            file.getParentFile().mkdirs();
            file.createNewFile();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    public void hideSoftInput(){
        if(inputMethodManager == null) {
            inputMethodManager = (InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), 0);
        editText.clearFocus();
        inputMethodManager.hideSoftInputFromWindow(editText2.getWindowToken(), 0);
        editText2.clearFocus();
    }
    /**
     * 把16进制字符串转换成字节数组
     */
    public static byte[] hexStringToByte(String hex) {
        int len = (hex.length() / 2);
        byte[] result = new byte[len];
        char[] achar = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int pos = i * 2;
            result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
            if (result[i] == 0) {
                result[i] = 00;
            }
        }
        return result;
    }
    private static int toByte(char c) {
        byte b = (byte"0123456789ABCDEF".indexOf(c);
        return b;
    }
}

上述代码实现了存档的直接修改,界面如下,不需要ROOT权限:

输入数值后,点击修改即可完成三叶草及抽奖券的修改 ,更多物品修改请自行尝试 。

0x3.apk修改

思路:分析apk包,找到脚本文件,反编译后找到关键method进行修改,然后重新打包

工具:Android Killer,DnSpy

Android Killer相关操作这里不再赘述,反编译后我们发现这是一个mono框架的Unity3D游戏,Unity3D游戏的脚本文件都存放在Assembly-CSharp.dll或Assembly-CSharp-firstpass.dll文件中,很显然,旅行青蛙的脚本文件位于Assembly-CSharp.dll,我们使用Dnspy进行分析看看。

我们搜索三叶草的英文clover,发现getCloverPoint可能是我们需要找的关键method。

根据getCloverPoint的源码,我们发现这个method的功能是在三叶草数量发生变化时在三叶草数量进行增减运算,那么我们可以对函数内部增加数量的这句代码进行修改,修改为发生变化增加固定数量的三叶草,比如10000。

(抽奖券相关修改也在SuperGameMaster中可以找到,method名为getTicket,此处不作演示,请大家自己尝试修改)

修改后函数变更为:

保存后打包apk运行,只要三叶草数量发生变化(如收割三叶草或者购买物品),三叶草的数量就会增加10000。

0x4.总结

本文通过多种思路对旅行青蛙的修改进行了分析,内容较为简单,主要目的是分享游戏破解分析的思路,有兴趣的可以尝试更多物品数量的修改。

Android Studio插件整理

现在Android的开发者基本上都使用Android Studio进行开发(如果你还在使用eclipse那也行,毕竟你乐意怎么样都行)。使用好Android Studio插件能大量的减少我们的工作量。

1.GsonFormat

快速将json字符串转换成一个Java Bean,免去我们根据json字符串手写对应Java Bean的过程。

使用方法:快捷键Alt+S也可以使用Alt+Insert选择GsonFormat

2.Android ButterKnife Zelezny

配合ButterKnife实现注解,从此不用写findViewById,想着就爽啊。在Activity,Fragment,Adapter中选中布局xml的资源id自动生成butterknife注解。

使用方法:Ctrl+Shift+B选择图上所示选项

3.Android Code Generator

根据布局文件快速生成对应的Activity,Fragment,Adapter,Menu。


4.Android Parcelable code generator

JavaBean序列化,快速实现Parcelable接口。

5.Android Methods Count

显示依赖库中得方法数

6.Lifecycle Sorter

可以根据Activity或者fragment的生命周期对其生命周期方法位置进行先后排序,快捷键Ctrl + alt + K


7.CodeGlance

在右边可以预览代码,实现快速定位


8.findBugs-IDEA

查找bug的插件,Android Studio也提供了代码审查的功能(Analyze-Inspect Code…)

9.ADB WIFI

使用wifi无线调试你的app,无需root权限
也可参考以下文章:
Android wifi无线调试App新玩法ADB WIFI

10.AndroidPixelDimenGenerator

Android Studio自动生成dimen.xml文件插件

11.JsonOnlineViewer

在Android Studio中请求、调试接口

12.Android Styler

根据xml自动生成style代码的插件



Usage:

a. copy lines with future style from your layout.xml file
b. paste it to styles.xml file with Ctrl+Shift+D (or context menu)
c. enter name of new style in the modal window
d. your style is prepared!

13.Android Drawable Importer

这是一个非常强大的图片导入插件。它导入Android图标与Material图标的Drawable ,批量导入Drawable ,多源导入Drawable(即导入某张图片各种dpi对应的图片)








14.SelectorChapek for Android

通过资源文件命名自动生成Selector文件。



15.GenerateSerialVersionUID

实现Serializable序列化bean

Adds a new action ‘SerialVersionUID’ in the generate menu (alt + ins). The action adds an serialVersionUID field in the current class or updates it if it already exists, and assigns it the same value the standard ‘serialver’ JDK tool would return. The action is only visible when IDEA is not rebuilding its indexes, the class is serializable and either no serialVersionUID field exists or its value is different from the one the ‘serialver’ tool would return.

16.genymotion

速度较快的android模拟器

17.SQLScout

在 Android Studio 上调试数据库 ( SQLite )

详细使用参考:在 Android Studio 上调试数据库 ( SQLite )

18.Android Postfix Completion

可根据后缀快速完成代码,这个属于拓展吧,系统已经有这些功能,如sout、notnull等,这个插件在原有的基础上增添了一些新的功能,我更想做的是通过原作者的代码自己定制功能,那就更爽了

19.Android Holo Colors Generator

通过自定义Holo主题颜色生成对应的Drawable和布局文件

20.dagger-intellij-plugin

dagger可视化辅助工具

21.GradleDependenciesHelperPlugin

maven gradle 依赖支持自动补全

22.RemoveButterKnife

ButterKnife这个第三方库每次更新之后,绑定view的注解都会改变,从bind,到inject,再到bindview,搞得很多人都不敢升级,一旦升级,就会有巨量的代码需要手动修改,非常痛苦
当我们有一些非常棒的代码需要拿到其他项目使用,但是我们发现,那个项目对第三方库的使用是有限制的,我们不能使用butterknife,这时候,我们又得从注解改回findviewbyid
针对上面的两种情况,如果view比较少还好说,如果有几十个view,那么我们一个个的手动删除注解,写findviewbyid语句,简直是一场噩梦(别问我为什么知道这是噩梦)
所以,这种有规律又重复简单的工作为什么不能用一个插件来实现呢?于是RemoveButterKnife的想法就出现了。

具体介绍

23.AndroidProguardPlugin

一键生成项目混淆代码插件,值得你安装~(不过目前可能有些第三方项目的混淆还未添加完全)

24.otto-intellij-plugin

otto事件导航工具。


25.eventbus-intellij-plugin

eventbus导航插件(对于最新版的 EventBus 3.0.0 好像无效,请替换为eventbus3-intellij-plugin此插件地址在本文第51个)

26.idea-markdown

markdown插件

27.Sexy Editor

设置AS代码编辑区的背景图

首先点击界面的设置按钮 进入设置界面,选中Plugins,右边选择 Browser … ,输入Sexy … 下面自动弹出候选插件,右边点击Install 安装

安装成功 后需要重启AS

重启完成之后 进入设置界面 选择other Setting 下的Sexy Editor , 右侧 insert 一张或多张图片即可,上面的其他设置可以设置方位 间隔时间 透明度等等,设置完成后,要关闭打开的文件,重新打开项目文件即可在代码编辑区显示插入的图片,作为代码编辑区的背景图。

28.folding-plugin

布局文件分组的插件

29.Android-DPI-Calculator

DPI计算插件

使用:

或者

30.gradle-retrolambda

在java 6 7中使用 lambda表达式插件

修改编译的jdk为java8:

31.Android Studio Prettify

可以将代码中的字符串写在string.xml文件中

选中字符串鼠标右键选择图中所示

这个插件还可以自动书写findViewById

32.Material Theme UI

添加Material主题到你的AS

33..ignore

我们都知道在Git 中想要过滤掉一些不想提交的文件,可以把相应的文件添加到.gitignore 中,而.gitignore 这个Android Studio 插件根据不同的语言来选择模板,就不用自己在费事添加一些文件了,而且还有自动补全功能,过滤文件再也不要复制文件名了。我们做项目的时候,并不是所有文件都是要提交的,比如构建的build 文件夹,本地配置文件,每个Module 生成的iml 文件,但是我们每次add,commit 都会不小心把它们添加上去,而gitignore 就是解决这种痛点的,如果你不想提交的文件,就可以在创建项目的时候将这个文件中添加即可,将一些通用的东西屏蔽掉。

34.CheckStyle-IDEA

CheckStyle-IDEA 是一个检查代码风格的插件,比如像命名约定,Javadoc,类设计等方面进行代码规范和风格的检查,你们可以遵从像Google Oracle 的Java 代码指南 ,当然也可以按照自己的规则来设置配置文件,从而有效约束你自己更好地遵循代码编写规范。

35.Markdown Navigator

github:Markdown Navigator
Markdown插件

36.ECTranslation

Android Studio Plugin,Translate English to Chinese. Android Studio 翻译插件,可以将英文翻译为中文。

37.PermissionsDispatcher plugin

github:PermissionsDispatcher plugin
自动生成6.0权限的代码

38.WakaTime

github:WakaTime
记录你在IDE上的工作时间

39.AndroidWiFiADB

无线调试应用

40.AndroidLocalizationer

可用于将项目中的 string 资源自动翻译为其他语言的 Android Studio/IntelliJ IDEA 插件

41.TranslationPlugin

又一翻译插件,可中英互译。

42.SingletonTest

快速生成单例模式的预设

43.BorePlugin

Android Studio 自动生成布局代码插件

代码生成规则

a.自动遍历目标布局中所有带id的文件, 无id的不会识别处理
b.控件生成的变量名默认为id名称, 可以在弹出确认框右侧的名称输入栏中自行修改
c.所有的Button或者带clickable=true的控件, 都会自动在代码中生成setOnClickListener相关代码
d.所有EditText控件, 都会在代码中生成非空判断代码, 如果为空会提示EditText的hint内容, 如果hint为空则提示xxx字符串不能为空字样, 最后会把所有输入框的验证合并到一个submit方法中
e.会自动识别布局中的include标签, 并读取对应布局中的控件

44.jimu Mirror

能够实时预览Android布局,它会监听布局文件的改动,如果有代码变化,就会立即刷新UI。

45.jRebel For Android

不仅能够做到UI布局的实时预览,它甚至做到了让你更改java代码后就能实时替换apk中的类文件,达到应用实时刷新,官网的介绍是:Skip build, install and run,因此它可以节约我们很多很多的时间,它的效果也十分不错。

46.sdk-manager-plugin

SDK管理插件,自动检测更新并下载。(图片与插件无关哈)

47.Codota

搜索最好的Android代码。(Studio里面直接可以搜到此插件)

48.LayoutFormatter

drakeet 开发一个一键格式化你的 XML 文件的 Android Studio 插件,至于为什么不用 Android Studio 自带的格式化功能而用这个插件,可以看下作者的一篇 Blog -> 当我们谈 XML 布局文件代码的优雅性

49.android-strings-search-plugin

一个可以通过输入文字找到strings.xml资源的插件

50.ideaVim

vim 本身就是一款很优秀的文本编辑器,而Android Studio 更是一款编写APP应用的神器。如果两个款优秀的软件结合在一起感觉会怎样呢?
详细请看文章:Android Studio +Vim

51.eventbus3-intellij-plugin

引导 EventBus 的 post 和 event(对于最新版的 EventBus 3.0.0 有效)
主要Bug修复工作:
修改包名和方法名以适应 EventBus 3.X
替换一个在新版的 intellij plugin SDK 已经不存在的类
增加若干 try-catch ,防止插件崩溃

52.Exynap

Exynap 一个帮助开发者自动生成样板代码的 AndroidStudio 插件

53.gradle-cleaner-intellij-plugin

Force clear delaying & no longer needed Gradle tasks.

54.MVPHelper

一款Intellj IDEA 和Android Studio的插件,可以为MVP生成接口以及实现类,解放双手。
具体请查看Android Studio插件之MVPHelper,一键生成MVP代码一文

55.Matchmaker

这是一款专为微信小程序开发的插件,目前可在 IntelliJ IDEA 中使用。它可以帮你完成重复机械无趣麻烦的绑定方法的过程,自动的将需要新建的方法注入到 js 文件中去。

56.Emoji Support Plugin

让 Intellij 支持 Emoji 输入提醒

57.Open-Uploader

上传apk文件到指定的地址,提供自定义参数

58.MultiTypeTemplates

生成MultiType和itemviewprovider(关于MultiType请查看Android 复杂的列表视图新写法 MultiType)

59.Android-ButterKnife-Plugin-Plus

Android Studio 的插件,方便快速实现ButterKnife注解框架,包含了android-butterknife-zelezny 1.6版本的所有功能,并在此基础上新增如下功能:

1.可以自由选择是否在当前类中对ButterKnife进行初始化,避免了原版本只要使用插件初始化控件会自动在onCreate中进行ButterKnife.bind(this)的尴尬。

这样就可以在基类中进行ButterKnife的初始化,不必要每个类中都要初始化,对开发框架的搭建更加方便。

2.在Android Studio的设置界面,对在当前类中是否强制初始化提供了默认值设置,这样就可以让插件使用更符合自己的操作习惯。

60. ApkMultiChannelPlugin

这是一个为了方便 Android 多渠道打包的 Android Studio / IDEA 插件

安装:

  • 打开 Android Studio: 打开 Setting/Preferences -> Plugins -> Browse repositories 然后搜索 ApkMultiChannel 安装重启

或者

  • 下载 ApkMultiChannelPlugin.jar 然后 Setting/Preferences -> Plugins -> Install plugin from disk 选择 ApkMultiChannelPlugin.jar 安装重启

使用方式:

  1. 选择 apk

    选择一个 apk 然后右键,点击 Build MultiChannel

  2. 配置

    配置签名信息,打包方式和渠道等

    配置说明:

    Key Store Path: 签名文件的路径

    Key Store Password: 签名文件的密码

    Key Alias: 密钥别名

    Key Password: 密钥密码

    Zipalign Path: zipalign文件的路径(用于优化 apk;zipalign 可以确保所有未压缩的数据均是以相对于文件开始部分的特定字节对齐开始,这样可减少应用消耗的 RAM 量。)

    Signer Version: 选择签名版本:apksigner 和 jarsigner

    Build Type: 打包方式

    Channels: 渠道列表,每行一个,最前面可加 > 或不加(保存信息的时候,程序会自行加上)

  3. 开始打包

    配置完成之后按 OK 就会开始进行渠道打包,文件会输出在选中的apk的当前目录下的channels目录中

61.CodeMaker

一个 IDEA 的代码生成插件,通过 Velocity 支持自定义代码模板来生成代码。详细介绍IDEA代码生成插件CodeMaker

62.adb-idea

可以一键清理缓存并重启APP

此插件来自[email protected]的分享,感谢[email protected]的分享

63.JVM Debugger Memory View

Android Studio和IDEA中一个很有用的内存调试插件

详细可参考说一说Android Studio和IDEA中一个很有用的内存调试插件一文。

64.TinyPic

功能:压缩图片资源,一次最多压缩500张 压缩的核心功能是TinyPng这个网站提供的

https://tinypng.com/

但是这个网站一次只能上传20张图片,所以你需要上传下载,上传下载重复工作。 好在这个网站提供了api可以压缩图片。

在开发者页面下申请api key。对于一个key,每月有500次的免费压缩额度,如果压缩超过了 500张图片,就不能使用了。需要另外付费。但是申请这个api特别简单,填下邮箱,用户名就行,多申请 两个邮箱。1000张图片也妥妥够了。 这里推荐google个十分钟邮箱,不需要注册,只能使用十分钟,用来收一下验证码很方便。

使用方式:
1.在File->Settings->Plugins里下载插件 TinyPic

2.安装完后重启,在Tools目录下找到TinyPic

3.输入在 https://tinypng.com/developers 申请的api key

4.选择图片,可以选择图片,或者选择文件夹或者同时选中,反正是遍历文件夹下的图片,筛选jpg和png ,key的剩余次数

5.压缩进度

6.超过500次的提示(后续会考虑加入 生成压缩的信息的文件,因为大家都用git,其实也不是很必要)

65.ReciteWords

这是一个androidStudio翻译与陌生单词记录插件

你所翻译的单词会被记录在你当前用户目录下的ReciteWords.md文件中(如:C:\Users\Bolex\ReciteWords.md)。可以通过Markdown编辑器打开它进行学习。效果如下:

66.TemplateBuilder

TemplateBuilder是一款能够帮助我们快速生成Android Studio Template的AS插件,将通过逐个文件去配置模板的方式改进为通过插件来实现,对于简单的模板制作,只需要一键即可生成。

具体使用请参考TemplateBuilder(中文版)](TemplateBuilder

67.intellij-java2smali

将Java & Kotlin编译成smali

68.innerbuilder

InnerBuilder 一款Intellj IDEA 和Android Studio自动生成内部类Builder代码的插件。

69.Statistic

统计代码行数

使用可参考:Android studio插件Statistic的使用

70.create-intent-inspection

创建intent

71.color-manager

颜色管理

72.new-instance-inspection

创建fragment实例

73.Exynap

exynap是一个可以帮助你查找并实现您需要的代码的插件

74.databinding-support

一个可以快速实现databinding的插件

75.pomodoro-tm

番茄工作法的 Android Studio / IDEA 插件

76.freeline

Freeline 是 Android 平台上的秒级编译方案,Instant Run 的替代品

77.svgtoandroid

Intellij Platform插件,通过其可以完成从svg文件到Android VectorDrawable的自动化转换

78.instapk-studio-plugin

分享apk文件

79.here-be-dragons

加上@SideEffect注解的方法,在调用的地方会出现一只鸟

80.android-studio-proteus-plugin

将xml转化为json

*最后:推荐梯子:(Github上的star数13000+)

XX-Net
具体使用请参考里面文档已经写得很清楚了,按照文档一步步操作即可。关键是免费的!免费的!免费的!速度也快!

本文也可以访问简书内容是一样的

本文会持续更新(如果发现有好玩,好用的插件,欢迎通过Email:[email protected]告诉我),请持续关注。哈哈!

说一说Android Studio和IDEA中一个很有用的内存调试插件

JetBrains JVM Debugger Memory View plugin

在我最近的研发活动期间寻找新的工具,以提高我的开发经验,使Android Studio的生活更轻松,我发现一个有用的插件,我从来没有听说过。 这就是为什么,我决定写这个强大的工具,它如何帮助我与内存调试我的应用程序。

What is the plugin about?

根据plugin page:

此插件扩展了内置的JVM调试器,具有在调试会话期间观察JVM堆中的对象的功能。

内存视图按照类名称分组来显示堆中的对象总数

当你一步步调试代码时,“Diff”列显示调试器停靠点(debugger stops也就是debug点)之间对象数量的变化。 这种方式你可以很容易地看到你的步进代码如何影响堆。

双击类名称,打开一个包含该类实例的对话框。 该对话框允许您通过计算表达式过滤实例。 所有调试器操作(如检查,标记对象,评估表达式,添加到观察等)都可以应用于此对话框中的实例。

How to install this wonderful plugin?

打开Android Studio Plugins页面:

  • 快捷键:command/ ctrl shift A,类型 插件 随后,按enter键:
  • 或打开 Preferences/Settings:(Mac:Android Studio – >Preferences / Windows和Linux:File – >Settings)并找到Plugins页面:

Install jetBrains plugin… 按钮,搜索 JVM Debugger Memory View 然后 Install 

装完重新启动Android Studio。

At first glance:

回到Android Studio后,您会发现Memory View Tool Window已经添加到工具栏的右侧。

Memory View Tool Window

内存视图工具窗口

如果没有看到内存视图,打开工具窗口,使用主菜单:ViewTool Windows Memory View

首先,这个工具只有在打了调试断点并在debug模式运行期间才会显示数据。

其次,我要提到的是,我阅读了Android Studio可能会发生的一些警告和错误,不过,我并没有碰到过。

警告:Android Studio版本包含以下限制:

  • 由于Android内存限制,获取大量的实例可能会失败,并会停止VM。
  • Android Studio可能会停止响应,请参阅此错误

Let’s debug!

在调试模式下运行应用程序并在BreakPoint上暂停后,您会看到很神奇的画面:

这个表让我们最感兴趣的地方是Diff ”列,当你一步步调试代码行时,你将看到会有多少新的对象实例被创建或销毁!

我想寻找我自己的对象(即ProfileModel类),所以我搜索它:

正如你可以看到我已经在这行代码更新了ProfileModel vairable,在GC删除旧对象之前我得到差异是+1 ,也可以访问之前不可能访问到的旧的对象。 通过双击这条记录,我将在窗口中获取ProfileModel类的实例:

此窗口还允许你使用类方法通过计算的表达式过滤实例,例如,您可以使用 OkHttp Response 类的 isSuccessful 方法来过滤筛选在内存中加载不成功的响应:

实例过滤器功能

另一个有用的功能是跟踪新实例,您可以通过Memory View Tool窗口中的右键菜单启用:

此功能可帮助您跟踪已生成类的新实例的代码!

你可以在JetBrains blog中阅读有关此插件的更多信息.

TL;DR:

这篇文章是关于一个JetBrains插件,可能会帮助你在使用Android Studio是对应用程序进行内存调试和对在应用程序运行的所有加载对象进行访问。

本文翻译自:hackernoon.com/a-useful

注:IDEA版本请选择2016.3以上

  • 原文链接:一叶知秋
  • 作者:知秋
  • [ 转载请保留原文出处、作者。]

IDEA中一个很有用的内存调试插件

JetBrains JVM Debugger Memory View plugin

在我最近的研发活动期间寻找新的工具,以提高我的开发经验,使Android Studio的生活更轻松,我发现一个有用的插件,我从来没有听说过。 这就是为什么,我决定写这个强大的工具,它如何帮助我与内存调试我的应用程序。

What is the plugin about?

根据 plugin page :

此插件扩展了内置的JVM调试器,具有在调试会话期间观察JVM堆中的对象的功能。

内存视图按照类名称分组来显示 堆中的对象总数 。

当你一步步调试代码时, “Diff”列显示调试器停靠点(debugger stops也就是debug点)之间对象数量的变化 。 这种方式你可以很容易地看到你的步进代码如何影响堆。

双击类名称,打开一个包含该类实例的对话框。 该对话框允许您 通过计算表达式过滤实例 。 所有调试器操作(如检查,标记对象,评估表达式,添加到观察等)都可以应用于此对话框中的实例。

How to install this wonderful plugin?

打开Android Studio Plugins 页面:

  • 快捷键: 按 command/ ctrl + shift + A, 类型 插件 随后,按 enter 键:
  • 或打开 Preferences/Settings: (Mac:Android Studio – >Preferences / Windows和Linux:File – >Settings)并找到 Plugins 页面:

按 Install jetBrains plugin… 按钮,搜索 JVM Debugger Memory View 然后 Install 。

装完重新启动Android Studio。

At first glance:

回到Android Studio后,您会发现 Memory View Tool Window 已经添加到工具栏的右侧。

Memory View Tool Window

内存视图工具窗口

如果没有看到内存视图,打开工具窗口,使用主菜单: View → Tool Windows → Memory View。

首先,这个工具只有在打了调试断点并在 debug模式 运行期间才会显示数据。

其次,我要提到的是,我阅读了Android Studio可能会发生的一些警告和错误,不过,我并没有碰到过。

警告:Android Studio版本包含以下限制:

  • 由于Android内存限制,获取大量的实例可能会失败,并会停止VM。
  • Android Studio可能会停止响应,请参阅 此错误

Let’s debug!

在调试模式下运行应用程序并在BreakPoint上暂停后,您会看到很神奇的画面:

这个表让我们最感兴趣的地方是 Diff ”列,当你一步步调试代码行时,你将看到会有多少新的对象实例被创建或销毁!

我想寻找我自己的对象 (即ProfileModel类) ,所以我搜索它:

正如你可以看到我已经在这行代码更新了ProfileModel vairable,在GC删除旧对象之前我得到差异是+1 ,也可以访问之前不可能访问到的旧的对象。 通过双击这条记录,我将在窗口中获取ProfileModel类的实例:

此窗口还允许你使用类方法通过计算的表达式过滤实例,例如,您可以使用 OkHttp Response 类的 isSuccessful 方法来过滤筛选在内存中加载不成功的响应:

实例过滤器功能

另一个有用的功能是跟踪新实例,您可以通过Memory View Tool窗口中的右键菜单启用:

此功能可帮助您跟踪已生成类的新实例的代码!

Android Studio 掌握这些调试技巧,Debug 能力不能再高啦

Android Studio 掌握这些调试技巧,Debug 能力不能再高啦

Debug断点跟踪调试是软件开发过程中分析代码、解决BUG的一个重要手段,不同IDE下的Debug工具的使用有所不同,但提供的调试功能一定是应有尽有。很多程序员的Debug能力都停留在基本的单步执行、断点跳跃上,殊不知还有很多鲜为人知但非常方便的调试技巧。本文就以Android Studio工具为例,展示一些一般人不知道的Debug调试技巧,掌握这些,你也算是Debug调试大师了。

基本使用


Debug App有两种途径,第一种是直接点击下图运行按钮右侧的小虫状图标,运行并调试当前Project,这个我想大家都知道。

Debug App.png

第二种就是调试当前已经处于运行状态下的App,这也是我们用的更多的一种调试手段,即Attach debugger to Android process。点击运行按钮右侧第三个按钮,弹出Choose Process窗口,选择对应的进程,点击OK按钮即可进入调试模式,此时,我们便可以在需要的地方直接下断点调试代码了:

Attach debugger to Android process.png

接下来就是常见的调试方法了,在Debug窗口顶部工具栏有一排操作按钮,比如Step Over(单步执行)、Step Into(进入方法)等,如图所示:

Debug窗口.png

打断点和取消断点最直接的方式就是单击目标代码行的行号右侧空白处,然后在Debug窗口左侧有个断点浏览按钮View Breakpoints,位于停止按钮下方第一个,可以浏览Project中的所有断点,同时可以添加删除断点:

View Breakpoints.png

条件断点


有时候,我们的断点打在了循环体里面,但是我们只想看某一特定循环次数下的运行情况,难道要使用Run to Cursor功能不停地跳至下一次断点直至满足我们的要求吗?

循环里的断点.png

如果你知道条件断点的话,一定会悔不当初。条件断点可以满足开发人员自己输入条件,比如fori循环中输入i == 5即可让程序直接运行至第六次循环,for each循环中针对list某一元素下的断点调试。只需要右键点击断点,在弹出的窗口中输入Condiction条件,点击Done按钮,然后当程序执行到循环体时,会在满足条件的一次循环中停下来,供我们调试:

条件断点.png

日志断点


打印日志也是跟踪程序分析问题的一个非常有效的手段,但是如果我们的程序已经运行并且处于调试模式,此时如果想打印日志更加直观的分析代码,难道还要停止调试、添加Log代码并重新编译运行吗?

如果你知道日志断点,就不用如此大费周折,费时费力了。还是右键点击断点,在弹出的窗口中取消勾选Suspeng复选框(即表示程序运行至此断点时不会停下来供开发者调试),然后勾选Log evaluated expression:,并输入打印语句即可。这样,当Debug模式下的程序执行至此,不会停下来,而是在控制台中打印对应信息,如:

日志断点.png

变量赋值


比如,我们的代码里有一个变量,这个变量的值会影响到程序的执行结果。如果我们想观察这个变量在不同的赋值下程序的执行结果怎么办呢?难道要一遍遍的在代码里修改变量值,然后重新运行程序吗?显然这是非常麻烦的操作。其实,如果利用Debug模式下的变量赋值(Set Value),只需要运行一次,就能达到我们的观察效果。在使用该变量的代码处打个断点,然后在Variables窗口找到对应的变量,修改变量值并执行即可。

Set Value.png

变量观察


Variables变量区和Watches观察区可以查看Debug模式下,程序执行到断点处的变量值或者对象的各属性值,但是多多少少查看起来还是有些不方便。其实可以通过弹出窗口的形式查看属性值,只要将光标定位至断点代码行所用到的变量,IDE会自动弹出一个小窗口,如下图所示,此时,使用对应的快捷键或者点击这个小窗口里的变量即可弹出变量属性值窗口,Mac下的快捷键位command + F1,如图所示:

变量观察01.png

变量观察02.png

对象求值


在断点处,如果有变量对象,系统提供了表达式求值功能,针对Variables视图中的变量对象,我们可以输入任何计算语句,实时查看表达式计算结果。具体操作为,右键Variables视图中的变量对象,选择Evaluate Expression,弹出表达式窗口,输入任何你想要的计算语句,点击Evaluate计算按钮,即可显示Result结果:

Evaluate Expression01.png

Evaluate Expression02.png

方法断点


通常我们会对方法里的代码添加断点调试,很少对方法本身调试。其实,如果只是为了看到方法的参数和返回结果,我们可以在定义方法的第一行打断点,直接对方法本身调试,此时断点的展示图标样式也会与众不同:

方法断点.png

变量断点


有时候,我们想知道自定义的变量的何时何地发生了改变,就可以使用变量断点。变量断点的图标样式也与众不同,在变量定义行打断点,开启Debug模式,在程序执行的过程中,如果该变量的值发生改变,程序会自动停下来,并定位在改变变量值的地方,供开发者调试:

变量断点.png

异常断点


程序在执行的过程中可能会出现各种各样的未知性异常,如果能在发生异常的时候第一时间让程序停下来,并定位到异常出现的地方,供开发者调试,那当然是极好的。而万能的Android Studio就提供了这样的功能。

打开断点管理器,这里有两种方式打开:点击工具栏菜单Run,选择View Breakpoints;在Debug窗口直接点击View Breakpoints图标。点击左上角加号按钮,可以添加各种断点,包括前文提到的Method BreakpointsField Watchpoints断点,这里我们选择Exception Breakpoints异常断点,在弹出的Enter Exception Class窗口中输入需要监控的异常类别即可:

Breakpoints.png

Exception Breakpoints.png

欢迎补充


以上便是使用Android Studio工具的开发过程中很是实用但却少见的Debug调试技巧,当然所有这些操作都可以通过快捷键打开,将鼠标光标移到对应图标处,都会显示对应快捷键组合,大家自行酌情使用。

当然,如果你还有更好的调试技巧,欢迎留言补充,让我们一起在分享中学习,交流中进步。

启用 https 简单免费的 Let’s Encrypt SSL证书配置

Let’s Encrypt简介

Let's Encrypt 是一个免费、开放,自动化的证书颁发机构。

如果我们要启用 HTTPS,就需要从证书授权机构(以下简称 CA ) 处获取一个证书,Let's Encrypt 就是一个 CA。本文是 Debian 8 + Nginx 下的配置过程。

Let’s Encrypt 安装

Let’s Encrypt 官方 推荐我们使用 certbot 客户端

注:letsencrypt 或者 letsencrypt-auto 这种方式从2016年5月开始已经过时了。

certbot 客户端要求安装 Python 要有 root 权限

clipboard.png

如上图,我们在 certbot 的官网选择自己使用的服务器和操作系统。网站就会给出对应的安装文档。

安装客户端

sudo apt-get install certbot -t jessie-backports

获取证书

证书的获取有两种方式

第一种,web 服务器已经在运行了,不能关闭端口和服务器,可以使用 --webroot 模式

sudo certbot certonly --webroot -w /var/www/example -d example.com  --agree-tos --email 你的@邮箱.com

这个命令会为 example.com 域名生成一个证书,使用 –webroot 模式会在 /var/www/example 中创建 .well-known 文件夹,这个文件夹里面包含了一些验证文件,letsencrypt 会通过访问 example.com/.well-known/acme-challenge 来验证你的域名是否绑定的这个服务器。--agree-tos 参数是你同意他们的协议。

第二种,如果我们的项目没有根目录,只是一个微服务,可以使用 --standalone 模式。
这种模式会自动启用服务器的 443 端口,来验证域名的归属。我们有其他服务(例如nginx)占用了443端口,就必须先停止这些服务,在证书生成完毕后,再启用。

sudo certbot certonly --standalone -d example.com --agree-tos --email 你的@邮箱.com

证书生成目录在 /etc/letsencrypt/live/,该目录下可以看到对应域名的文件夹,里面存放了指向证书的一些快捷方式。

证书申请成功后会提示证书的文件路径,以及证书到期时间:

IMPORTANT NOTES:

- Congratulations! Your certificate and chain have been saved at

/etc/letsencrypt/live/example.com/fullchain.pem. Your cert will

expire on 2018-02-08. To obtain a new version of the certificate in

the future, simply run Let's Encrypt again.

- If you like Let's Encrypt, please consider supporting our work by:

Donating to ISRG / Let's Encrypt:  https://letsencrypt.org/donate

Donating to EFF:                    https://eff.org/donate-le

Nginx 配置

server {
        server_name example.com www.example.com;
        listen 443;
        ssl on;
        ssl_certificate /etc/letsencrypt/live/diamondfsd.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/diamondfsd.com/privkey.pem;

        location / {
           proxy_pass http://localhost:4000;
        }
    }

重启 Nginx 服务就可以使用 https 了

证书自动更新

Let's Encrypt 提供的证书只有 90 天的有效期,我们必须在证书到期之前,重新获取这些证书。

我们可以在服务器添加一个 crontab 定时任务来处理

crontab -e

注:如果要执行的脚本需要 root 权限, 那就用 root 用户创建 crontab

添加下面的内容

10 1 * */2 * certbot renew --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"

这个 cron 计划,意思是 每隔两个月的凌晨 1:10 执行更新操作。

--pre-hook 这个参数表示执行更新操作之前要做的事情,因为我用的 --standalone 模式的证书,所以需要停止 nginx服务,解除端口占用。

--post-hook 这个参数表示执行更新操作完成后要做的事情,这里就恢复 nginx 服务的启用。

验证 https

使用 ssllabs 在线测试服务器证书强度及配置正确性

参考扩展

Let’s Encrypt 使用教程,免费的SSL证书,让你的网站拥抱 HTTPS