IOS 搭建CocoaPods环境

搭建CocoaPods环境
CocoaPods 是什么?
CocoaPods 是一个负责管理 iOS 项目中第三方开源库的工具。

开发 iOS 项目不可避免地要使用第三方开源库,在使用第三方库时,除了需要导入源码,集成这些依赖库还需要我们手动去配置,还有当这些第三方库发生了更新时,还需要手动去更新项目,这就显得非常麻烦。

而 CocoaPods 的出现使得我们可以节省设置和更新第三方开源库的时间,通过 CocoaPods,我们可以将第三方的依赖库统一管理起来,配置和更新只需要通过简单的几行命令即可完成。

为什么要使用 CocoaPods?
在使用 CocoaPods 之前,开发项目需要用到第三方开源库的时候,我们需要:

把开源库的源代码复制到项目中
添加一些依赖框架和动态库
设置 -Objc,-fno-objc-arc 等参数
管理它们的更新
在使用 CocoaPods 之后,我们只需要把用到的开源库放到一个名为 Podfile 的文件中,然后执行 pod update 就可以了,CocoaPods 就会自动将这些第三方开源库的源码下载下来,并且为我们的工程设置好相应的系统依赖和编译参数。

CocoaPods 的安装
替换源
移除系统 ruby 默认源

$gem sources –remove https://rubygems.org/

使用新的源

$gem sources -a https://ruby.taobao.org/(淘宝的源,不建议使用)

$gem source -a https://gems.ruby-china.org(源已改变:$gem source -a https://gems.ruby-china.com)

验证是否替换成功

%title插图%num

$gem sources -l

安装

$pod setup

pod setup 的作用:将所有第三方的 Podspec 索引文件更新到本地的 ~/.CocoaPods/repos 目录下。所有的第三方开源库的 Podspec 文件都托管在 https://github.com/CocoaPods/Specs 管理,我们需要把这个 Podspec 文件保存到本地,这样才能使用命令 pod search 来搜索一个开源库。

进入文件目录 ~/.CocoaPods

$cd ~/.CocoaPods

查看文件大小

$du -sh

验证是否安装成功以及是否是自己需要的版本

$pod –version

Android DeepLink的实现原理

前言

之前我们又是看源码又是研究动画,今天分享一个比较简单的技术点:DeepLink。

DeepLink,深度链接技术,主要应用场景是通过Web页面直接调用Android原生app,并且把需要的参数通过Uri的形式,直接传递给app,节省用户的注册成本。简单的介绍DeepLink概念之后,我们看一个实际的例子:

朋友通过京东分享给我一个购物链接:

%title插图%num

于是我通过微信打开了这条链接:

%title插图%num

在微信中打开这个网址链接,提示我打开京东app,如果我点击了允许,就会打开我手机中的京东app,并且跳转到这个商品的详情页:

%title插图%num

通过这种形式,我可以快速的找到需要查看的商品,并且完成购买相关的操作。是不是非常方便,这就是DeepLink。

正文

这么流弊的DeepLink是不是非常的难?其实DeepLink的基本实现是简单到不可思议,他的核心思想实际上是Android的隐式启动。我们平时的隐式启动主要是通过Action和Category配合启动指定类型的Activity:

  1. <activity
  2. android:name=“.SecondActivity”
  3. android:exported=“true”>
  4. <intent-filter>
  5. <action android:name=“com.lzp.deeplinkdemo.SECOND” />
  6. <category android:name=“android.intent.category.DEFAULT” />
  7. </intent-filter>
  8. </activity>
  1. val intent = Intent(“com.lzp.deeplinkdemo.SECOND”)
  2. intent.addCategory(Intent.CATEGORY_DEFAULT)
  3. startActivity(intent)

除了action和category,还有一种隐式启动的用法是配置data:

  1. <data
  2. android:scheme=“xxxx”
  3. android:host=“xxxx”
  4. android:port=“xxxx”
  5. android:path=“xxxx”
  6. android:pathPattern=“xxxx”
  7. android:pathPrefix=“xxxx”
  8. android:mimeType=“xxxx”/>

scheme:协议类型,我们可以自定义,一般是项目或公司缩写,String

host:域名地址,String

port:端口,int。

path:访问的路径,String

pathPrefix:访问的路径的前缀,String

pathPattern:访问路径的匹配格式,相对于path和pathPrefix更为灵活,String

mimeType:资源类型,例如常见的:video/*, image/png, text/plain。

通过这几个配置项,我们发现data实际上为当前的页面绑定了一个Uri地址,这样就可以通过Uri直接打开这个Activity。

复习一下Uri的结构:

<scheme> :// <host> : <port> / [ <path> | <pathPrefix> | <pathPattern> ]

示例:https://zhidao.baidu.com/question/2012197558423339788.html

scheme和host不可缺省,否则配置无效;path,pathPrefix,pathPattern一般指定一个就可以了,pathPattern与host不可同时使用;mimeType可以不设置,如果设置了,跳转的时候必须加上mimeType,否则不能匹配到Activity。

现在我们修改SecondActivity的intent-filer:

  1. <activity
  2. android:name=“.SecondActivity”
  3. android:exported=“true”>
  4. <intent-filter>
  5. <action android:name=“com.lzp.deeplinkdemo.SECOND” />
  6. <category android:name=“android.intent.category.DEFAULT” />
  7. </intent-filter>
  8. <intent-filter>
  9. <data
  10. android:scheme=“lzp”
  11. android:host=“demo”
  12. android:port=“8888”
  13. android:path=“/second”
  14. android:pathPattern=“/second”
  15. android:pathPrefix=“/second”
  16. android:mimeType=“text/plain”/>
  17. </intent-filter>
  18. </activity>

打开SecondActivity:

  1. val intent = Intent()
  2. intent.setDataAndType(Uri.parse(“lzp://demo:8888/second”), “text/plain”)
  3. startActivity(intent)

现在在App中已经可以打开页面了,那么用web能不能正常打开呢?首先配置MainActivity的intent-filter:

  1. <activity
  2. android:name=“.MainActivity”
  3. android:exported=“true”>
  4. <intent-filter>
  5. <action android:name=“android.intent.action.MAIN” />
  6. <category android:name=“android.intent.category.LAUNCHER” />
  7. </intent-filter>
  8. <intent-filter>
  9. <data
  10. android:scheme=“lzp”
  11. android:host=“demo”
  12. android:path=“/main”/>
  13. </intent-filter>
  14. </activity>

Web需要打开url链接,所以我们不需要配置mimeType,

手写一个简单的Html页面:

  1. <!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01//EN” “http://www.w3.org/TR/html4/strict.dtd”>
  2. <html>
  3. <head>
  4. <meta http-equiv=“Content-Type” content=“text/html; charset=utf-8”>
  5. <meta http-equiv=“Content-Style-Type” content=“text/css”>
  6. <title></title>
  7. <meta name=“Generator” content=“Cocoa HTML Writer”>
  8. <meta name=“CocoaVersion” content=“1561.4”>
  9. <style type=“text/css”>
  10. p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 17.0px; font: 12.0px ‘Songti SC’; color: #000000; -webkit-text-stroke: #000000; min-height: 17.0px}
  11. span.s1 {font-kerning: none}
  12. </style>
  13. </head>
  14. <body>
  15. <a href=“lzp://demo/main”>打开main</a>
  16. </html>

Html页面添加了一个链接,点击打开lzp://demo/main这个地址。把html导入到手机中,用浏览器打开,点击“打开app”,毫无反应!!!

没错,如果只是配置了data,Web还是没办法通过url地址打开我们的Activity,那怎么解决这个问题呢?

  1. /**
  2. * Activities that can be safely invoked from a browser must support this
  3. * category. For example, if the user is viewing a web page or an e-mail
  4. * and clicks on a link in the text, the Intent generated execute that
  5. * link will require the BROWSABLE category, so that only activities
  6. * supporting this category will be considered as possible actions. By
  7. * supporting this category, you are promising that there is nothing
  8. * damaging (without user intervention) that can happen by invoking any
  9. * matching Intent.
  10. */
  11. @SdkConstant(SdkConstantType.INTENT_CATEGORY)
  12. public static final String CATEGORY_BROWSABLE = “android.intent.category.BROWSABLE”;

