2016年8月30日星期二

关于GCM的 非常也用的一个帖子

准备工作

翻墙

  先翻墙,翻不了墙一切都白搭……

Google账号

  • 申请Google账号
  • 进入Google开发管理台
  • 创建工程(Google管理台上的Project)
  • 开启Google Cloud Messaging API。


Demo工程

  参考google官方指导文档,在google中搜索GCM,或者直接点击此处打开。本文均以Android为例,打开页面后,点击左上方的”TRY IT ON ANDROID”按钮,进入针对安卓的指导页。以下步骤官方指导写的比较详细的,本文就不赘述,一笔带过,有需要注意的会补充。
  1. 下载Android示例工程 
      下载下来是个压缩包,GCM的工程目录为google-services-master/android/gcm,简单介绍下各个类的作用:
    • MyGcmListenerService.java 
      GCM接收监听服务类,接收GCM发过来的通知消息,并显示到手机状态栏中。 
      对应的AndroidManifest.xml配置如下: 
      <!-- [START gcm_receiver] -->
      <receiver
        android:name="com.google.android.gms.gcm.GcmReceiver"
        android:exported="true"
        android:permission="com.google.android.c2dm.permission.SEND">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
            <category android:name="gcm.play.android.samples.com.gcmquickstart"/>
        </intent-filter>
      </receiver>
      <!-- [END gcm_receiver] -->
      <!-- [START gcm_listener] -->
      <service
        android:name=".MyGcmListenerService"
        android:exported="false">
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE"/>
        </intent-filter>
      </service>
      <!-- [END gcm_listener] -->
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    GcmReceiver接收,然后发送给GcmListenerService,我们在GcmListenerServcie的实现类中处理收到的gcm消息。Google官方对GcmReceiver的解释(原文链接):
    WakefulBroadcastReceiver that receives GCM messages and delivers them to an application-specific GcmListenerService subclass.
    如何将通知消息展示到状态栏:
    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_stat_ic_notification)
                .setContentTitle("GCM Message")
                .setContentText(message + count)
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setContentIntent(pendingIntent);
    
    NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    
    int notificationId = Integer.parseInt(("" + System.currentTimeMillis()).substring(10));
    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • RegistrationIntentService.java 
      向GCM服务器进行设备注册,获取注册token,该token会作为通知消息接收设备的标识,该类中还进行了topics的订购。
    • MyInstanceIDListenerService.java 
      接收token更新通知,收到通知后会重新通过RegistrationIntentService获取新的token。
    • GcmSender.java 
      推送消息发送类,通过该类向GCM发送HTTP消息,GCM再推送给终端设备。该类并不是必需的,下文也提到了,可以通过Postman等工具直接发送HTTP消息。 
  2. 下载配置文件。 
      下载过程中会给出API Key和Sender ID。(没记住也没关系,管理台上也可以查到,分别对应API Key和Project number)
  3. 将配置文件放入工程
    注意,工程下载好后,里面默认代码使用的API Key以及Sender ID需要修改。
    API Key: GcmSender.java(模拟服务器向Google发送推送消息使用,后面可以不用main函数,直接用浏览器插件发消息的方式,更方便点。)
    Sender ID:RegistrationIntentService.java(代码默认使用的是”R.string.gcm_defaultSenderId”)。
  4. 安装并运行示例App 
      工程打开时会向Google注册,并获取注册Token,logcat日志中会打出来。后面发送消息时以获取到的token作为客户端设备标识。
02-23 10:39:18.709 19735-19763/gcm.play.android.samples.com.gcmquickstart I/RegIntentService: GCM Registration Token:
 dnbhEeyYCWg:APA91bH_yYRmgPsuzpC7qMKp86JV3jR5d...Iw6VvPHilRa2d9u7sW4Xs6El2S1nsqtGM4yO2vVjHv-nSs_DkF3-sdn3b...7mxrbdsyl5xb53
  • 1
  • 2
  • 1
  • 2

