class Danger::DangerApkstats

Show stats of your apk file. By default, it's done using apkanalyzer in android sdk.

All command need your apk filepath like below

apkstats.apk_filepath=<your new apk filepath>

@example Compare two apk files and print it.

apkstats.compare_with(<your old apk filepath>, do_report: true) # report it in markdown table
apkstats.compare_with(<your old apk filepath>, do_report: false) # just return results

@example Show the file size of your apk file.

apkstats.file_size

@example Show the download size of your apk file.

apkstats.download_size

@example Show all required features of your apk file.

apkstats.required_features

@example Show all non-required features of your apk file.

apkstats.non_required_features

@example Show all requested permissions of your apk file.

apkstats.permissions

@example Show the min sdk version of your apk file.

apkstats.min_sdk

@example Show the target sdk version of your apk file.

apkstats.target_sdk

@example Show the methods reference count of your apk file.

apkstats.method_reference_count

@example Show the number of dex of your apk file.

apkstats.dex_count

@see Jumpei Matsuda/danger-apkstats @tags android, apk_stats

Constants

COMMAND_TYPE_MAP

@deprecated this field have no effect

Attributes

apk_filepath[RW]

Required Your target apk filepath.

@return [String]

apkanalyzer_path[RW]

Required A path of apkanalyzer command

@return [String] _

command_path[RW]

Required A path of apkanalyzer command

@return [String] _

command_path=[RW]

Required A path of apkanalyzer command

@return [String] _

command_type[RW]

@deprecated this field have no effect This will be removed in further versions

@return [String] _

Public Instance Methods

compare_with(other_apk_filepath, do_report: true) click to toggle source

Get stats of two apk files and calculate diffs between them.

@param [String] other_apk_filepath your old apk @param [Boolean] do_report report markdown table if true, otherwise just return results @return [Hash] see command/executable#compare_with for more detail

# File lib/apkstats/plugin.rb, line 109
def compare_with(other_apk_filepath, do_report: true)
  raise "apk filepaths must be specified" if apk_filepath.nil? || apk_filepath.empty?

  base_apk = Apkstats::Entity::ApkInfo.new(apkanalyzer_command, apk_filepath)
  other_apk = Apkstats::Entity::ApkInfo.new(apkanalyzer_command, other_apk_filepath)

  result = {
      base: base_apk.to_h,
      other: base_apk.to_h,
      diff: Apkstats::Entity::ApkInfoDiff.new(base_apk, other_apk).to_h,
  }

  return result unless do_report

  diff = result[:diff]

  md = +"### Apk comparison results" << "\n\n"
  md << "Property | Summary" << "\n"
  md << ":--- | :---" << "\n"

  diff[:min_sdk].tap do |min_sdk|
    break if min_sdk.size == 1

    md << "Min SDK Change | Before #{min_sdk[1]} / After #{min_sdk[0]}" << "\n"
  end

  diff[:target_sdk].tap do |target_sdk|
    break if target_sdk.size == 1

    md << "Target SDK Change | Before #{target_sdk[1]} / After #{target_sdk[0]}" << "\n"
  end

  result[:base][:file_size].tap do |file_size|
    size = Apkstats::Helper::Bytes.from_b(file_size)

    md << "New File Size | #{size.to_b} Bytes. (#{size.to_mb} MB) " << "\n"
  end

  diff[:file_size].tap do |file_size|
    size = Apkstats::Helper::Bytes.from_b(file_size)

    md << "File Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
  end

  diff[:download_size].tap do |download_size|
    size = Apkstats::Helper::Bytes.from_b(download_size)

    md << "Download Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
  end

  result[:base][:method_reference_count].tap do |method_reference_count|
    md << "New Method Reference Count | #{method_reference_count}" << "\n"
  end

  diff[:method_reference_count].tap do |method_reference_count|
    md << "Method Reference Count Change | #{method_reference_count}" << "\n"
  end

  result[:base][:dex_count].tap do |dex_count|
    md << "New Number of dex file(s) | #{dex_count}" << "\n"
  end

  diff[:dex_count].tap do |dex_count|
    md << "Number of dex file(s) Change | #{dex_count}" << "\n"
  end

  report_hash_and_arrays = lambda { |key, name|
    list_up_entities = lambda { |type_key, label|
      diff[key][type_key].tap do |features|
        break if features.empty?

        md << "#{label} | " << features.map { |f| "- #{f}" }.join("<br>").to_s << "\n"
      end
    }

    list_up_entities.call(:new, "New #{name}")
    list_up_entities.call(:removed, "Removed #{name}")
  }

  report_hash_and_arrays.call(:required_features, "Required Features")
  report_hash_and_arrays.call(:non_required_features, "Non-required Features")
  report_hash_and_arrays.call(:permissions, "Permissions")

  markdown(md)
  true