我们还需要配置:

<category android:name="android.intent.category.BROWSABLE" />

从官方的注释上写明:需要浏览器打开Activity,需要设置这个分类。例如邮件,只有设置了这个分类的Activity才会考虑被打开。加上这个配置后,再次点击看看有没有效果。

如果你真的亲自尝试了,你会发现还是没有效果。这个时候我们需要回顾一下action和category的用法:

首先需要尝试匹配action,action匹配成功了之后,才会继续匹配设置的category,所以单独匹配category是没有任何效果的。

因为我们要打开的仅仅是一个页面,所以我们设置:

  1. /**
  2. * Activity Action: Display the data to the user. This is the most common
  3. * action performed on data — it is the generic action you can use on
  4. * a piece of data to get the most reasonable thing to occur. For example,
  5. * when used on a contacts entry it will view the entry; when used on a
  6. * mailto: URI it will bring up a compose window filled with the information
  7. * supplied by the URI; when used with a tel: URI it will invoke the
  8. * dialer.
  9. * <p>Input: {@link #getData} is URI from which to retrieve data.
  10. * <p>Output: nothing.
  11. */
  12. @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
  13. public static final String ACTION_VIEW = “android.intent.action.VIEW”;

官方的注释说明ACTION_VIEW表示展示数据的页面,系统默认的Action就是ACTION_VIEW。添加上ACTION_VIEW,再次点击打开app。

还是不行,但是跟之前不同的是,这次出现了启动app的提示窗口,但是app却闪退了,看一下崩溃日志:

  1. 0906 14:35:15.459 12163270/? W/IntentResolver: resolveIntent failed: found match, but none with CATEGORY_DEFAULT
  2. 0906 14:35:15.473 2670826708/? E/AKAD: thread:Thread[main,5,main]
  3. android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=lzp://demo/main?id=111 flg=0x10000000 (has extras) }
  4. at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)
  5. at android.app.Activity.startActivityIfNeeded(Activity.java:4420)
  6. at android.app.Activity.startActivityIfNeeded(Activity.java:4367)
  7. at org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl$3.onClick(ExternalNavigationDelegateImpl.java:239)
  8. at com.qihoo.browser.dialog.CustomDialog$3.onClick(CustomDialog.java:274)
  9. at android.view.View.performClick(View.java:5267)
  10. at android.view.View$PerformClick.run(View.java:21249)
  11. at android.os.Handler.handleCallback(Handler.java:739)
  12. at android.os.Handler.dispatchMessage(Handler.java:95)
  13. at android.os.Looper.loop(Looper.java:148)
  14. at android.app.ActivityThread.main(ActivityThread.java:5541)
  15. at java.lang.reflect.Method.invoke(Native Method)
  16. at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
  17. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)

日志上写的很明确,虽然找到了匹配的页面,但是没有设置CATEGORY_DEFAULT。看来Web通过url来打开链接,必须要求设置CATEGORY_DEFAULT,添加上后,看一下我们完整的xml配置:

  1. <activity
  2. android:name=“.MainActivity”
  3. android:exported=“true”>
  4. <intent-filter>
  5. <action android:name=“android.intent.action.MAIN” />
  6. <category android:name=“android.intent.category.LAUNCHER” />
  7. </intent-filter>
  8. <intent-filter>
  9. <action android:name=“android.intent.action.VIEW” />
  10. <category android:name=“android.intent.category.DEFAULT” />
  11. <category android:name=“android.intent.category.BROWSABLE” />
  12. <data
  13. android:scheme=“lzp”
  14. android:host=“demo”
  15. android:path=“/main”/>
  16. </intent-filter>
  17. </activity>

*后看一下效果:

 

%title插图%num

那么如何在通过url给app传递参数呢?要实现这个也很简单,首先我们知道要想给url添加参数,直接在url后拼接key=value就可以了,例如:

http://www.baidu.com/s?wd=android

其中wd=android就是我们要添加的参数,现在假设我们需要为Activity传递一个参数id,我们就可以修改uri为:

lzp://demo/main?id=111

客户端接收key为id的参数的方法:

  1. if (intent != null && intent.data != null) {
  2. Log.e(“lzp”, intent.data.getQueryParameter(“id”))
  3. }

如果只是接收参数的话,客户端不需要进行任何修改,但是这里有一种情况,如果我们Activity必须传递id,如果不传递id不允许跳转怎么办呢?我们有两种办法解决这个问题:

1、在刚才的if语句增加else判断,当参数为空的时候,进行finish操作。

2、通过pathPattern,通过通配符设置必须有参数。

