文章 12 - Android 应用发布和变现初学者指南






4.95/5 (18投票s)
将您的 Android 应用推送到应用商店并变现的分步指南。
从 Google Play 商店下载应用
(运行应用前请仔细阅读自述文件)
目录
3. 应用开发与发布
3.1 Soocial Sentiment 应用核心概念
3.1.1 准备应用程序、Logo、图标
3.1.2 应用优化清单
3.2 应用发布
3.2.1 创建签名 APK
3.2.2 完成商店列表
3.2.3 上传 APK 并发布
3.3 更新应用
5. 使用 Google AdMob 盈利
5.1 获取 AdMob 账户并准备 Eclipse 进行 AdMob 集成
5.2 在 AdMob 控制台创建广告单元
6. 使用 Google Analytics 跟踪应用统计信息
7. 应用内计费
7.1 为应用启用应用内计费
7.2 为应用内计费设置 Eclipse 项目
1. 背景
很多时候,应用开发者会带着一个梦想开始他们的旅程,希望成为像 Rovio 一样成功的应用发布者,并可能成为一名“应用创业者”。梦想家名单里也包括我!但幸运的是,我用我的一款名为 Walk Mania 的 Windows 应用取得了一定的成功,该应用已获得超过 7000 次下载。最近,我还在开发我的 Android 应用 Dream Curtains ,该应用在 Google Play 商店已获得约 500 次下载。坦率地说,与那些下载量迅速突破数千次的应用相比,这些数字并不显著。因此,阅读一位本身并非“百万下载”发布者的应用发布指南可能会让人有些怀疑。所以,我不会以“这将是你 Android 应用发布的圣经”之类的宣言来夸大其词。相反,我将分享我关于应用发布和营销的知识与经验,以及应用发布过程中涉及的各种事项。我还将在本文中推广一款应用,向您展示将应用放入应用商店到底需要什么。
然后,我将整合某些对任何应用成功都至关重要的功能,这些功能被所有跨平台和应用商店的应用发布者普遍采用。那么,让我们开始讨论话题,看看我们能否真正成功!
2. 构思
制作应用的第一步是构思。当你第一次想到一个想法时,你可能会觉得这个想法非常棒。你甚至可能觉得这个想法真的会改变你的人生。说实话,在你开始编码之前,你必须先对这个想法进行研究。研究的第一部分是 细分市场。 细分市场是你将应用归属的细分或类别。例如,如果你的应用是关于跟踪你走过的距离,那么它很容易归入“减肥”细分市场。如果你的应用是关于帮助用户处理账户,那么它就属于“金融”细分市场。游戏是一个大家都很熟悉的常见细分市场。
因此,如果你的应用是健康应用,你应该在 Google 上搜索健康应用,你应该查看商店里的一些健康应用,更推荐下载并试玩其中几个。无论你的应用想法属于哪个细分市场,都值得仔细考察。通过研究,你就会知道该细分市场中哪些应用销量最好或下载量最高,它们提供了什么,以及你的想法是否提供了什么重要内容。
研究的第二部分涉及人们为应用付费的可能性。所以,如果你正在制作一款图像修饰应用,会有很多人愿意付费吗?你的目标受众是谁?他们的消费能力如何?
我觉得现在是时候透露我计划发布的内容了!我计划制作一款 Twitter 意见极性挖掘应用,该应用可以告知搜索和主题的情绪。
图 2.1:应用创意研究
此刻,我非常有信心,这至少在 Google Play 上是独一无二的。你可能不总是那么幸运,可能会发现你的想法已经有很多类似的应用存在。如果你发现这种情况,不必担心。你所要做的就是逐一下载它们。试用它们。找出用户作为用户来说,是否觉得它们有任何不足之处。你的想法是否能提供其他同类细分市场中没有的应用?或者,它的用户界面可能存在用户不喜欢的缺陷?你的想法的主题是否存在更直观/高效的替代工作流程?
如果是这样,那么你仍然可以从各种可能性中产生影响。如果不是,那么最好选择另一个主题或想法,无论你的想法看起来多么有前景。请记住,切勿在研究上节省成本,否则你将在开发和营销日程上付出沉重的代价。
一旦你的研究结果支持你的想法(就像我的情况一样),你就需要找到以下问题的答案:
1) 谁会使用我的应用? 在我们的例子中,我认为在购买产品之前,客户可能想了解品牌的积极性如何?促销经理可能想了解活动进展得有多积极!政治家可能想了解他/她所在党派/选区的民意,或普遍民意。所以,是的,我认为我知道可能会尝试我们应用的人。
2)应用是否提供了问题解决方案或增值?: 例如,如果你正在制作一个计算一个人每天爬多少级楼梯的应用,并将这些数据转化为一种挑战,让他/她知道自己消耗了多少卡路里,那么这样的应用将被称为解决方案应用。如果你的应用是一个简单的手电筒,利用了闪光灯,那么它就是一个解决方案。如果你的应用可以读出短信,那么这就是增值。解决方案比增值更有可能成功。尽管游戏都是增值,但我们目前主要将游戏排除在讨论之外。 在我们的例子中,我认为目前我的应用可能为产品意见分析提供独特的解决方案,同时也能为 Twitter 搜索增加价值。(我在这里过于偏颇,因为我必须表明我在决定继续开发这款应用时是理性的)。请诚实且务实地回答。
3)是否有足够多的人在搜索类似解决方案? 这个问题无法靠思考来回答。它需要研究。你怎么知道人们是否真的在寻找你打算提供的东西?为此,我们使用了一个称为 关键词研究 的概念。 你可以在 Google 关键词研究工具 中搜索你的想法。
你应该寻找高搜索量和高竞争。高竞争意味着广告商为这些关键词出价更高。更高的搜索量意味着更多的用户对你的应用感兴趣。看看我进行应用发布前的研究。
图 2.2:关键词研究
所以我的研究表明,“Sentiment”这个词在搜索量和广告商出价方面都相当不错。虽然你不会为应用的收入使用 Google 的 AdSense 模型,但它能很好地反映你应用的潜力。从中得到启示,我决定给我的应用命名为
4)关于应用货币化的初步计划: 如果你有一个很棒的产品或服务,你确信人们会“需要”并使用它,而不管价格如何,并且你确信你的应用为这个概念增加了巨大的价值,那么你可以选择直接付费应用,并设定合适的价格。开始时将价格保持在最低点,然后随着应用寻求者数量的增加而提高,这总是说得通的。
然而,以高价销售应用然后降价被认为是对以高价购买然后发现应用现已降价出售的客户的一种欺骗。因此,从低到高是更受欢迎的模式。
然而,即使是 0.99 美元,你也会发现很难找到买家。看看过去一年 Play 和 iTunes 应用商店的趋势,你会发现免费应用的数量显著增加。
那么,为什么选择免费应用?它是如何盈利的?
这种模式称为 Freemium 模式。你创建一个应用,并发布一个包含足够功能以对用户有用的版本,同时将某些版本关闭或限制某些功能。当用户使用你的应用并发现锁定功能对他/她来说极其重要时,他/她可以从应用内解锁这些功能。这种现象称为应用内购买。应用内购买产品可以是一个简单的解锁代码、一些额外功能或订阅(需要定期重复购买,如每月、每年等)。
应用的免费版本可能包含广告,这些广告可以从完整版本中移除。这是最流行的应用货币化模式。因此,在本教程中,我们将重点关注基于广告的 Freemium 应用货币化。
Social Sentiment
一款社交网络情绪分析应用,带有用于 Twitter 的文本挖掘!
4)我的应用适合哪种平台和设备?
图 2.3 Google Play 上的热门购买设备(图片来源:mobilephonedevelopment.com)
这并非难事,大家都知道高端设备比低端手机更倾向于购买应用。因此,你的研究应该真正了解你的目标受众是否拥有更多高端设备。例如,如果你正在制作一款对医生有帮助的医疗应用,那么你可以确信大多数医生都会拥有高端设备,因此销售机会应该更高。如果你的应用获取 Facebook 流,那么你可以确信大多数用户都是大学生,他们可能没有高端设备。因此,即使你的应用类别“Facebook”在搜索和竞争方面相当热门,它也很难为你带来太多收入。
在我的情况下,我假设主要用户是城市用户。他们从学生到专业人士都有。所以,我很有把握我不会针对“主要使用低端设备的用户”。没有这种积极的信心,很难生存,不是吗?
在确信了想法的潜力并构思了这个想法之后,终于到了转向开发的时候了。
3. 应用开发与发布
我们已经有了应用的详细信息。因此,在本教程中,我将不涵盖应用概念本身的技术细节,但我们将讨论将要为应用发布添加的功能。
在我们深入我们最喜欢的部分——编码之前,有一件重要的事情是,我们将总体分析一个应用应该具备哪些功能才能取得巨大成功。请看图 3.1 来理解我们正在关注的概念。
图 3.1:Android 应用的通用架构
好的,上面的图表基本概括了我们的目标。那么,让我们以我们的应用为例,看看我们如何实现应用发布?
在进行开发之前,我鼓励你获取一个 Google Play 开发者账号。
引用:Android.com
- 访问 Google Play 开发者控制台。
- 输入关于你的开发者身份的基本信息——姓名、电子邮件地址等。你以后可以修改这些信息。
- 阅读并接受你所在国家或地区的开发者分发协议。请注意,你在 Google Play 上发布的应用和商店列表必须遵守开发者计划政策和美国出口法律。
- 使用 Google Wallet 支付 25 美元的注册费。如果你没有 Google Wallet 账户,可以在过程中快速设置一个。
- 你的注册获得验证后,你将收到注册时填写的电子邮件地址的通知。
创建账户后,你可以登录开发者控制台,其中有一个“添加新应用”按钮,你可以通过该按钮提交你的应用程序。
我们将通过我们的 Social Sentiment 应用来展示应用提交过程。我们已经在我们的社交集成教程中涵盖了 Twitter 登录和访问推文,并在我们的 Android 数据处理教程中涵盖了意见挖掘,因此我们将跳过解释应用的核心逻辑。然而,我们将讨论用于测试应用的每一个编程和非编程方面。
3.1 Social Sentiment 应用的核心概念