rescue StandardError => e
  warn("apkstats failed to execute the command due to #{e.message}")

  on_error(e)
  false
end
dex_count(_opts = {}) click to toggle source

Show the number of dex of your apk file.

@return [Fixnum] return positive value if exists, otherwise -1.

# File lib/apkstats/plugin.rb, line 270
def dex_count(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result || -1
end
download_size(_opts = {}) click to toggle source

Show the download size of your apk file.

@return [Fixnum] return positive value if exists, otherwise -1.

# File lib/apkstats/plugin.rb, line 214
def download_size(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result ? result.to_i : -1
end
file_size(_opts = {}) click to toggle source

Show the file size of your apk file.

@return [Fixnum] return positive value if exists, otherwise -1.

# File lib/apkstats/plugin.rb, line 206
def file_size(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result ? result.to_i : -1
end
method_reference_count(_opts = {}) click to toggle source

Show the methods reference count of your apk file.

@return [Fixnum] return positive value if exists, otherwise -1.

# File lib/apkstats/plugin.rb, line 262
def method_reference_count(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result || -1
end
min_sdk(_opts = {}) click to toggle source

Show the min sdk version of your apk file.

@return [String, Nil] return nil unless retrieved.

# File lib/apkstats/plugin.rb, line 248
def min_sdk(_opts = {})
  run_command(apkanalyzer_command, __method__)
end
non_required_features(_opts = {}) click to toggle source

Show all non-required features of your apk file. The result doesn't contain required features.

@return [Array<String>, Nil] return nil unless retrieved.

# File lib/apkstats/plugin.rb, line 232
def non_required_features(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result ? result.to_a : nil
end
permissions(_opts = {}) click to toggle source

Show all permissions of your apk file.

@return [Array<String>, Nil] return nil unless retrieved.

# File lib/apkstats/plugin.rb, line 240
def permissions(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result ? result.to_a : nil
end
required_features(_opts = {}) click to toggle source

Show all required features of your apk file. The result doesn't contain non-required features.

@return [Array<String>, Nil] return nil unless retrieved.

# File lib/apkstats/plugin.rb, line 223
def required_features(_opts = {})
  result = run_command(apkanalyzer_command, __method__)
  result ? result.to_a : nil
end
target_sdk(_opts = {}) click to toggle source

Show the target sdk version of your apk file.

@return [String, Nil] return nil unless retrieved.

# File lib/apkstats/plugin.rb, line 255
def target_sdk(_opts = {})
  run_command(apkanalyzer_command, __method__)
end

Private Instance Methods

apkanalyzer_command() click to toggle source
# File lib/apkstats/plugin.rb, line 289
def apkanalyzer_command
  return @apkanalyzer_command if defined?(@apkanalyzer_command)

  command_path = apkanalyzer_path || `which apkanalyzer`.chomp

  if command_path.empty?
    sdk_path = ENV["ANDROID_HOME"] || ENV["ANDROID_SDK_ROOT"]

    if sdk_path
      tmp_path = File.join(sdk_path, "cmdline-tools/tools/bin/apkanalyzer")
      tmp_path = File.join(sdk_path, "tools/bin/apkanalyzer") unless File.executable?(tmp_path)

      command_path = tmp_path if File.executable?(tmp_path)
    else
      warn("apkstats will not infer the apkanalyzer path in further versions so please include apkanalyer in your PATH or specify it explicitly.")
    end
  end

  command_path = command_path.chomp

  raise Error, "Please include apkanalyer in your PATH or specify it explicitly." if command_path.empty?
  raise Error, "#{command_path} is not executable." unless File.executable?(command_path)

  @apkanalyzer_command = Apkstats::Command::ApkAnalyzer.new(command_path: command_path)
end
on_error(err) click to toggle source

@param [StandardError] err a happened error @return [NilClass]

# File lib/apkstats/plugin.rb, line 317
def on_error(err)
  warn err.message
  err.backtrace&.each { |line| warn line }
  nil
end
run_command(command, name) click to toggle source

@param [Apkstats::Command::Executable] command a wrapper class of a command @param [String] name an attribute name

# File lib/apkstats/plugin.rb, line 279
def run_command(command, name)
  raise "#{command.command_path} is not found or is not executable" unless command.executable?

  return command.send(name, apk_filepath)
rescue StandardError => e
  warn("apkstats failed to execute the command #{name} due to #{e.message}")

  on_error(e)
end