我们看一下第二种的实现方式:

  1. <data
  2. android:pathPattern=“lzp://demo/main?id=*”
  3. android:scheme=“lzp” />

之前已经说过,pathPattern不能和host同时使用,所以我们只能删除host,pathPattern匹配的是整个Uri,这样我们还可以指定多个参数。但是AndroidManifest.xml会报错,我们忽略就可以了

总结

其实DeepLink的实现原理就是这么简单,只是我们对于隐式启动理解的不够。是不是也想给自己的App加上DeepLink呢?赶快尝试一下吧~

 

IOS测试之sonar检查ios代码质量

ios项目
我提供了一个sample的项目,首先git clone该项目到本地。

git clone https://github.com/DoctorQ/ios_test.git
Cloning into ‘ios_test’…
remote: Counting objects: 88, done.
remote: Total 88 (delta 0), reused 0 (delta 0), pack-reused 87
Unpacking objects: 100% (88/88), done.
Checking connectivity… done.

安装OCLint
因为sonar-objective-c使用的是OCLint来扫描代码的,所以需要安装OCLint,使用下面的命令来安装 (前提是你有brew工具)。

brew install https://gist.githubusercontent.com/TonyAnhTran/e1522b93853c5a456b74/raw/157549c7a77261e906fb88bc5606afd8bd727a73/ocli nt.rb

安装sonar
看我另外两篇关于sonar的安装使用。

sonarQube
sonarRunner
其他工具安装
安装XCtool
详细安装方法参考官网

brew install xctool

安装gcovr
详细安装方法参考官网

brew install gcovr

xcode安装
直接app store上安装xcode*新版

添加sonar-objective-c文件
文件添加
如果想要分析代码,需要在待分析项目根目录下添加2个文件:

run-sonar.sh
sonar-project.properties
文件修改
sonar-project.properties文件中修改一些配置信息。详细的配置说明我已经写在各个脚本的注释上了:

##########################
# Required configuration #
##########################
#sonar服务器上的访问地址后缀,比如我们可以设置下面的属性为ios,就可以通过http://server:port/dashboard/index/ios来访问该项目的数据
sonar.projectKey=ios
#项目的名称
sonar.projectName=ios
#版本号
sonar.projectVersion=1.0
sonar.language=objc

# Project description
sonar.projectDescription=test for sonar-objective-c

# 要检测的源码的目录
sonar.sources=0403_test
# 要检测的测试源码目录,如果没有注释掉即可
# sonar.tests=testSrcDir

# Xcode project configuration (.xcodeproj or .xcworkspace)
# -> If you have a project: configure only sonar.objectivec.project
# -> If you have a workspace: configure sonar.objectivec.workspace and sonar.objectivec.project
# and use the later to specify which project(s) to include in the analysis (comma separated list)
#项目根目录下的xcodeproj的名称
sonar.objectivec.project=0403_test.xcodeproj
# sonar.objectivec.workspace=myApplication.xcworkspace

# 应用的scheme信息,如果不知道没关系,先执行该脚本,它会提示你当前项目的scheme信息
sonar.objectivec.appScheme=0403_test
# 同上,只是用于测试,如果没有就直接注释掉
# sonar.objectivec.testScheme=myApplicationTests

##########################
# Optional configuration #
##########################

# Encoding of the source code
sonar.sourceEncoding=UTF-8

# JUnit report generated by run-sonar.sh is stored in sonar-reports/TEST-report.xml
# Change it only if you generate the file on your own
# The XML files have to be prefixed by TEST- otherwise they are not processed
# sonar.junit.reportsPath=sonar-reports/

# Cobertura report generated by run-sonar.sh is stored in sonar-reports/coverage.xml
# Change it only if you generate the file on your own
# sonar.objectivec.coverage.reportPattern=sonar-reports/coverage*.xml

# OCLint report generated by run-sonar.sh is stored in sonar-reports/oclint.xml
# Change it only if you generate the file on your own
# sonar.objectivec.oclint.report=sonar-reports/oclint.xml

# Paths to exclude from coverage report (tests, 3rd party libraries etc.)
# sonar.objectivec.excludedPathsFromCoverage=pattern1,pattern2
sonar.objectivec.excludedPathsFromCoverage=.*Tests.*

# Project SCM settings
# sonar.scm.enabled=true
# sonar.scm.url=scm:git:https://…

执行sonar检测
命令行定位到项目根目录下,执行sh run-sonar.sh命令

sh run-sonar.sh
Running run-sonar.sh…
-n Extracting Xcode project information
-n .

-n .

Skipping tests as no test scheme has been provided!
-n Running OCLint…

-n Running SonarQube using SonarQube Runner
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .
-n .

执行成功后,查看结果如下:

%title插图%num

DONE!

Android应用开发中使用deeplink

一、DeepLink的应用场景
DeepLink简单理解就是通过在手机上点击一个链接后能实现如下功能:

如果目标App没有启动,那么就拉起App,并跳转到App内指定页面
如果目标App已经启动,那么就把App拉到前台并跳转到App内指定页面
二、DeepLink的实现思路
在Android开发中,可以通过在清单文件中配置scheme来实现页面跳转,所以可以通过scheme匹配的方式来实现DeepLink的功能。配置方式大概分为三种:

为每一个要跳转的Activity都指定一个对应的匹配条件,如果页面太多的话,这种方式管理起来不太方便,而且没有办法对App全局配置信息、用户状态等进行统一处理
配置闪屏页面为匹配页面,闪屏页一般都是App冷启动时才会出现,而且打开首页后,闪屏页就会关闭,这种方式在App没有启动的情况下可以很好的处理对应的Intent信息,但是如果App已经启动过了,在去拉起闪屏页就不合理了
配置首页为匹配页面,首页在App中一般都是常驻的,一般情况下首页关闭就意味着App的退出,所以可以在首页中统一处理匹配scheme得到的Intent信息,然后进行统一的跳转分发(需要将首页Activity的启动模式设置为singleTask以防止首页创建多个页面)
三、DeepLink的实现案例
下面使用上述的方式3写个Demo进行验证,实现步骤如下:

提前定义好自己的scheme、host等信息配置到清单文件里面,scheme是必须要有的,像host等信息可以配置也可以没有,我这里配置了scheme和host两个条件,其中sheme是“link”,host是“cn.znh.deeplinkdemo”,清单文件配置如下:
<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”cn.znh.deeplinkdemo”>

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>