发送通知消息

  下载下来的工程中GcmSender.java这个类就是专门发消息的,Google的指导中有给出调用其Main函数的命令,本文推荐直接通过浏览器插件或工具发送(反正就是个HTTPS的消息,用什么发都一样,对吧)。 
  推荐”Postman”,通过Chrome网上应用店安装,有独立的应用版本。
消息内容如下: 
其中消息头中的key为API Key(Google管理台上可以查),token为上文第四步中提到的注册token。
https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "to": "/topics/foo-bar",
  "data": {
    "message": "This is a GCM Topic Message!",
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 消息2:指定发送给一个设备,to即上文提到的注册token。
https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "to": "token",
  "data": {
    "message": "This is a GCM token Message!",
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 消息3:发送给多个设备,token1、token2即上文提到的注册token。
https://gcm-http.googleapis.com/gcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
  "registration_ids": ["token1","token2"],
  "data": {
    "message": "This is a GCM token Message!",
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

2016年8月29日星期一

腾讯API 登录,获取用户

开始之前在这里吐槽一下. 腾讯文档做的太没诚意了.很多都是老版本的都没更新.
哎~ 还得一个一个找.

1.注册腾讯开发者账号,并登录账号.
  一些简单的就一笔带过.不详细讲解.因为谁都可以做.

这是开发者官方:http://wiki.open.qq.com/wiki/%E9%A6%96%E9%A1%B5
>需要注意的是 注册账号并且账号是审核阶段的时候是无法测试的.会出现 104001错误
>申请应用的时候如果是测试阶段,测试的QQ账号只能是自己在开发者里注册的QQ账号.
   所谓的测试阶段是提交审核之前.但是必须得走到上传APK文件的那一步

2.登录代码

public void tecentLogin(){
    if (mTencent==null){
        mTencent=Tencent.createInstance(getString(R.string.tencent_qq_key), getApplicationContext());    }

    if (mTecentListener!=null){
        if (!mTencent.isSessionValid()){
            mTencent.login(this, QQ_LOGIN_SCOPE, mTecentListener);        }
    }else{
        MakeContent.getInstance().ContextMake(mContext,getString(R.string.tencent_callback_hint));    }
}

IUiListener mTecentListener=new IUiListener() {
    @Override    public void onComplete(Object o) {
        MakeContent.getInstance().ContextLoggerInfo(o.toString());        JSONObject jsonObject= (JSONObject) o;        initOpenidAndToken(jsonObject);        getQQUserInfo();    }

    @Override    public void onError(UiError uiError) {
        MakeContent.getInstance().ContextLoggerInfo(uiError.errorMessage);    }

    @Override    public void onCancel() {

    }
};

下面是非常重要的一个方法. 在这里腾讯Doc里没有说明.所以我找了很久.
那就如果想要获取 QQ用户信息的话 必须对Tencent的进行初始化

public void initOpenidAndToken(JSONObject jsonObject) {
    try {
        String token = jsonObject.getString(Constants.PARAM_ACCESS_TOKEN);        String expires = jsonObject.getString(Constants.PARAM_EXPIRES_IN);        // TODO: 16. 8. 29. 개발할 때 이 openID로 가져가면 될 거 같다        String openId = jsonObject.getString(Constants.PARAM_OPEN_ID);        if (!TextUtils.isEmpty(token) && !TextUtils.isEmpty(expires)
                && !TextUtils.isEmpty(openId)) {
            mTencent.setAccessToken(token, expires);            mTencent.setOpenId(openId);        }
    } catch(Exception e) {
    }
}

3.获取用户信息

private void getQQUserInfo() {
    if (mTencent != null && mTencent.isSessionValid()) {
        IUiListener listener = new IUiListener() {

            @Override            public void onError(UiError e) {

            }
            @Override            public void onComplete(final Object response) {

                try{
                    if (response!=null){
                        //QQ정보를 가져온다                        QQInfoData qqInfoData=new Gson().fromJson(response.toString(),QQInfoData.class);                    }
                    MakeContent.getInstance().ContextLoggerInfo("onComplete:"+response);                }catch (Exception e){

                }
            }

            @Override            public void onCancel() {

            }
        };        tencentQQInfo = new UserInfo(this, mTencent.getQQToken());        tencentQQInfo.getUserInfo(listener);
    } else {
    }
}

好了,困扰我半天的代码终于解决了。

2016年8月18日星期四

toolbar中 字体与图标一起使用

1.toolbar的 menu中 使用actionLayout来指定自定义view文件. 之后自己定的view中有textview并且添加tupian(drawableLeft)

<menu xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    tools:context=".activity.MemberActivity" >
    <item android:id="@+id/like_menu"        android:title=""        android:orderInCategory="60"        app:showAsAction="always"        app:actionLayout="@layout/view_content_like"        />
    <item android:id="@+id/comment_menu"        android:title=""        android:orderInCategory="60"        app:showAsAction="always"        app:actionLayout="@layout/view_content_reply"        />
    <item        android:id="@+id/update_menu"        android:orderInCategory="80"        android:title="修改"        app:showAsAction="never"/>
    <item        android:id="@+id/delete_menu"        android:orderInCategory="80"        android:title="删除"        app:showAsAction="never"/>
</menu>


view_content_reply.xml
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    >
    <TextView        android:id="@+id/name_textview"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:paddingLeft="@dimen/dp5"        android:gravity="center"        android:drawableLeft="@mipmap/reply_icon"        android:drawablePadding="@dimen/dp3"        android:text="57"        android:textColor="@color/white"        android:clickable="true" /></LinearLayout>

之后在optioonMenu中可以使用。

@Overridepublic boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.content_menu,menu);    MenuItem item=menu.findItem(R.id.like_menu);    TextView nameTextView= (TextView) item.getActionView().findViewById(R.id.name_textview);    //좋아요 수량    nameTextView.setText("100");
    MenuItem commentItem=menu.findItem(R.id.comment_menu);    //댓글 수량    TextView commentNameTextView= (TextView) commentItem.getActionView().findViewById(R.id.name_textview);    commentNameTextView.setText("500");
    return super.onCreateOptionsMenu(menu);}

另外小提示:
toolbar的 menu中有drop图片。想对那个图片设置颜色则在style里使用 colorControlNormal

<?xml version="1.0" encoding="utf-8"?><resources>
        <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">                <!-- Customize your theme here. -->                <item name="colorPrimary">@color/colorPrimary</item>                <item name="colorPrimaryDark">@color/colorPrimaryDark</item>                <item name="colorAccent">@color/colorAccent</item>                <item name="android:actionMenuTextColor">@color/white</item>                <item name="actionMenuTextColor">@color/white</item>                <item name="android:colorControlNormal">@color/white</item>        </style></resources>


Camera 文件调用

这个在 developer里 有实例
https://developer.android.com/guide/topics/media/camera.html

但是onactivityresult返回的 intent是空的.
因为使用了.EXTRA_OUTPUT.这个的作用是.当生成图片时 会默认保存已经指定的文件里

下面是调用 camera的方法。可以看到使用了 EXTRA_OUTPUT
private Uri fileUri;/** * 카메라 열기 */public void openCamera(){
    Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);    fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image    intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name    startActivityForResult(intent,CAMERA_CAPTURE);}