创建应用后,就可以更改图标和 Logo 了。我倾向于让两者相同,但你也可以为你的应用设计不同的图标和 Logo。我为我们的 Social Sentiment 应用创建了一个简单的图标兼 Logo。根据你的应用需求和设计选择,你可以选择填充设计或透明设计。作为传统的 Windows 开发者,
图 3.3:我们的应用 Logo
设计好 Logo 后,你现在可以上传新的图标/Logo 到你的 drawable-xhdpi 目录,并在 AndroidManifest 中将文件选为 Logo/图标选项。请记住:图标/Logo 文件名不允许使用大写字母、特殊字符(下划线除外)和空格。
图 3.4:通过 Manifest 选择图标和 Logo
构建并运行应用后,测试它作为已安装图标以及在你的 Activity 表单中作为 Logo 的外观。你可以反复修改设计过程,直到你对它在实际设备上的外观和感觉满意为止。
查看下图了解 Logo 和图标的位置。
图 3.5:应用 Logo 和图标
3.1.2 应用优化清单
应用准备好发布后,不要急于发布。在发布之前,你需要通过一些标准的检查清单。虽然这些并不重要,但对于将稳定应用发布到商店很重要。我们将讨论通用应用清单的主要要点以及我们如何完成清单。
1)检查整个工作流程是否正常运行: Social Sentiment 应用的逻辑是
a) 共享首选项、登录、身份验证和多个主屏幕 首次启动时,应用必须创建一些应用特定的共享首选项数据,包括 Twitter 访问令牌、应用版本等。所有令牌都必须是空的。第一个屏幕只能有一个选项:即登录 Twitter 的选项。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.twitter_login);
btnLogin=(Button)findViewById(R.id.btnLogin);
btnLogin.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
// Twitter login Needs to be called from here
}
});
///////////////////// After Authorizing app, browser will relaunch app with access token/////////
Uri uri = getIntent().getData();
////////////////////////////So Initialize Shared Preferences////////////////////
mSharedPreferences = getApplicationContext().getSharedPreferences(
"socialSentimentPref", 0);
if(mSharedPreferences.contains("ACCESS_TOKEN")) // Means app is already authorized
{
try{
formNo=2;
LoadForm2(); // Launch home screen which has search option
return;
}catch(Exception ex)
{
Log.e("Error!",ex.getMessage());
}
}
if(twitter==null)
twitter = new TwitterFactory().getInstance();
//////This part is called when MainActivity is relaunched with access token//////////////////////////
if(uri!=null && uri.toString().startsWith(TWITTER_CALLBACK_URL))
{
uri = getIntent().getData();
//URL_TWITTER_OAUTH_TOKEN
final String verifier = uri.getQueryParameter(URL_TWITTER_OAUTH_VERIFIER);
final String token = uri.getQueryParameter(URL_TWITTER_OAUTH_TOKEN);
try {
// requestToken=new RequestToken(token, verifier);
// Get the access token
MainActivity.this.accessToken = twitter.getOAuthAccessToken(requestToken, verifier);
long userID = accessToken.getUserId();
User user = twitter.showUser(userID);
uname = user.getName();
screenName=accessToken.getScreenName();
/**************************** Store all details in Shared Preferences*******************/
Editor ed=mSharedPreferences.edit();
ed.putString("ACCESS_TOKEN", MainActivity.this.accessToken.toString());
ed.putString("TOKEN", accessToken.getToken());
ed.putString("TOKEN_SECRET", accessToken.getTokenSecret());
ed.putString("UID", ""+userID);
ed.putString("SCREEN_NAME", ""+screenName);
ed.commit();
// After storing all details in Shared Preferences, Home Screen will be launched///////////////////
LoadForm2();
showToast("Twitter Login Successfull.. "+screenName);
/////////////////////////////////////////////////////
}
catch(Exception ex1)
{
Log.i("Fetching Access token Error",ex1.getMessage());
}
}
///////////////////////////////////////////////////////
}
因此,我们使用 LoadForm2() 来隔离呈现主屏幕的逻辑。所以,如果应用已授权,则启动 Form2(主窗体),否则启动 Login 表单。
void LoadForm2()
{
formNo=2;
///////////////////////// Load the Layout Corresponding to Home Form//////////
parent=LayoutInflater.from(this).inflate(R.layout.activity_main, null);;
setContentView(parent);
////////////////////////////////////////////////////////////////////////////////
////////////// Initialize all other Controls of the home form////////////////////
tvMessage=(TextView)findViewById(R.id.tvMessage);
tvResult=(TextView)findViewById(R.id.tvResult);
tvResult.setText("");
btnSearch=(Button)findViewById(R.id.btnSearch);
listView1=(ListView)findViewById(R.id.listView1);
edSearch=(EditText)findViewById(R.id.edSearch);
btnSearch.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
// Searching logic must be implemented here
}
});
tvMessage.setText("Welcome "+uname+"("+screenName+")");
invalidateOptionsMenu();
}
因此,我们可以根据应用的不同状态逻辑地呈现不同的屏幕。另外,请注意 invalidateOptionMenu() 的使用。调用此方法会调用 onCreateOptionsMenu()。请注意,我们希望仅在用户登录时才向用户显示菜单选项。因此,我们仅在显示的屏幕是 form 2 时才膨胀菜单。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
if(formNo==2)
{
getMenuInflater().inflate(R.menu.main, menu);
}
return true;
}
b) 浏览器回调 用户按下按钮后,浏览器必须打开一个授权 URL,用户需要在其中授权应用。授权后,将获得新的访问令牌,浏览器将重定向到应用的第二个表单。共享首选项必须存储此令牌。
设置好应用的基本结构后,我们只需要调用我们在社交集成教程中开发的 MyTwitterLogin() 方法来设置登录过程。我们将再次强调,为了让 Twitter 能够在你成功授权后呈现你的应用,你需要指定一个回调 URL,如下所示。
static final String TWITTER_CALLBACK_URL = "x-oauthflow-twitter://callback";
你还需要通过 manifest 指定你的 MainActivity 类(因此也是 main 布局)是一个可浏览意图(可以从浏览器触发的意图),在你的 activity 标签中使用意图过滤器。所以你的 activity 看起来像下面这样
<activity
android:name="com.integratedideas.socialsentiment.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" >
<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="x-oauthflow-twitter" android:host="callback" />
</intent-filter>
</activity>
粗体和带下划线的部分是允许浏览器在成功授权后重新加载应用的特定部分。
c) 搜索 用户应该能够通过文本框搜索 Twitter。搜索结果中的每个推文都必须经过意见分析,最后必须显示搜索的整体意见。
请记住,在我们的社交集成部分,我们使用了一个简单的 String 适配器将搜索结果分配给一个列表框。然而,当你计划制作一个商业应用时,我建议你使用自定义列表视图,你可以根据需要进行自定义。
与社交集成教程不同,在这里我希望显示搜索结果、用户详细信息、转推-收藏以及应用的意见数据。所以我们将为列表框的一行设计一个自定义布局,以显示 Twitter 结果。
创建一个名为 list_row.xml 的新布局,并粘贴以下 XML 代码。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/BLACK"
android:orientation="horizontal"
android:padding="5dip" >
<!-- ListRow Left sied Thumbnail image -->
<LinearLayout
android:id="@+id/thumbnail"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_alignParentLeft="true"
android:layout_marginRight="5dip"
android:padding="3dip" >
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/list_image"
style="@dimen/activity_horizontal_margin"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="top"
android:contentDescription="@string/app_name"
android:maxHeight="80dp"
android:maxWidth="120dp" />
</LinearLayout>
<TextView
android:id="@+id/tvTweet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/thumbnail"
android:paddingTop="16dip"
android:paddingBottom="8dip"
android:text="tweet"
android:textColor="@color/WHITE"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvUserStat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/tvNameScreenName"
android:layout_toRightOf="@+id/tvNameScreenName"
android:layout_marginLeft="15dp"
android:textColor="#00fffa"
android:textSize="15sp"
android:text="TextView" />
<TextView
android:id="@+id/tvNameScreenName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvTweet"
android:layout_alignTop="@+id/thumbnail"
android:paddingTop="5dip"
android:text="name"
android:textColor="@color/WHITE"
android:textSize="15sp" />
<TextView
android:id="@+id/tvTweetStat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvTweet"
android:layout_below="@+id/tvTweet"
android:paddingTop="8dip"
android:text="Tweet Stat"
android:textColor="#fb0aff"
android:textSize="15sp" />
<TextView
android:id="@+id/tvPolarity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/tvTweetStat"
android:layout_alignBottom="@+id/tvTweetStat"
android:layout_toRightOf="@+id/tvTweetStat"
android:paddingLeft="16dip"
android:textSize="15sp"
android:textColor="#ff0a24"
android:text="TextView" />
</RelativeLayout>
你可以看到 tvPolarity 用于保存极性数据,tvTweetStat 用于保存转推和收藏的统计数据,tvNameScreenName 用于保存与推文相关的用户信息,tvUserStat 用于保存用户关注者数量,tvTweet 用于保存推文。
但最有趣的是一个名为
com.android.volley.toolbox.NetworkImageView
这是怎么回事?Android 提供了一个名为 Volly 的出色库,可以帮助你更有效地访问(传输和接收)网络数据。你可以从这里下载 Volly。
构建项目并复制 Volly.jar。将其放入项目的 lib 文件夹并重新构建项目,你就可以开始使用 Volly 了。该库提供了几个用于网络相关操作的强大工具。
正如你所理解的,我们想显示推文发布者的图片(在 Twitter 俚语中通常称为 DP)。然而,为每次搜索抓取图片非常耗费资源,你需要某种缓存来简化这个过程。所以我们将使用 LruBitmapCache.java 来执行位图图像缓存。
public class LruBitmapCache extends LruCache<String, Bitmap> implements
ImageCache {
public static int getDefaultLruCacheSize() {
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
return cacheSize;
}
public LruBitmapCache() {
this(getDefaultLruCacheSize());
}
public LruBitmapCache(int sizeInKiloBytes) {
super(sizeInKiloBytes);
}
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
@Override
public Bitmap getBitmap(String url) {
return get(url);
}
@Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
现在,我们终于需要一个适配器类来保存列表项的数据。
名为 TweetAdapter 的类必须扩展 BaseAdapter,并应包含一个 twitter4j.Status 列表。当请求视图时,它应该将此列表中的一个元素格式化为列表框的一行。
public class TweetAdapter extends BaseAdapter
{
public ArrayList<twitter4j.Status> tweetData;
Context context;
LayoutInflater inflater;
int sumScore=0;
ImageLoader imageLoader = AppController.getInstance().getImageLoader();
public TweetAdapter()
{
}
public TweetAdapter(Context context,ArrayList<twitter4j.Status>searchResult)
{
this.tweetData=searchResult;
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.context=context;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return tweetData.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return tweetData.get(arg0);
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
ViewHolder holder;
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(position==0)
{
sumScore=0;
}
View vi=convertView;
if(convertView==null)
{
Log.i("Tracking view", "-----Scrolling---------");
vi = inflater.inflate(R.layout.list_row, null);
holder = new ViewHolder();
holder.tvName = (TextView)vi.findViewById(R.id.tvNameScreenName); // city name
holder.tvUserStat = (TextView)vi.findViewById(R.id.tvUserStat); // city name
holder.tvTweet = (TextView)vi.findViewById(R.id.tvTweet); // city weather overview
holder.tvPolarity = (TextView)vi.findViewById(R.id.tvPolarity);
holder.tvTweetStat = (TextView)vi.findViewById(R.id.tvTweetStat); // city weather overview
holder.tvImage =(ImageView)vi.findViewById(R.id.list_image); // thumb image
holder.thumbNail =(NetworkImageView)vi.findViewById(R.id.list_image); // thumb image
vi.setTag(holder);
}
else{
holder = (ViewHolder)vi.getTag();
}
try
{
String s=tweetData.get(position).getText();
//////////////////////////////////////
holder.thumbNail.setImageUrl(tweetData.get(position).getUser().getBiggerProfileImageURL(), imageLoader);
holder.tvName.setText("@"+tweetData.get(position).getUser().getName()+"["+tweetData.get(position).getUser().getScreenName()+"]");
holder.tvTweet.setText(s);
int followers=tweetData.get(position).getUser().getFollowersCount();
int retweets=tweetData.get(position).getRetweetCount();
holder.tvUserStat.setText("Followers:"+followers+" Follows:"+tweetData.get(position).getUser().getFollowersCount());
holder.tvTweetStat.setText("Retweets:"+retweets+" Favourites:"+tweetData.get(position).getFavoriteCount());
////////////////////////////////////////////////////////////////////////////
// Opinion polarity logic for variable s
int score=OpnionLogic(s);// here opinion logic is just some method which needs to be replaced by our
// Actual opinion polarity method
/////////////////////////Change the color of Text view based on Opinion////////////////
if(score>0)
{
holder.tvPolarity.setText("[Positive "+score+" ]");
holder.tvPolarity.setTextColor(Color.GREEN);
}
else
{
if(score<0)
{
holder.tvPolarity.setText("[Negative "+score+" ]");
holder.tvPolarity.setTextColor(Color.RED);
}
else
{
holder.tvPolarity.setText("[Neutral "+score+" ]");
holder.tvPolarity.setTextColor(Color.BLUE);
}
}
///////////////// Download DP image asynchronously////////////////////////////////////////////////////////
ImageDownloader(imvs[position]).execute(tweetData.get(position).getUser().getBiggerProfileImageURLHttps());
}
catch(Exception ex)
{
}
// TODO Auto-generated method stub
return vi;
}
static class ViewHolder{
NetworkImageView thumbNail;
TextView tvName;
TextView tvTweet;
TextView tvUserStat;
ImageView tvImage;
TextView tvTweetStat;
TextView tvPolarity;
}
class ImageDownloader extends AsyncTask<String, Void, Bitmap> {
ImageView bmImage;
public ImageDownloader(ImageView bmImage) {
this.bmImage = bmImage;
}
protected Bitmap doInBackground(String... urls) {
String url = urls[0];
Bitmap mIcon = null;
try {
InputStream in = new java.net.URL(url).openStream();
mIcon = BitmapFactory.decodeStream(in);
} catch (Exception e) {
Log.e("Error", e.getMessage());
}
return mIcon;
}
protected void onPostExecute(Bitmap result) {
bmImage.setImageBitmap(result);
}
}
}
d) 对搜索结果进行意见挖掘: 请注意,列表框视图是临时的。只有你看到的屏幕部分才会被创建并呈现。你想在列表框的每一行中显示推文的极性。因此,极性必须在适配器类创建视图时计算。因此,当您向下滚动或向上滚动时,它必须更新。分数必须在创建第一行时(即 position=0)重置为 0,并在显示所有行时解释最终分数。
现在替换
int score=OpnionLogic(s)
为以下代码
if(position==0)
{
sumScore=0;
}
int score=((MainActivity)context).mp.SimpleMine(s);
score=score*(retweets+1);
followers=followers/1000;
sumScore=sumScore+score;
if(position>=tweetData.size())
{// Put the final score interpretation
((MainActivity)context).tvResult.setText("Result Score of Opinion mining is "+sumScore);
}
你可以再次更改最后一行以适应你想要的显示。
e) 分享 用户必须能够分享搜索结果,在这种情况下,它必须被转换为图像,暂时保存,然后允许用户分享图像。
请记住,当用户成功登录时,菜单将被膨胀,其中包含“分享”选项。一旦点击分享,窗体的内容就必须转换为位图,并以时间戳生成的文件名保存,以避免歧义。
图像创建后,必须触发一个分享意图,并在意图的 extra 字段中包含文件名。
case R.id.menuShare:
///////////////////////////////////////////////////// Generate File name///////////////////
String filePath=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath()+"/";
String fileName = new SimpleDateFormat("yyyyMMddhhmm'.jpg'").format(new Date());
String fname=filePath+fileName;
/////////////////////////////////////////////////////////////////////////////////////////////
// Generate bitmap from View//////////////
Bitmap b=loadBitmapFromView(parent);
//////////////////////////////////////////
try
{
// Save bitmap into the file location with name as obtained above///////
OutputStream fOut = null;
File file = new File(fname);
fOut = new FileOutputStream(file);
b.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
//////////// If saving was success, trigger share menu//////////
try{
Log.d("Ok this is pressed","In the share menu");
Intent myIntent = new Intent(Intent.ACTION_SEND);
myIntent.setType("image/jpeg");
try{
myIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
}catch(Exception ex)
{
}
myIntent.putExtra(Intent.EXTRA_SUBJECT, "Social Sentiment Analysis of "+edSearch.getText().toString().trim());
myIntent.putExtra(Intent.EXTRA_TEXT, "Shared by Social Sentiment");
startActivity(Intent.createChooser(myIntent, "Share Image"));
}catch(Exception ex)
{
}
/////////////////////////////////////////////
}catch(Exception ex)
{
}
break;
其中 loadBitmapFromView() 是一个如下所示的实用方法。
public static Bitmap loadBitmapFromView(View v)
{
if (v.getMeasuredHeight() <= 0) {
v.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
Bitmap b = Bitmap.createBitmap(v.getMeasuredWidth(),v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
// v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
v.draw(c);
return b;
}
该方法首先保留相当于屏幕宽度和高度的绘图区域,然后使用 canvas API 的绘制内容到绘图区域,并将其作为位图返回。
从 LoadForm2() 方法回忆一下,parent 是实际的父视图,由布局膨胀器膨胀。
parent=LayoutInflater.from(this).inflate(R.layout.activity_main, null);;
e) 登出 用户应该能够登出,在这种情况下,应用应该清除共享首选项中的访问令牌,并向用户呈现第一个屏幕。用户应该能够从第一个屏幕再次登录。
请注意,我们在此未提供任何货币化逻辑。那是因为你的应用首先需要完美地完成其应有的功能。
2)应用必须正确启动: 应用安装后必须正确启动。由于你将创建和检查多个共享首选项,因此应用可能会失败。一旦核心逻辑完成,请将 apk 从项目的 bin 目录复制到你手机/平板电脑的任何目录,如 download。在设置中启用“可安装外部 apk”。卸载任何以前安装的版本,然后从 apk 安装应用。从安装的图标启动应用,看看应用是否能顺利启动。应用在启动时崩溃对用户来说是一个很大的打击。此外,应用启动必须快速,不得让用户等待过久。如果你想在开始时获取某些资源,请向用户显示关于等待的适当消息。
一旦你的应用启动并成功完成整个工作流程,就可以进行测试了。
3)测试所有可能的条件: 考虑用户输入和其他可能的场景。例如,在浏览器中显示 URI 后,如果互联网连接变得不可用怎么办?如果互联网连接可用但速度非常慢怎么办?如果用户在会话已销毁后大约 10 分钟才按下浏览器中的“授权应用”按钮怎么办?尝试创建场景,并使应用在所有这些测试条件下都具有响应性。
4)UI 响应性: 对于现代应用来说,这是另一个至关重要的问题。你的许多应用选项可能非常消耗资源,完成起来可能需要很长时间。在此期间,不要阻塞你的 UI。尽可能使用异步执行。使用进度条、进度环和动画让用户知道某些任务正在进行中。切勿从主线程调用资源密集型线程。如果能实现这些步骤,用户肯定会喜欢你的应用。
请记住,我们在第 1)点中介绍了我们应用逻辑,其中我们实际上将按钮单击事件留空了。我想在此子标题下涵盖这些主题,以展示如何确保响应性。
让我们先以 TwitterLogin 为例。当您单击按钮时,首先会根据请求令牌创建一个 URI,并打开一个带有该 URI 的浏览器选项卡。此过程大约需要 1.5 秒到 2 秒。但是,如果您直接从按钮单击中调用了 MyTwitterLogin() 方法,用户可能会因为没有响应而感到困惑,并可能多次按下同一个按钮。因此,整个过程现在嵌入到一个异步任务中。在 preExecute 中,我们向用户显示适当的进度环,让他们知道一些操作已开始。任务启动时,进度环会消失,最后会通知用户任务成功或失败。
class TweetLoginAsync extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
//hrd.start();
try
{
MyTwitterLogin();
}catch(Exception ex)
{
}
return null;
}
@Override
protected void onPostExecute(Void result)
{
ringProgressDialog.dismiss();
}
@Override
protected void onPreExecute()
{
ringProgressDialog.show();
btnLogin.setVisibility(View.INVISIBLE);
}
}
最后,从 onCreate 中初始化的 btnLogin 的 onClick 方法中,我们异步调用 Twitter 登录。
btnLogin.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
ringProgressDialog = new ProgressDialog(MainActivity.this);
ringProgressDialog.setCancelable(true);
ringProgressDialog.setMessage("Preparing for Login :) ");
ringProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
ringProgressDialog.setIndeterminate(true);
ringProgressDialog.show();
TweetLoginAsync tla=new TweetLoginAsync();
tla.execute();
}
});
我们对搜索也使用相同的异步任务逻辑。
class TweetSearchAsync extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
//hrd.start();
try
{
tweets=new ArrayList<twitter4j.Status>();
try{
Query query = new Query(edSearch.getText().toString().trim());
query.setCount(100);
list = twitter.search(query).getTweets();
for (int i=0;i<list.size();i++)
{
twitter4j.Status each = (twitter4j.Status) list.get(i);
tweets.add(each);
}
adapter = new TweetAdapter(MainActivity.this ,tweets);
listView1.setAdapter(adapter);
}catch(Exception ex){}
}catch(Exception ex)
{
}
return null;
}
因此,在后台,我们生成查询并将结果收集到一个列表中,该列表传递给适配器构造函数以初始化适配器,然后加载适配器作为列表适配器来显示结果。
为了执行意见挖掘,我们需要来自服务器的数据。但是我们不会为每次搜索获取意见表。相反,我们将数据下载到本地表中,并且仅在数据可用后触发搜索。因为搜索结果必须利用意见挖掘。
所以我们从 postExecute 中的另一个异步任务调用 Search 选项,在数据可用后。
class OpinionDatabaseFetchAsync extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
//hrd.start();
try
{
///////////////////// If opinion data not available, call soap and get this data
if(datas==null)
{
CallSoap cs=new CallSoap();
webResult=cs.GetSentiData();
}
////////////////////////////////////////////////////////////////////////
}catch(Exception ex)
{
}
return null;
}
@Override
protected void onPostExecute(Void result)
{
ringProgressDialog.dismiss();
/////////////////////// If for first time we come here after soap, deserialize soap result//
if(datas==null)
{
String [] rows=webResult.split("\n");
String [][]datas=new String [rows.length][2];
for(int i=0;i<rows.length;i++)
{
datas[i]=rows[i].split("#");
}
mp=new MinePolarity(datas);
}
///////// Opinion Database is With Us. Go for search///////////
TweetSearchAsync tsa=new TweetSearchAsync();
tsa.execute();
//////////////////////////////////////////////////////////////
}
@Override
protected void onPreExecute()
{
ringProgressDialog=new ProgressDialog(MainActivity.this);
ringProgressDialog.setTitle("Preparing Senti Analysis System");
ringProgressDialog.show();
btnLogin.setVisibility(View.INVISIBLE);
}
}
最后,这是我们如何通过在按钮的 onClickListener 中触发获取意见数据来触发搜索过程。
btnSearch.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
adapter=new TweetAdapter();
listView1.setAdapter(null);
OpinionDatabaseFetchAsync odfa=new OpinionDatabaseFetchAsync();
odfa.execute();
// TODO Auto-generated method stub
}
});
现在,我的意思是测试,你可以通过替换上面代码中的这两行来找出
adapter=new TweetAdapter();
listView1.setAdapter(null);
没有这两行,第二次搜索时应用就会崩溃。这是因为新查询的结果行数不同,因此重新初始化适配器很重要。但是当你这样做时,适配器可以呈现任何视图。所以应用崩溃了。因此,listView 适配器必须设置为 null。
你可能还会注意到,在你点击按钮后,屏幕键盘仍然存在,这会让用户感到非常恼火,因为他必须手动最小化它。在 btnSearch 的 onClickListener 中添加以下行,以便在按钮单击后尽快最小化键盘。当你点击文本框进行文本搜索时,它会再次弹出。
InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
5) 布局独立运行: 请记住,每当更改设备方向时,Android 都会调用 onCreate 方法。设备的状态可以保存在 bundle 中,并从 bundle 中恢复。然而,在许多应用中,如 YouTube 视频查看器,或在我们的例子中,应用不仅有要保存的视图,同时还有网络状态。所以,如果你正在进行搜索过程以获取数据,你不会希望在更改方向时一切都被重置。使用一个简单的 Manifest 技巧,你可以确保在更改方向时你的应用也能正常运行。只需像下面这样修改 Manifest 中的 activity 标签即可看到此效果。现在,当你改变方向时,你的布局会改变,但数据不会被销毁。
<activity
android:name="com.integratedideas.socialsentiment.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" >
3.2 应用发布
完成所有编码、优化和测试后,你终于可以发布你的应用了。然而,在发布之前还有一些事情需要注意。让我们再次制作一个清单,逐一执行任务。
3.2.1 创建签名 APK 在开发项目时,它被配置为构建模式,需要更改为发布模式,并必须用密钥签名以防止任何篡改。
要生成应用的签名发布版本,请右键单击你的 Eclipse 项目,选择 Android Tools,然后选择 Export Signed Application Package ,如下图 3.6 所示。
图 3.6:准备签名和发布 APK
假设你第一次使用此选项,我们将继续创建一个新的密钥库。在应用签名和导出屏幕上选择下一步,这样你就会看到 3.7 屏幕。在这里,你必须选择 'Create New Keystore' (创建新密钥库) 选项,并提供一个你可以记住的密钥库位置。设置一个你能够记住的强密码。
3.7:创建新密钥库
提供你的详细信息以及你在上一屏幕使用的密码,如 3.8。
图 3.8:填写创建新密钥库的详细信息
请避免在字段中使用 ,-#$ 以及任何其他特殊字符。否则,你可能会得到无效的 ava 格式。
完成这些步骤后,你将在图 3.7 中指定的文件夹中看到签名的 apk 文件。现在是时候将 apk 上传到 Google Play 商店了。
3.2.2 完成商店列表 登录 Google Play 开发者控制台 。你已经 注册了你的应用名称。现在选择该应用名称,并首先完成商店列表,如下图 3.9 所示。
图 3.9:完成应用的商店列表
完成文本数据后,该是完成图形素材的时候了。为了成功完成你的资源,你需要
1) 至少为手机提供一张截图,为 7 英寸平板电脑提供一张截图。
2) 高分辨率图标(你为项目设计的图标)
3) 特色图形
这些图像各有其尺寸规格。如果你的图像不符合要求,系统会提示你上传不同尺寸的图像。你可以使用任何照片调整软件来进行更改。要在 Eclipse 中生成截图,你可以点击 DDMS 标签,然后点击相机图标。这就是我为本文拍摄所有截图的方式。
图 3.10:从 Eclipse 生成应用截图
如果你确信你的应用在手机上也看起来一样,那么你可以上传相同的图片集用于手机和平板电脑。此外,别忘了重新排列图片,使其反映应用的工作流程。
特色图形是将在商店中显示的图形。你可以将其中一张截图作为特色图形,或者最好为应用设计一个简单有效的横幅。
图 3.11 将让你了解每个字段对最终列表的含义。
图 3.11:图形和文本信息如何组合成商店列表
现在你可以根据图 3.11 来规划你的图形和文本信息了。
3.2.3 上传 APK 并发布 我们已经导出了签名 APK。现在是时候从开发者控制台中应用的 Apk 标签上传它了。你所要做的就是点击开发者控制台中你的应用名称下的 Apk,然后将签名 APK 拖放到提示上传生产 APK 的框中。
图 3.12 上传生产 APK
现在,你可以通过点击右上角显示“aqpp 准备发布”的链接来发布应用,如下图所示。
图 3.13:准备发布消息
如果缺少任何必要信息,此按钮将不可用。相反,它会告诉你为什么无法发布。
一旦你发布了你的应用,你就会在开发者控制台主页上看到应用详细信息从“草稿”更改为“已发布”。
图 3.14 开发者控制台中应用发布确认
应用发布后,需要一些时间才能在 Google Play 上反映出来。你可以通过以下 URL 检查 Google Play 列表:
https://play.google.com/store/apps/details?id=YOUR_APP_FULL_PACKAGE_NAME
其中,**YOUR_APP_FULL_PACKAGE_NAME** 在这里是 **com.integratedideas.socialsentiment**。
你可以在发布后约半小时刷新此 URL,你的商店列表将可见。始终建议从设备上的链接下载应用,以查看应用是否能正确安装和启动。在执行此操作之前,请不要忘记卸载开发和测试应用时设备上的调试版本。
3.3 更新应用
即使在处理了崩溃和测试用例之后,你可能仍会在应用发布后遇到麻烦。另一方面,你可能希望集成最初版本中遗漏的功能。所以现在你需要更新应用。要更新,你只需上传新的 APK。新的 APK 必须具有不同于之前上传的应用程序版本和版本名称。你可以从 Manifest 文件中更改版本,如下所示。
图 3.15 修改后更改应用版本
这个新版本也必须像我们的第一个版本一样进行构建、测试和导出。导出前,不要忘记删除驱动器上先前导出的应用。Eclipse 并不总是会覆盖导出的 APK。
一旦你选择发布新的 APK,旧的 APK 将被自动取消发布,新的 APK 将可用。
图 3.16:更新你的应用
你不必在应用更新后明确通知你的客户。已安装应用的用户设备将自动更新,这将由设备上的 Google Play 负责。
现在请理解,应用更新不可避免地会触发下载新版本应用,这意味着额外的带宽。客户非常关心通过更新反复下载相同数据而浪费他们的数据计划。因此,你必须规划你的 APK,以便那些很少更改的图形素材从外部源下载,而不是与新 APK 一起下载。这样,你就可以限制下载大小,你的客户一定会喜欢使用新的应用版本。
4. 应用评分
你的应用的受欢迎程度取决于应用商店中的评分和评论。越多的人喜欢你的应用,它就会越受欢迎,并会排在竞争对手应用的前面。让用户给你的应用评分并不容易,因为这需要去商店进行评分。安装应用后,用户几乎不会回到商店去评分(除非他们真的对应用非常满意,无论是积极的还是消极的)。
因此,始终建议你的应用具备某种应用评分引擎,该引擎会在使用一定次数或经过一定天数后提示用户对应用进行评分。这让用户有机会表达他们对应用的喜爱,同时也为你的应用在应用商店中普及提供了机会。
有一个非常酷的开源库(或者更确切地说,一个类文件),由 Chris Hager 编写,用于为你的应用评分,可以从这里下载:这里。
这是一个非常简单的类,它带有自己的共享首选项,可以通过这些首选项记住最后显示的时间、用户对对话框的偏好等等。
在我的例子中,我希望在每次搜索结果后弹出评分器。所以我把以下代码行放在了 TweetAdapter 中。你可以自由选择何时以及以何种频率显示评分消息。
AppRater appRater = new AppRater(context);
appRater.setDaysBeforePrompt(0);
appRater.setLaunchesBeforePrompt(0);
appRater.show();
如果你是从 mainActivity 表单使用它,你可以传递 MainActivity.this 而不是 context。
setDaysBeforePrompt 在提示用户评分你的应用之前,会等待用户使用应用几天。setLaunchesBeforePrompt() 将设置用户必须启动应用的次数,然后才显示应用评分对话框。
下图 4.1 显示了结果。
4.1 应用评分对话框
当用户选择为应用评分时,他将被带到 Play 商店列表,在那里他可以评分并提交评论。
图 4.2:应用内评分结果
你也可以保留一个菜单图标并显示对话框。 在这种情况下,你可能只在用户尚未评价应用的情况下显示应用评分菜单。所以我还在 AppRater.java 中添加了一个实用方法,如下所示。
public boolean isAlreadyRated()
{
SharedPreferences prefs = mContext.getSharedPreferences(mPrefGroup, 0);
try{
return prefs.getBoolean(mPreference_dontShow, true);
}catch(Exception ex)
{
return false;
}
}
所以你可以使用 Activity 的上下文创建一个 AppRater 实例,并检查应用是否已评分,如果没有,则显示该选项。
5. 使用 Google AdMob 盈利
要使用几乎任何广告引擎,你需要注册广告引擎,下载 SDK,集成到你的应用中,然后广告就会开始出现在客户设备上的应用中。有不同类型的应用竞价:例如,基于CPI 的,基于CPA 的等等。如果你之前不知道它们是什么,那么广告商会根据浏览次数或操作次数付费。假设你正在组织一个会议,你的教育应用正在展示一个广告。那么广告商会支付费用,如果有人通过你的设备上的广告点击进入会议地点并实际注册了会议。那就是每次行动成本。它也可能涉及销售。有些应用下载量很大(每月数千次)。在这种情况下,广告商更喜欢放置 CPI/CPM 广告,其中广告商按每 1000 次展示付费。
但归根结底,成功的关键在于你的用户对你的应用的持久性。最终一切都取决于下载量和持久性(那些没有卸载你的应用的设备)。
由于或多或少所有的广告网络和移动广告引擎都以相同的方式工作,我将使用 Google Ad Engine。坦率地说,我从未在我的任何应用中使用过应用引擎,这是我第一次使用它们。那么我是如何通过我的应用生存下来的,这是一个我将尽可能作为一个单独的子部分分享的故事,作为可能的替代方案。
那么,让我们开始吧。
5.1 获取 AdMob 账户并准备 Eclipse 进行 AdMob 集成
1) 前往此链接 此处>> 创建你的 AdMob 账户。使用你的 Gmail 账户登录。
2) 我已经拥有 AdSense 和 AdWords 账户。这使我更容易,因为我的账户已经验证。所以我只需要验证我的 AdSense 和 AdWords 账户。如果你没有 AdSense 账户,Google 会要求你创建一个。(我真的不能说它需要什么信息,因为这些程序是因国家而异的。所以你应该仔细完成这个过程)。
3) **查看政策: ** 不要在不阅读的情况下点击“我同意”条款。AdSense 和 AdMob 有一些非常严格的政策,你在继续之前必须阅读并理解。每天都有成千上万的账户被封禁。所以,花点时间阅读。
4) 要使用移动广告,你无需下载任何单独的 SDK。它打包在 Google Play 服务中,与其他服务捆绑在一起。在 Eclipse 中,点击 SDK 管理器,并确保已安装 Google Play 服务,如下图所示。
图 5.1:Eclipse 中的 Google Play 服务 API
5. 将 Google Play 服务导入你的项目
你可以在你的文件夹中搜索你的 android-sdks 。它通常位于 C:\Users\<SYSTEM_USER_NAME>\android-sdks。在我的系统中,它是 C:\Users\Rupam\android-sdks。找到 android 文件夹后,打开 extras\goolgle 文件夹,在我系统中是 C:\Users\Rupam\android-sdks\extras\google。
现在将 google_play_services 文件夹复制到你的项目所在的目录。你可以右键单击任何项目,然后在属性中查看目录。
图 5.2:将 google-play-services 复制到工作空间目录
现在,通过 File > Import,选择 Android > Existing Android Code into Workspace (现有 Android 代码导入工作区) 选项,将 google_play_services 目录下的 google-play-services_lib 项目导入。请注意,不要使用 Existing Project Into Workspace (将现有项目导入工作区) (见下图)。
图 5.3:正确导入 Google Play 服务的方式
现在,你可以通过右键单击你的 Eclipse 项目 -> properties -> Android -> Library -> add 来引用你的项目到库项目,如下面的图所示。
图 5.4:从你的项目引用 Google Play 服务
6) 增加 Eclipse 的内存:现在是最棘手的部分。不幸的是,Google 的 Play 服务会泄漏大量内存。当你编译使用 Play 服务的项目时,编译可能需要很长时间,最终因内存溢出异常而失败。因此,需要通过编辑 Eclipse.ini 来更改 Eclipse 设置,如下图所示。不要忘记以管理员模式编辑文件。
图 5.5:编辑 Eclipse.ini 以帮助编译包含 Google Play 服务的应用
将你的 Eclipse.ini 备份到另一个文件夹,关闭 eclipse,像上面那样打开 eclipse.ini 并用以下内容替换代码。
-startup plugins/org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar --launcher.library plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.200.v20120913-144807 -showsplash org.eclipse.platform --launcher.XXMaxPermSize 1024m --launcher.defaultAction openFile -vmargs -Xms1024m -Xmx1024m
保存文件,关闭它,现在重新启动你的 eclipse。它已准备好编译你的 Google Play 启用的广告(特别是 MobAds)。
6) 启用 API 版本 7 兼容性: 使用 AdMob 集成的另一个重要问题是它需要 API 7 库来有效地编译你的应用。为此,只需在 Eclipse 中创建一个空白项目,并用以下 AndroidManifest.xml 文件替换 Manifest 文件。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.v7.appcompat">
<uses-sdk android:minSdkVersion="7"/>
<application />
</manifest>
构建项目,你将在 lib 目录中看到 android-support-v7-appcompat.jar 。
图 5.6: 创建与 API 7 兼容的应用
或者,你可以 下载 appcompat_v7.zip ,解压缩并导入 Eclipse 中的项目进行构建。
5.2 在 AdMob 控制台创建广告单元
你的 AdMob 账户需要几个工作日才能获得批准。一旦获批,你就可以登录控制台了。在控制台中,点击“Monetize new app”(盈利新应用),搜索你的应用列表并选择。如果你刚刚上传了 APK 并且你的应用还没有通过搜索找到,你可以选择手动选项。
图 5.7:添加新应用进行盈利
一旦你的应用准备好盈利,它就会显示在左侧面板的 All Apps (所有应用)选项卡下。点击应用。
图 5.8:创建新的广告单元
你可以通过点击“New Ad Unit”(新广告单元)按钮来创建新的广告单元。广告基本上有两种类型:横幅广告和插页式广告。插页式广告是全屏或闪屏广告,会在某些事件之前或之后出现。横幅广告可以放置在你的应用中的战略位置,或者可以通过编程触发。
一旦你的广告单元创建成功,记下广告单元 ID,它会类似于
ca-app-pub-27************82/4++++++++8 格式。
5.3 AdMob 集成到你的代码中
为了使用 AdMob API,首先你必须在你的项目中引用 Google Play 服务和 appcompat_v7。右键单击项目 -> Properties -> Android,然后通过点击右下角面板中的 add 选项来添加这两个项目。
图 5.9:设置 AdMob 集成的项目引用
我们还将使用一个名为 ToastAdListener 的实用类,该类可以在 android-sdks\extras\google\admob_ads_sdk 中找到。
你可能还想查看同一项目中的所有 AdMob 集成示例。
现在我们在 LoadForm2() 方法中添加以下行,以便仅在第二个屏幕上显示广告。
av = (AdView) findViewById(R.id.adView);
av.setAdListener(new ToastAdListener(this));
RelativeLayout layout = (RelativeLayout) findViewById(R.id.main_layout);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
String androidId = "" + android.provider.Settings.Secure.getString(this.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
Log.i("AdRequest.Builder.addTestDevice",androidId);
AdRequest adRequest=null;
try{
adRequest = new AdRequest.Builder()
.addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
.addTestDevice(androidId)
.build();
av.loadAd(adRequest);
如你所见,广告是通过 AdRequest 对象拉取的。你需要授权该对象以在模拟器和真实设备上拉取广告。你需要将加密的设备 ID 传递给 adRequest 对象,该 ID 可以通过 Secure.getString() 方法获得。
一旦拉取了广告,它就会显示在名为 adView 的控件中。这是我们包含显示应用占位符的 XML 布局文件。
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/btnSearch"
ads:adSize="BANNER"
ads:adUnitId="ca-app-pub-27---------------82/4----------8" >
</com.google.android.gms.ads.AdView>
指定正确的 adSize 和 adUnitId 非常重要。没有它们,广告将无法获取。
最后,这是我们的 GUI 视图设计模式,可以让你了解我们正在做什么。
图 5.10:应用 Social Sentiment 的 GUI
即使成功执行了所有这些步骤,在构建应用时也可能会遇到错误。这是因为 Google Play 服务存在固有的问题。在构建和运行应用之前,另一个需要检查的重要因素是项目属性下的 Java Build Path(参见图 5.9)。请参考下图,确保你的应用的设置与下图完全相同。
当我将 AdMob 集成到我的应用中时,我遇到了很多问题,尤其是在 Eclipse 内存、构建顺序和库方面。如果你遵循 Android Developer 网站上的说明直接导入 Google Play 示例,99.99% 的情况下你将无法成功集成。
逻辑上,你应该将 appcompat_v7 保存在 Android_dependencies 中,以及 google-play-services。
Android 私有库应该包含 appcompat_v7 以外的所有 jar。Java Build Path 不应包含 Google APIs 版本 4.0。并且你的项目的构建目标必须是 Google APIs(图 5.9)。为什么会有这种奇怪的配置,超出了任何逻辑解释。只有 Google 才能解释这种奇怪之处。
图 5.11:应用的 Java Build Path 设置
然而,还有一件重要的事情需要包含。你需要在 Manifest 文件中的 activity 标签之前添加以下 meta description。
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
如果一切顺利,你将看到图 5.12 所示的结果。
图 5.12:带有 Ad 集成的应用截图
6. 使用 Google Analytics 跟踪应用统计信息
像往常一样,第一步是为应用生成跟踪密钥。为此,你只需点击 AdMob 控制台中的 Analyze(分析)标签,如下图所示。
图 6.1:创建新应用进行跟踪
一旦出现新应用创建窗口,选择你的应用,就像你在图 5.7 中所做的那样,或者提供确切的包名:com.integratedideas.socialsentiment,然后完成接下来的两个步骤。你将获得一个跟踪 ID,例如
UA-5******0-2。 保存此 ID。你需要在代码中提供此 ID 进行跟踪。
图 6.2:完成新应用的应用跟踪并获取跟踪 ID
在进行编码之前,将 android-sdks\extras\google\analytics_sdk\libGoogleAnalytics.jar 复制到你的项目 lib 文件夹。
在类中声明一个 GoogleAnalyticTracker 对象。
GoogleAnalyticsTracker tracker;
在 onCreate 方法中初始化跟踪器。
tracker = GoogleAnalyticsTracker.getInstance();
// Start the tracker in manual dispatch mode...
tracker.startNewSession("UA-5****-2", this);//replace UA-5****-2 with your tracking ID
现在,你想跟踪的任何页面/activity/表单/事件,都从该部分调用 tracker.trackPageView("SomeName")。
我用它来跟踪 Twitter 登录活动,将跟踪代码合并到 btnLogin 的 onClickListener 中。
tracker.trackPageView("/TwitterLogin");
同样在 btnSearch 的 onClickListener 中。
tracker.trackPageView("/Searching");
但是,跟踪器必须在你关闭表单之前进行分派。 因此,重写 Activity 类的 onDestroy 方法,并在此时分派跟踪器。不要忘记在分派之前调用 stopSession。
@Override
protected void onDestroy() {
super.onDestroy();
// Stop the tracker when it is no longer needed.
try{
tracker.stopSession();
tracker.dispatch();
}catch(Exception ex)
{
}
}
现在构建并调试你的应用。当你进入你的应用时,你将在分析窗口中看到新用户。
图 6.3:在分析跟踪中查看新用户
7. 应用内计费
应用内计费可能是所有应用商店中最流行的移动应用盈利工具。这是支持 Freemium 模式的最佳商业模式之一。因此,用户下载功能受到一定限制、支持广告的版本。他使用应用并喜欢它。他想要完全无广告的环境,所以他购买完整版应用,或者他可以购买免费版本中缺失的一些额外功能。
Google 的应用内计费流程有些复杂。但更具问题性的是 Google 复杂的流程,而是它存在一些 bug,有时会使这个模块难以正常工作。但一如既往,我们将 hack 掉其中一些缺点,并让我们的应用正常工作。
在这里,我想强调我们 Social Sentiment 应用的策略。免费版应用每天只允许用户进行 10 次搜索。共享首选项将跟踪搜索次数和日期。当用户超出应用限制时,它会向用户提供升级到完整版本的选项。选择并购买完整版本后,用户可以进行任意次数的搜索。你可以想到几种策略和工作流程,它们可以为你的应用带来业务。
但是,首先,让我们开始让我们的应用能够发出应用内计费请求。
7.1 为应用启用应用内计费
首先登录你的 Google Play 开发者控制台并选择你的应用。从左侧面板选择“In App Products”(应用内产品)。 你应该理想地创建一个产品,其包扩展名与你应用的包扩展名相似(参见下图)。
图 7.1:创建可购买的应用内产品
从 API 版本 3(当前版本)开始,Google Play 特别支持托管产品。托管产品是 Google Play 会记住的产品,每个用户账户/应用只支持一次购买交易。虽然 Google Play 也提供非托管产品,但这些产品必须从你的代码中显式消耗。如果你忘记这样做,将无法再次购买该产品。
订阅是一种需要定期更新的项目。
完成产品创建时,不要忘记从产品详细信息页面的右上角将产品从“-Inactive”更改为“+Active”。
产品创建成功后,你将看到如下列表。
图 7.2:应用商店的应用内计费产品列表
应用的所有应用内计费产品必须通过 API 密钥访问。
你可以通过从左侧面板选择“Services and APIs”(服务和 API)选项卡来获取此密钥。
图 7.3:获取用于应用内计费通信的密钥
7.2 为应用内计费设置 Eclipse 项目
对于计费 API,我们将使用 Google Play 服务自带的应用内计费示例。你可以在以下位置找到此示例: **android-sdks\extras\google\play_billing**。
现在执行以下步骤来设置你的项目以支持 Billing API V3。
1) 首先,将 play_billing\samples\TrivialDrive\src\com 文件夹(包含另外两个文件夹:android、example)的内容复制到 your_project_directory\src\com 文件夹。现在刷新你的 Eclipse 项目。你将看到你的 Src 文件夹结构与图 7.4 类似。
图 7.4:导入 Billing 示例 Src 后项目的 Src 结构
你将看到 IInAppBillingService.aidl ,这是一个 Android Interface Definition Language (AIDL) 文件,它定义了与 In-app Billing Version 3 服务接口。在你的 manifest 中包含“com.android.vending.BILLING”权限。
现在清理并构建你的项目。你的项目已准备好实现应用内计费。
首先,在你的 MainActivity 中声明一个 IabHelper 类的对象。
IabHelper mHelper;
请记住,IabHelper 类位于你的 src 文件夹下的 com.example.android.trivialdrivesample.util 包中。
在 onCreate 方法中初始化帮助器。
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess())
{
// Oh noes, there was a problem.
Log.d("Billing Problem", "Problem setting up In-app Billing: " + result);
}
else
{
Log.d("Billing SUCCESS", "SUCCESS setting up In-app Billing: " + result);
}
};
Billing API 3 中的每个调用都是异步的。mHelper 使用 base64EncodedPublicKey 进行初始化,如图 7.3 所示。
startSetup 会调用 Google Play 并进行通信。当你构建并运行应用程序时,如果显示如图所示的计费成功,则表示你的应用已准备好进行应用内计费。
但现在真正的麻烦开始了。在实现/测试 IAB 之前,你应该记住以下几点。
必备组件
- 打开声明 AndroidManifest 必须包含“com.android.vending.BILLING”权限。
- APK 以发布模式构建。
- APK 已用发布证书签名。
- APK 已至少上传一次到开发者控制台的 alfa/beta 分发渠道(以前是草稿状态)(需要一些时间约 2 小时 - 24 小时)。
- IAB 产品已发布。
- 测试账户已添加到开发者控制台。
测试要求
- 测试 APK 的 versionCode 与上传到开发者控制台的相同。
- 测试 APK 与上传到 dev.console 的证书签名相同。
- 测试账户(非开发者)是设备上的主账户。
这意味着,在此阶段,您必须拥有另一个 Google 帐号(而不是您登录 Google Play 所用的帐号)。在我们的应用内测试完成后,我们需要使用该帐号来测试 apk。此外,在开发 IAB 概念后,应用程序必须上传以进行 Beta 测试。请记住,Google 有时需要大约一天的时间才能在 Beta 版中反映新的 APK。
在我们进一步探讨之前,我们还需要了解一些 IAB 的逻辑。
1) 辅助程序应首先调用 queryInventory 以获取可购买产品的列表。
mHelper.queryInventoryAsync(false,new QueryInventoryFinishedListener() {
@Override
public void onQueryInventoryFinished(IabResult result, Inventory inv)
{
// TODO Auto-generated method stub
String s=result.getMessage();
Log.i("purchase",s);
}
}) ;
2) 可以在用户界面中将此库存列表显示为列表,或者如果您有有限的产品(就像我们一样),您可以直接通过名为 SKU 的产品代码发起购买。 Android 提供了一个名为“android.test.purchased”的伪造产品,可以(伪造地)从任何应用程序购买。因此,这是实现 IAB 逻辑的一种很棒的方式。一旦逻辑成功,您就可以用您的实际 SKU 替换此 SKU。
3) 即使是伪造的 SKU,购买后也应将其消耗掉,否则将无法再次购买。用于购买
mHelper.launchPurchaseFlow(this, ITEM_SKU, 10001, mPurchaseFinishedListener, "mypurchasetoken");
4) mPurchasedFinishedListener 必须在 onCreate 中初始化(如果 IAB 成功)。购买后,我们必须消耗产品以使购买项目再次可用。每次购买都带有哈希签名。但 Google 没有告诉你的是,伪造购买的签名永远与真实购买的签名不匹配。因此,当您使用 android.test.purchased 测试应用程序时,签名将失败,您将无法消耗,也无法通过对该伪造项目的任何进一步购买来测试您的应用程序。
因此,我更新了一些逻辑,我将逻辑设置为:如果 purchase 不为空(这意味着已完成)且只有签名验证失败,那么也认为购买成功。
成功后,我们将应用程序版本更改为完整模式。但同时您需要消耗此购买。这可以通过调用 mHelper.consumeAsync(purchase, mConsumeFinishedListener) 来完成。
mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
boolean success=false;
if(result.getMessage().contains("Signature verification failed")&&purchase!=null)
{
success=true;
String data="";
data=mSharedPreferences.getString("APP_MODE", data).trim();
Editor ed=mSharedPreferences.edit();
ed.putString("APP_MODE", "FULL");
ed.commit();
}
if (!success)
{
// purchase=new Purchase(base64EncodedPublicKey, base64EncodedPublicKey, base64EncodedPublicKey);
String purchaseToken = "inapp:"+getPackageName()+":"+ITEM_SKU;
try
{
int response = mHelper.mService.consumePurchase(3, getPackageName(),purchaseToken);
Log.i("Response",""+response);
}
catch (RemoteException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return;
}
if(success)
{
String data="";
data=mSharedPreferences.getString("APP_MODE", data).trim();
Editor ed=mSharedPreferences.edit();
ed.putString("APP_MODE", "FULL");
ed.commit();
invalidateOptionsMenu();
}
if (purchase.getSku().equals(ITEM_SKU))
{
mHelper.consumeAsync(purchase, mConsumeFinishedListener);
// buyButton.setEnabled(false);
}
}
};
mConsumedFinishedListener 如下所示:
mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
if (result.isSuccess())
{
String data="";
data=mSharedPreferences.getString("APP_MODE", data).trim();
Editor ed=mSharedPreferences.edit();
ed.putString("APP_MODE", "FULL");
ed.commit();
}
else
{
// handle error
}
}
};
}
// Hooray, IAB is fully set up!
}
});
设置好 IAB 后,我们来测试一下。当您点击购买无限搜索的菜单选项时,您将看到伪造的购买屏幕,如下所示。
图 7.5:伪造购买
成功购买后,您将看到购买成功屏幕,如下所示。
图 7.6 伪造购买成功
最后,是时候用我们的实际 SKU 替换伪造 SKU 了。
将 ITEM_SKU 值替换为“com.integratedideas.full_version_social_sentiment" 构建并发布签名的应用程序。您无法从自己的帐户中进行测试。因此,请从开发者控制台的“设置”->“用户帐户”中添加另一个 Google 帐户作为 Beta 测试人员。邀请。从其他帐户接受邀请。为 Beta 测试上传新版本,并将 Beta 测试 APK 提升为生产版本。
通过“设置”->“帐户”->“添加帐户”在手机中添加另一个 Gmail 帐户。使用新用户的帐户登录手机。安装应用程序并通过新帐户测试 IAB。注意:为了测试,您仍然需要输入信用卡详细信息。但是,从测试帐户进行的交易不会产生任何真实的财务交易(这意味着您不会损失任何金钱)。
8. 结论
编码、构建和发布应用程序只是商业移动应用业务的第一步。如果您想在应用市场中取得成功,您需要更多的可见性。随着您的应用程序在搜索结果中的排名上升,可见性也会增加。要使应用程序出现在您正在定位的细分市场或利基市场的搜索结果顶部,您需要学习一些搜索引擎优化 (SEO) 技能。这包括尽可能多地从网页指向应用程序页面。您可以通过在不同网站上撰写应用程序评论、请求应用程序评论者评论您的应用程序等方式来实现。SEO 必须是持续不断的努力,就像编码一样。另一个有趣的 SEO 工具是 YouTube。与博客和文本网站相比,在 YouTube 上获得更高的排名相对容易。因此,请始终考虑制作您的应用程序的视频并将其发布到 YouTube。您还可以从您的 YouTube 页面链接到应用商店页面。我只用了大约三天的时间来完成应用程序、发布它并将其货币化。由于缺乏时间进行应用程序开发,肯定会存在缺陷和测试问题。但是,我已尽力构建一个现实的实用应用程序,其中包含了商业应用程序应具备的大多数重要和常见的功能。本文还提供了应用程序发布和优化清单。使用实际示例的原因是为了帮助您通过实际应用程序理解概念。我不是应用创业者,也不靠应用市场谋生。因此,还有其他成功人士,他们的处境比我更好,能够告诉您成功的秘诀。但是,本教程的目标不是指导您成为一个成功的应用程序发布者,而是帮助您成为 Android 市场中的应用程序发布者。一旦您对在 Android 市场发布应用程序所需的内容有了全面的了解,您就可以运用您的技能来取得成功。当您最终成为一名鼓舞人心的应用程序发布者时,请不要忘记在这里分享一两个技巧,让我们从您的成功中汲取灵感。祝你好运。