<!–闪屏页设置为启动页–>
<activity android:name=”.SplashActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<!–配置首页–>
<activity
android:name=”.MainActivity”
android:launchMode=”singleTask”>
<intent-filter>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />

<data
android:host=”cn.znh.deeplinkdemo”
android:scheme=”link” />
</intent-filter>
</activity>

<!–普通的Activity–>
<activity android:name=”.Link1Activity” />
<activity android:name=”.Link2Activity” />
<activity android:name=”.Link3Activity” />
</application>

</manifest>

在首页Activity的onCreate方法和onNewIntent方法里面,接收Intent参数进行相应的跳转处理,首页代码如下:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
* Created by znh on 2018/12/23.
* <p>
* 首页
*/
public class MainActivity extends AppCompatActivity {

//定义的scheme
private static final String SCHEME_VALUE = “link”;

//定义的host
private static final String HOST_VAULE = “cn.znh.deeplinkdemo”;

/**
* 首次启动MainActivity调用该方法
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
schemeIntent(getIntent());
}

/**
* 启动已经存在的MainActivity调用该方法(singleTask启动模式)
*
* @param intent
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
schemeIntent(intent);
}

/**
* 处理intent事件
*
* @param intent
*/
private void schemeIntent(Intent intent) {
if (intent == null || intent.getData() == null) {
return;
}

//获取Uri
Uri uri = intent.getData();

//打印出uri里取出的Scheme和Host
Log.e(“schemeIntent”, “getScheme:” + uri.getScheme());
Log.e(“schemeIntent”, “getHost:” + uri.getHost());

//判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
return;
}

//如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
String type = uri.getQueryParameter(“type”);
String id = uri.getQueryParameter(“id”);

//打印uri里取出的参数
Log.e(“schemeIntent”, “type:” + type);
Log.e(“schemeIntent”, “id:” + id);

//进行统一的跳转分发
if (“1”.equals(type)) {
Intent intent1 = new Intent(this, Link1Activity.class);
startActivity(intent1);
} else if (“2”.equals(type)) {
Intent intent2 = new Intent(this, Link2Activity.class);
startActivity(intent2);
} else if (“3”.equals(type)) {
Intent intent3 = new Intent(this, Link3Activity.class);
startActivity(intent3);
}
}
}

上述两个步骤就可以实现deeplink的效果了,可以在AndroidStudio里输入如下命令进行测试:

adb shell am start -a android.intent.action.VIEW -d “link://cn.znh.deeplinkdemo?’type=2&id=335′”
1
观察结果发现页面跳转到Link2Activity了,log打印结果如下:

E/schemeIntent: getScheme:link
E/schemeIntent: getHost:cn.znh.deeplinkdemo
E/schemeIntent: type:2
E/schemeIntent: id:335

四、闪屏页的问题处理
经过上面两个步骤,确实已经可以实现deeplink的功能了,但是还有个问题,那就是如果App还没有启动的情况下,由于直接拉起的是首页页面,并没有经过闪屏页(如果App已经启动过了,不需要走闪屏页,直接走首页然后进行对应页面跳转是没有问题的),那么怎么解决这个问题呢,这里想到的一个解决方案是记录一个是否是经闪屏页启动的一个标志位,如果是就正常处理,如果不是就重新开启闪屏页。具体实现如下:

在闪屏页面跳转到首页时,在Intent中传递过去一个标志位参数isSplashLanuch,以标识闪屏页已经启动过了
//跳转到首页
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra(“isSplashLanuch”, true);
startActivity(intent);
finish();

在首页页面中获取isSplashLanuch的值来判断闪屏页面是否已经启动过(需要注意,在onNewIntent方法中要将isSplashLanuch的值永远设置为true),如果为true不进行特殊处理,如果为false就关闭首页并开启闪屏页,还要传递给闪屏页uri数据,方便跳转场景还原,首页处理Intent的方法如下:
/**
* 处理intent事件
*
* @param intent
*/
private void schemeIntent(Intent intent) {
if (intent == null || intent.getData() == null) {
return;
}

//获取Uri
Uri uri = intent.getData();

//打印出uri里取出的Scheme和Host
Log.e(“schemeIntent”, “getScheme:” + uri.getScheme());
Log.e(“schemeIntent”, “getHost:” + uri.getHost());

//判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
return;
}

//如果闪屏页启动过了,就不处理,否则关闭首页打开闪屏页
if (!isSplashLanuch) {
Intent intentSplash = new Intent(this, SplashActivity.class);
intentSplash.setData(uri);//设置uri数据,方便场景还原
startActivity(intentSplash);
finish();
return;
}

//如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
String type = uri.getQueryParameter(“type”);
String id = uri.getQueryParameter(“id”);

//打印uri里取出的参数
Log.e(“schemeIntent”, “type:” + type);
Log.e(“schemeIntent”, “id:” + id);

//进行统一的跳转分发
if (“1”.equals(type)) {
Intent intent1 = new Intent(this, Link1Activity.class);
startActivity(intent1);
} else if (“2”.equals(type)) {
Intent intent2 = new Intent(this, Link2Activity.class);
startActivity(intent2);
} else if (“3”.equals(type)) {
Intent intent3 = new Intent(this, Link3Activity.class);
startActivity(intent3);
}
}

在闪屏页执行结束跳转到首页时,将uri数据带到首页进行场景还原,跳转到首页的代码如下:
//跳转到首页,并重新设置拿到的uri数据
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
if (getIntent() != null && getIntent().getData() != null) {
intent.setData(getIntent().getData());
}
intent.putExtra(“isSplashLanuch”, true);
startActivity(intent);
finish();

在次输入命令进行测试,观察结果,冷启动时可以走闪屏页面了,跳转逻辑页正常。

五、闪屏页和首页的完整代码
闪屏页完整代码:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

/**
* Created by znh on 2018/12/23.
* <p>
* 闪屏页
*/
public class SplashActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);

//延迟3秒跳转到首页
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//跳转到首页,并重新设置拿到的uri数据
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
if (getIntent() != null && getIntent().getData() != null) {
intent.setData(getIntent().getData());
}
intent.putExtra(“isSplashLanuch”, true);
startActivity(intent);
finish();
}
}, 3000);
}
}

首页的完整代码:
package cn.znh.deeplinkdemo;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