下面是官方给的 生成图片文件的方法
/** Create a file Uri for saving an image or video */private static Uri getOutputMediaFileUri(int type){
    return Uri.fromFile(getOutputMediaFile(type));}

/** Create a File for saving an image or video */private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted    // using Environment.getExternalStorageState() before doing this.
    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), "MyCameraApp");    // This location works best if you want the created images to be shared    // between applications and persist after your app has been uninstalled.
    // Create the storage directory if it does not exist    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            MakeContent.getInstance().ContentErrorLogger("failed to create directory");            return null;        }
    }

    // Create a media file name    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());    File mediaFile;    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "IMG_"+ timeStamp + ".jpg");    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                "VID_"+ timeStamp + ".mp4");    } else {
        return null;    }

    return mediaFile;}

下面是在 onActivityResult 里返回文件。这里需要注意的是 有的机型 返回Intent为空.
所以直接拿过来 fileUri 即可

if (requestCode == CAMERA_CAPTURE) {
    if (resultCode == RESULT_OK) {
        // Image captured and saved to fileUri specified in the Intent        gallaryFilePath( fileUri.toString().replaceFirst("file://",""));    } else if (resultCode == RESULT_CANCELED) {
        // User cancelled the image capture    } else {
        // Image capture failed, advise user    }
}

2016年8月17日星期三

plugin with id 'com.jfrog.bintray' not found

需要添加 bintray的 plugin

添加位置是 项目文件的build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.2'
        // NOTE: Do not place your application dependencies here; they belong        // in the individual module build.gradle files        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.5'    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir}

2016年8月16日星期二

overridePendingTransition(0,0) 不起作用总结

1. startActivit 之前 或者finish之前的话 会失败

2.夹在那两个之间也会失败

3.只有在 startActivity,finish之后才能成功

4.如果 startActivity之后没有 finish的话 会成功

2016年8月4日星期四

ScrollView 嵌套 ListView 使用

项目做多了之后,会发现其实 ScrollView嵌套ListVew或者GridView等很常用,但是你也会发现各种奇怪问题产生。根据个人经验现在列出常见问题以及代码最少最简单的解决方法。
问题一 : 嵌套在 ScrollView的 ListVew数据显示不全,我遇到的是最多只显示两条已有的数据。
解决办法:重写 ListVew或者 GridView,网上还有很多若干解决办法,但是都不好用或者很复杂。
@Override
/**   只重写该方法,达到使ListView适应ScrollView的效果   */ 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
问题二 、打开套有 ListVew的 ScrollView的页面布局 默认 起始位置不是最顶部。
解决办法有两种都挺好用:
一是把套在里面的Gridview 或者 ListVew 不让获取焦点即可。
gridview.setFocusable(false); listview.setFocusable(false);
注意:在xml布局里面设置android:focusable=“false”不生效
方法二:网上还查到说可以设置myScrollView.smoothScrollTo(0,0);

2016年8月2日星期二

RecycleView GridLayoutManager中的 加载更多

public abstract class BaseScrollListener extends RecyclerView.OnScrollListener {

    protected RecyclerView.LayoutManager layoutManager;
    public BaseScrollListener(RecyclerView.LayoutManager layoutManager) {

        this.layoutManager = layoutManager;
        this.init();    }

    @Override    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

        super.onScrolled(recyclerView, dx, dy);
        this.onScroll(recyclerView, this.getFirstVisibleItem(), this.layoutManager.getChildCount(), this.layoutManager.getItemCount(), dx, dy);    }

    private int getFirstVisibleItem(){

        if(this.layoutManager instanceof LinearLayoutManager){

            return ((LinearLayoutManager) this.layoutManager).findFirstVisibleItemPosition();        } else if (this.layoutManager instanceof StaggeredGridLayoutManager){

            int[] spanPositions = null; //Should be null -> StaggeredGridLayoutManager.findFirstVisibleItemPositions makes the work.
            try{

                return ((StaggeredGridLayoutManager) this.layoutManager).findFirstVisibleItemPositions(spanPositions)[0];            }catch (Exception ex){

                // Do stuff...            }
        }

        return 0;    }

    public abstract void init();
    protected abstract void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy);
}

recyclerView.addOnScrollListener(new BaseScrollListener(gridLayoutManager) {
    @Override    public void init() {

    }

    @Override    protected void onScroll(RecyclerView recyclerView, int firstVisibleItem, int visibleItemCount, int totalItemCount, int dx, int dy) {
        MakeContent.getInstance().ContextLoggerInfo("firstVisibleItem:"+firstVisibleItem+" -- "+" visibleItemCount:"+visibleItemCount+" -- "+"totalItemCount:"+totalItemCount);        //더보기        if (firstVisibleItem+visibleItemCount == totalItemCount){
            moreListView();        }
    }
});