/**
* Created by znh on 2018/12/23.
* <p>
* 首页
*/
public class MainActivity extends AppCompatActivity {

//定义的scheme
private static final String SCHEME_VALUE = “link”;

//定义的host
private static final String HOST_VAULE = “cn.znh.deeplinkdemo”;

//闪屏页是否启动过了
private boolean isSplashLanuch = false;

/**
* 首次启动MainActivity调用该方法
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
isSplashLanuch = getIntent().getBooleanExtra(“isSplashLanuch”, false);
schemeIntent(getIntent());
}

/**
* 启动已经存在的MainActivity调用该方法(singleTask启动模式)
*
* @param intent
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
isSplashLanuch = true;
schemeIntent(intent);
}

/**
* 处理intent事件
*
* @param intent
*/
private void schemeIntent(Intent intent) {
if (intent == null || intent.getData() == null) {
return;
}

//获取Uri
Uri uri = intent.getData();

//打印出uri里取出的Scheme和Host
Log.e(“schemeIntent”, “getScheme:” + uri.getScheme());
Log.e(“schemeIntent”, “getHost:” + uri.getHost());

//判断取出的Scheme和Host是否和自己配置的一样,如果一样进行相应的处理,否则不处理
if (!SCHEME_VALUE.equals(uri.getScheme()) || !HOST_VAULE.equals(uri.getHost())) {
return;
}

//如果闪屏页启动过了,就不处理,否则关闭首页打开闪屏页
if (!isSplashLanuch) {
Intent intentSplash = new Intent(this, SplashActivity.class);
intentSplash.setData(uri);//设置uri数据,方便场景还原
startActivity(intentSplash);
finish();
return;
}

//如果Scheme和Host匹配成功,取出uri中的参数并进行相应的业务处理
String type = uri.getQueryParameter(“type”);
String id = uri.getQueryParameter(“id”);

//打印uri里取出的参数
Log.e(“schemeIntent”, “type:” + type);
Log.e(“schemeIntent”, “id:” + id);

//进行统一的跳转分发
if (“1”.equals(type)) {
Intent intent1 = new Intent(this, Link1Activity.class);
startActivity(intent1);
} else if (“2”.equals(type)) {
Intent intent2 = new Intent(this, Link2Activity.class);
startActivity(intent2);
} else if (“3”.equals(type)) {
Intent intent3 = new Intent(this, Link3Activity.class);
startActivity(intent3);
}
}
}

 

Android Deeplink配置

Deeplink启动应用配置注意事项
1. Deeplink格式说明
Deeplink是目前使用广告跟踪非常热门的一种方式,Deeplink的链接类型一般是schema://host/path?params样式。

2. 为接收Deeplink配置intent-filter
在Android设备中,点击Deeplink后可以打开指定应用,为了能够正确定位到需要打开的应用,并正确打开指定的Activity,需要应用开发过程中对Intent进行过滤接收进行配置(就是intent-filter),具体做法是在AndroidManifest.xml中对Activity声明的时候添加<intent-filter>的<data>节点,配置schema和一些必要的区分属性参数(如:host、path等)即可,配置的属性参数越多越详细,越能保证唯一性,准确打开需要打开的应用,而不是弹出打开应用选择框。

<intent-filter>标签包含以下属性

动作:外部打开必须配置成ACTION_VIEW,这样外部的打开指令才能到达;
范畴:必须包含DEFAULT,这个category允许你的Activity可以接收隐式Intent,如果没有配置这个,Activity只能通过指定应用程序容器名称打开;也必须包含BROWSABLE,这个category允许你的intent-filter可以在Web浏览器中访问,如果没有配置这个,点击Web浏览器中的Deeplink链接将无法解析并打开Activity;
数据:需要添加一个或者多个<data>标签,每一个<data>标签都描述了什么样格式的URI将会分派到Activity进行处理。每一个<data>标签至少且必须包含一个android:schema属性。
你可以添加更多的属性来提高Activity所能接收的URI类型的精准度。举个例子:你的应用会在多个activity中接收类似的URI(相同的schema和host),但这些URI根据有这不同的路径(path),在这种情况下,使用android:path属性,或者使用路径正则表达式(pathPattern)和路径前缀(pathPrefix)变种来区分对于不同的路径,系统需要打开哪个Activity。
测试的Deeplink链接:rsdkdemo://rs.com/test?referer=Deeplink_Test

Activity的配置intent-filter如下:

<activity
android:name=”com.rastargame.sdk.oversea.demo.RSDemoActivity”
android:configChanges=”orientation|keyboardHidden|navigation|screenSize”
android:launchMode=”singleTop”
android:screenOrientation=”sensor”
android:theme=”@android:style/Theme.Holo.Light.NoActionBar.Fullscreen”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
<intent-filter>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />
<data android:scheme=”rsdkdemo”
android:host=”rs.com”
android:pathPrefix=”/test”/>
</intent-filter>
</activity>

说明:
1.<data>中的属性参数配置必须要根据Deeplink来配置,尽可能配置更多属性参数保证唯一,否则点击deeplink连接会出现选择打开应用页面。
2.如果需要在浏览器中也能打开应用,需要在intent-filter中添加<category android:name=”android.intent.category.BROWSABLE” />这个配置(这个属性的含义就是在特定的情况下,可以在浏览器中打开Activity)

3. intent-filter配置注意事项
在有<action android:name=”android.intent.action.MAIN” />的<intent-filter>中添加<data>标签配置会无法通过Deeplink正确打开相应页面。一个Activity是可以有多个<intent-filter>标签,所以添加另外的<intent-filter>标签进行配置;
<category android:name=”android.intent.category.LAUNCHER” />和<category android:name=”android.intent.category.DEFAULT” />两个是相互冲突的,同时添加这两个category会导致桌面图标无法显示的问题;
说明:如果你添加<intent-filter>的Activity不包含android.intent.action.MAIN的<action>标签,就无需配置多个intent-filter。

4. Deeplink数据解析
点击Deeplink打开应用的时候,会将Deeplink传入到应用,应用在Activity的onCreate和onNewIntent对数据就进行处理。

5. Deeplink测试
5.1 命令行adb测试deeplink
直接使用命令行adb测试deeplink,使用命令:

adb shell am start -a android.intent.action.VIEW -d “rsdkdemo://rs.com/test?referer=Deeplink_Test”
1
5.2 测试网页点击deeplink
首先,需要编写一个简单的html文件,保存为test.html,html文件内容如下:
<!DOCTYPE html>
<head>
<meta charset=”UTF-8″ />
<meta id=”viewport” name=”viewport” content=”width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,minimal-ui”>
</head>
<html>
<input type=”button” value=”点击我打开Deeplink” onclick=”javascrtpt:window.location.href=’rsdkdemo://rs.com/test?referer=Deeplink_Test'”>
</html>

然后将html文件拷贝到设备中,使用浏览器打开,点击按钮即可打开应用。
5.3 测试Facebook deeplink
测试Facebook deeplink需要集成Facebook SDK,然后完成相应的配置,然后通过广告助手测试DeepLinked,测试设备上必须安装了Facebook客户端,并且登录了开发者账号对应的Facebook账号。

【Android】DeepLink跳转简介

一、DeepLink的概念
DeepLink是将用户直接带到应用中特定内容的网址。在Android中,可以通过添加intent filters 并从传入的intent中提取数据来设置DeepLink,从而将 用户引导向正确的Activity。

当单击的链接或编程请求调用Web URI的intent时,Android系统将按顺序尝试以下每个操作,直到请求成功为止:

1.打开用户首选的可以处理URI的App(如果已指定)。

2.打开唯一可以处理URI的可用App。

3.允许用户从对话框中选择App。

即:用户通过点击或者其他的操作发送url请求,系统会对该url进行解析,然后调起注册过相应scheme的应用,如果有多个注册,会弹出对话框让用户选择。

二、DeepLink的作用
实现了网页与App之间的跳转。每个App不再是一个个独立的孤岛。交互非常的方便,将App连接到了整个网络世界,用过浏览器就能随意的跳转。
2. 通过DeepLink方式App之间可以相互拉活,相互跳转。

三、使用
1.创建指向应用内容的链接,需要在应用的AndroidManifest.xml中配置包含如下元素和属性的intent filter
<action> 中指定ACTION_VIEW,以便可以从Google搜索访问意图过滤器

<data> 添加一个或多个<data> 标记,每个标记表示解析为活动的URI格式。<data>标签必须至少 包含该android:scheme 属性。

<category> 包括BROWSABLE 类别。为了从Web浏览器访问intent过滤器,需要它。没有它,单击浏览器中的链接无法解析为您的应用程序。

以下XML代码段显示了如何在清单中为深度链接指定intent过滤器。URI “example://gizmos”和“http://www.example.com/gizmos”两者都解析为此活动。

<activity
android:name=”com.example.android.GizmosActivity”
android:label=”@string/title_gizmos” >
<intent-filter android:label=”@string/filter_view_http_gizmos”>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />
<!– Accepts URIs that begin with “http://www.example.com/gizmos” –>
<data android:scheme=”http”
android:host=”www.example.com”
android:pathPrefix=”/gizmos” />
<!– note that the leading “/” is required for pathPrefix–>
</intent-filter>
<intent-filter android:label=”@string/filter_view_example_gizmos”>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT” />
<category android:name=”android.intent.category.BROWSABLE” />
<!– Accepts URIs that begin with “example://gizmos” –>
<data android:scheme=”example”
android:host=”gizmos” />
</intent-filter>
</activity>

注意:两个intent-filter仅元素不同。同一个intent-filter可以包括多个,创建intent-filter的目的是要申报的唯一URL(如特定的组合是非常重要的scheme和host),因此多个在同一个intent-filter实际上合并在一起考虑其组合属性的所有变体。例如,请考虑以下事项

<intent-filter>

<data android:scheme=”https” android:host=”www.example.com” />
<data android:scheme=”app” android:host=”open.my.app” />
</intent-filter>

2.从传入的intent中读取数据
一旦系统通过intent filter启动Activity,就可以使用它提供的数据Intent来确定您需要呈现的内容。调用getData()和 getAction()方法来检索与传入相关的数据和操作Intent。可以在活动的生命周期中随时调用这些方法,但通常应该在早期回调期间执行此操作,例如 onCreate()或 onStart()。

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
}

参考网站:https://developer.android.google.cn/training/app-links/deep-linking

Android如何跳转到应用商店的APP详情页面

项目流程:从App内部点击Button按钮或者相应的条目,跳转到应用商店的某个APP的详情页面。

实现:

*步:导入获取手机App工具类
public class MarketUtils {
/**
* 获取已安装应用商店的包名列表
*
* @param context
* @return
*/
public static ArrayList<String> queryInstalledMarketPkgs(Context context) {
ArrayList<String> pkgs = new ArrayList<String>();
if (context == null)
return pkgs;
Intent intent = new Intent();
intent.setAction(“android.intent.action.MAIN”);
intent.addCategory(“android.intent.category.APP_MARKET”);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
if (infos == null || infos.size() == 0)
return pkgs;
int size = infos.size();
for (int i = 0; i < size; i++) {
String pkgName = “”;
try {
ActivityInfo activityInfo = infos.get(i).activityInfo;
pkgName = activityInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
if (!TextUtils.isEmpty(pkgName))
pkgs.add(pkgName);

}
return pkgs;
}

/**
* 过滤出已经安装的包名集合
*
* @param context
* @param pkgs
* 待过滤包名集合
* @return 已安装的包名集合
*/
public static ArrayList<String> filterInstalledPkgs(Context context,
ArrayList<String> pkgs) {
ArrayList<String> empty = new ArrayList<String>();
if (context == null || pkgs == null || pkgs.size() == 0)
return empty;
PackageManager pm = context.getPackageManager();
List<PackageInfo> installedPkgs = pm.getInstalledPackages(0);
int li = installedPkgs.size();
int lj = pkgs.size();
for (int j = 0; j < lj; j++) {
for (int i = 0; i < li; i++) {
String installPkg = “”;
String checkPkg = pkgs.get(j);
try {
installPkg = installedPkgs.get(i).applicationInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
if (TextUtils.isEmpty(installPkg))
continue;
if (installPkg.equals(checkPkg)) {
empty.add(installPkg);
break;
}

}
}
return empty;
}

/**
* 启动到app详情界面
*
* @param appPkg
* App的包名
* @param marketPkg
* 应用商店包名 ,如果为””则由系统弹出应用商店列表供用户选择,否则调转到目标市场的应用详情界面,某些应用商店可能会失败
*/
public static void launchAppDetail(String appPkg, String marketPkg) {
try {
if (TextUtils.isEmpty(appPkg))
return;
Uri uri = Uri.parse(“market://details?id=” + appPkg);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (!TextUtils.isEmpty(marketPkg))
intent.setPackage(marketPkg);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Myapp.getMyapp().startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第二部: 实现工具类的LanunchAppDetail方法

MarketUtils.launchAppDetail(“*个参数目标App的包名;”,”第二个参数:应用商店包名”);
注意:如果 应用商店包名为空 就会将手机上已下载的应用商店都列出来,让你选择一个进行跳转。

/*
*主流应用商店对应的包名如下:
com.android.vending Google Play
com.tencent.android.qqdownloader 应用宝
com.qihoo.appstore 360手机助手
com.baidu.appsearch 百度手机助
com.xiaomi.market 小米应用商店
com.wandoujia.phoenix2 豌豆荚
com.huawei.appmarket 华为应用市场
com.taobao.appcenter 淘宝手机助手
com.hiapk.marketpho 安卓市场
cn.goapk.market 安智市场
* */
希望可以给大家带来帮助。

如何避免Google Play账号关联问题

*近上架Google Play的过程中,遇到的*多的问题就是账号被关联,*严重的一次,全部在线超过30多个APP一夜之间全部被下线。身边的友商,100多个也是彻底死*。简直是被Google一招打回起点,幸幸苦苦经过一年多才积累起来的数量,一夜之间被搞死。

打开邮件,内容都是下面这样:

This is a notification that your Google Play Publisher account has
been terminated.

REASON FOR TERMINATION: Prior violations of the Developer Program
Policies and Developer Distribution Agreement by this or associated
accounts as outlined in previous emails sent to the registered email
address(es) of the Publisher account(s).

Google Play Publisher suspensions are associated with developers, and
may span multiple account registrations and related Google services.

You can visit the Developer Policy Center to better understand how we
enforce Developer Program Policies. If you’ve reviewed the policy and
feel this termination may have been in error, please reach out to our
policy support team.

Do not attempt to register a new developer account. We will not be
restoring your account at this time.

The Google Play Team

经过这一波,开始重视账号关联问题,之前出现过几次账号关联,但是都没有这次这么严重,基本都只是个别APP被关联封号,所以没有深入研究。但是这次,简直是伤经动骨,武功彻底被废,再不解决关联问题,那么能不能活下去都是一个问题。

经过我们反复研究,历经2个多月,基本已经找到了有效避免账号关联的操作方法,今天就在这里分享给大家具体思路,希望广大开发者能有效的避免账号关联的问题。思路主要分三个方面:

1、账号隔离;

2、代码隔离;

3、产品合法;

通过这三个方面,可以有效的避免关联问题,下面就详细和大家介绍下具体做法。

1、账号隔离;

所谓账号隔离,指的是开发者账号隔离,包括账号注册信息,日常登录使用,付款信息,登录环境等等。与账号相关的都需要做隔离。新账号与之前被封的账号不能有任何关联,特别是开发同事,千万不能在本地电脑登录开发账号。可以采用云服务器或虚拟机,具体采用何种方式,大家可以根据自身条件进行选择。

账号隔离做好了,被关联的风险能有效的降低,至少在APP上架前不会出什么幺蛾子,可以顺利的提交APP。

2、代码隔离;

代码隔离,即所有开发者,在开发的时候,需要做混淆加固等基础操作。同时避免直接从之前被下架、被封号的APP中大段的复制引用代码。包括图片文件、第三方库文件,引用的时候都需要留个心眼,避免重复使用。

我们的一个猜测是,Google使用机器学习的方法在对代码进行对比。所以代码层面的关联查处会越来越容易,这就非常考验开发人员。开发人员可以逆向推导自己代码写作过程,比如把自己想成Google的审核人员,如何来判定代码是不是同一个开发者或者是同一种功能。

代码隔离是*难的,方法也是非常不固定的,每个开发者都有自己的开发习惯和固定方式,在应对代码隔离的过程中,需要每个开发者结合自身习惯针对性的改变。

同时也需要避免不同账号使用同属于一个谷歌体系下第三方组件的服务,比如谷歌登录、谷歌统计分析、谷歌广告等,这些使用的时候,也尽量做一下隔离。

3、产品合法;

在我看来,产品符合谷歌开发者政策,这一点是*重要的。很多时候,账号被封,产品被下架,大家不愿意花时间走申诉流程,一方面是Google申诉流程漫长,另一方面是对自身产品的功能没有底气,其中总有一些是谷歌明确不允许的行为,所以也就懒得申诉了。

所以,我们在设计产品的时候,一定要让Google审核人员,能够明确知道你的产品功能,并且确定产品没有违反Google的开发者政策,在这里建议大家有时间,还是多看看Google开发者政策,当然Google开发者政策只是参考,毕竟制定政策的是谷歌自己,而且很多政策都看不懂,这个具体就需要每个人自己去判断Google的底线了。

开发者政策中,大家尽量避免触犯欺诈和隐私权;

这里重点说一下隐私权,随着大众和各国对隐私的保护法规健全,隐私权越来越被各大应用商店严格管控,因此大家在开发APP时,需要合理有效的使用各种权限,避免出现要了一堆的权限,但是很多都没有使用。或者是采用违规方式,获取敏感数据等违规行为。总之,对于各种权限的使用,一定要想清楚,按需使用。

好了,以上就是我们总结的关于避免账号关联的一些方法。大家可以参考,结合自身遇到的情况灵活应对。相信大家一定能解决账号关联问题。

千万别违规,Google Play 开发者政策解读

本篇文章篇幅较长,阅读完大约需要8分钟

今天我会结合我自己的经验分享下新手如何避免违规,先说下什么样的情况下会影响你的开发者账号信誉,拒*更新不会影响信誉,但是应用被下架就会影响了,谷歌是*注重信誉和版权的,一旦应用被下架大家也别去想什么歪脑筋,根据邮件提示去申诉说明情况,还是有可能申诉成功继续上架的,那什么情况下应用会被下架呢? 一个是你应用经常被拒*更新尤其是连续2次,或者直接违反了严重政策,比如侵犯了有版权内容的版权,宣传了暴力内容,侵犯隐私等,下面分别说下:

假冒和知识产权
首先大家必须要从头到尾阅读谷歌开发者政策的,就像我们做安卓开发必须要阅读应用开发文档一样, 但是官方文档可能没有很多实际例子来说明所以不是很好理解,我会根据一些例子来帮助大家理解, 简单说下假冒侵权,顾名思义假冒他人名称或者品牌,图标,资源都是假冒,尤其是知名流行品牌,比如谷歌,Facebook,你的应用标题,描述以及应用展示图, 开发者名称等等都不能包含这些字样,**强调一点:**很多同学的应用其实就是针对 Facebook 开发的,比如下载 Facebook 的视频应用,你可以叫 Video Downloader for Facebook, 但是不能起名字为: Facebook Video downloader, 大家能体会吧,总结来说就是不要误导用户觉得你和 Facebook 有关系。

知识产权相关问题就比较多了,这里说几个经常违反的,主要是自己应用侵权和鼓动用户侵权,在未经授权的情况下使用有版权的音乐,图片,公众人物等等都是违规的,举一个例子,前面文章提到上传应用*好拍摄一个应用介绍教程,那么这个教程里面就不能涉及到有版权内容,比如你觉得视频没声音,想加一个音乐,必须使用没有版权可以下载的音乐,其次比较重要的是别人的著作权,专利权做出来的产品如果你使用了可能违反 数字千年法案 ,这个法案厉害了违反了是直接下架应用的!所以在使用网上的资源的时候尤其要注意,多搜索一下再用。

上面说的属于自己侵权的,那什么是鼓动用户侵权呢?字面理解一下就是煽动用户使用受版权保护的内容,比如你在应用截图里提示用户去下载有版权音乐或者视频,其次还有商标侵权,比如你的应用是针对 Instagram 做的,所以你的应用 Logo 特别像 Instagram,这里的像的界限明显比较模糊,举几个维度:Instagram 的线条你不要使用,它 Logo 里面的方框别用,不要让用户一看就 Ins 就行了,但是我们可以用配色,形状,然后加入自己的图标,这样用户一看只能知道这可能是和 Ins 相关的,直接上个图:

%title插图%num
这够形象了吧,很明显能体会这2个应用图标都是干啥的,右边是和 Facebook 相关的,但是只是配色和形状差不多,这是没问题的,这里再提醒大家一点,用户通过我们的工具自己生成的内容是没有版权的,因为那是用户自己生成的,因为我们大部分做的都是工具,功能上基本都是没问题的,所以注意不要在Logo ,应用描述以及应用展示图上违规。

假冒和知识产权是新手*容易违反的政策了,但是咱们也不可能把所以违规类型都列举出来,所以平时有几个习惯大家可以尝试来避免,如果自己都觉得做法有可能违规,那就要多考虑了,另外如果你做某一个类型的应用时多看看其他应用,因为本来你也要看竞争对手都把应用做成了什么样了,**这里有一点就要注意了,**如果你看竞争对手的应用之后觉得要是加上这样一个功能肯定更好,也没啥开发量,但是它就是没加,那我是不是可以在自己的应用里加,注意,自己好好想想人家不加是不是因为这个功能违规了所以去掉了!

元数据政策
元数据政策也是很多同学经常违反的政策,什么是元数据政策? 我们不允许任何应用(包括但不限于应用的说明、开发者名称、标题、图标、屏幕截图和宣传图片)中包含误导性、不相关、过多或不恰当的元数据。我们也不允许在应用的说明中添加用户感言。这是官方文档给出的描述,理解一下来说就是蹭关键词要有度,比如说你可以说你的应用支持 小米,三星,Nexus(谷歌手机) 等等一些手机型号,这样当用户搜索上述手机型号时也可能搜索到你的应用,但是你不能堆砌关键词,比如你说你的应用制作出来的内容可以分享到 youtube,Facebook ,Twitter 等等一堆知名应用上去,很明显这是不行的,因为你可以统称他们为社交平台,比如你写 Facebook 等社交平台,是可以的。

还有一些伙伴在自己的应用描述中提及其他知名产品的关键词,比如你写比哪个应用更好或者怎么样,这是在误导用户和其他产品的关联,再有是不是我多写几次自己应用的核心关键词在描述中就能提高搜索权重呢,确实可以多次提及毕竟你做的产品就是这个核心关键词,但是一定要和句子连在一起,突出自己应用的特点。再有用户感言,用户感言不能写的例子就太多了,可以看下官网的例子:

%title插图%num
还有过多的细节描述,比如你列举了很多其他种类的狗都违反了使用过多细节的政策,我就不一一列举了。

再说一下引导用户评分,在合适的时候提示用户去五星好评这是没有问题的,但是千万不要和用户有交易,比如只要给好评我就允许你怎么样,要不然就不让你使用某些功能,这是不允许的,因为评分和评论是影响应用的排名的,所以大家想在这上面做文章,一直引导用户去给好评或者在回复用户评论的时候回复了一些不相关的链接让用户去访问和分享,这都是不行的,不过你可以留下一个 Gmail 邮箱,提醒用户可以发送邮件详细描述自己使用应用时遇到的问题。

垃圾重复内容
现在真的不是随便做一个应用就能赚钱的时候了,我们要做原创高质量应用,不管任何一个行业都是这样,要做精品,不知道有没有新手这样想,做一个 Webview 然后里面展示一下 Google 地址, 我这不就是搜索引擎应用了,展示一下 Facebook 官网不就是社交应用了,再展示一个视频地址就是视频应用了? 仔细想想这必然违规啊!另外功能很少但是广告一大堆就是以投放广告为目的的应用了,一个展示页面*多有一个广告就行了,广告政策相关的我就不在这篇文章细说了,后面再写一篇如何集成广告以及相关政策的。

篇幅真的有限,谷歌开发者政策还有很多,大家后续可以后台和我继续探讨,总结一下:就像文章上面提到的,当你自己觉得做了很轻松的事情就可以得到很多的下载量但是其他应用却没有做,那多半是违规的,大家都不傻,那么好的事怎么可能都没想到呢,还有强调一点如果如果应用违规了谷歌可能不会立即对你的应用做出惩罚,因为大家知道谷歌是机器加人工审核,但是不要心存侥幸心里,现在没检查出来以后也会检查出来,毕竟谷歌的技术不是盖的。

虽然说大家在发布应用的过程中难免违反谷歌的政策,但是我觉得这是好事,鼓励原创,反对抄袭,这样我们辛苦做出的应用才能一直为我们盈利,另外说句题外话,跟随*牛的公司,使用他们的工具,技术提供给用户服务,我们就会盈利,谷歌需要开发者,开发者也需要谷歌,就像阿里需要淘宝卖家,那么我们做技术的当然是和*牛的技术公司一起合作啊,所以我选择谷歌的王牌产品安卓来和谷歌建立某种联系。

这篇文章真的有点长了,如果你能一字一句的读到这里相信你会有所收获,希望大家不会违反谷歌开发者政策,稳稳赚钱。