Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

material 风格和 ios 风格,可传入 context 构建,可在任意界面弹出,包含了 loading,alert,单选和多选对话框,输入对话框,actionSheet 和 bottomShe

$
0
0

DialogUtil

项目地址:hss01248/DialogUtil
简介:material 风格和 ios 风格,可传入 context 构建,可在任意界面弹出,包含了 loading,alert,单选和多选对话框,输入对话框,actionSheet 和 bottomSheet.不知道是 bu 是东半球最全?

material 风格(v7 支持包中的),ios 风格,传入 context 构建,可在任意界面弹出,以及 dialog 样式的 activity(todo)

特性

loading 对话框和 ios 风格的 dialog 传入 context 和 activity 均可弹出 dialog.

样式包括常用的 ios 风格 dialog 和 meterial design 风格的 dialog.

可以传入自定义的 view,定义好事件,本工具负责显示

考虑了显示内容超多时的滑动和与屏幕的间隙.

更新

1.ios 风格的按钮颜色改成 ios 的蓝色(图中蓝色不正是因为 total control 截图后偏色)

2.按钮添加按下的效果

3.增加 meterial design 的单选和多选,增加 ios 风格的输入框

4.增加传入自定义的 view(此时工具类作为一个壳,view 相关数据样式和回调自己实现.)

2016-10-20

1.增加转菊花的 loading 效果

2.bottomsheet 加上 listview 和 gridview,只需要设置图标和文字

参考:

http://blog.csdn.net/qibin0506/article/details/51002241 http://www.cnblogs.com/warnier-zhang/p/4904296.htmlhttp://www.jianshu.com/p/21bb14e3be94/comments/1570995 坑

https://github.com/android-cjj/BottomSheets

todo

1.windows 风格的 dialog

2.md 风格按钮样式无法自定义的 bug

3.bottomsheet 图标大小和文字大小的自定义

示例图

loading-common

loading

md_alert

md_single_choose

md_multi_choose

ios 风格(含按下效果)

ios_alert

ios_alert_v

ios_input

ios_centerlist

ios_bottom

bottomsheet:

btnsheet-lv

btnsheet-gv

gridview 拉出来时:

btnsheet-gv-out

useage

gradle

Step 1. Add the JitPack repository to your build file

Add it in your root build.gradle at the end of repositories:

    allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }

Step 2. Add the dependency

    dependencies {
           // compile 'com.github.hss01248:DialogUtil:1.0.0'
    }

示例代码(MainActivity 里)

//通过普通的 activity 弹出进度条(转圈圈)
StyledDialog.showProgressDialog(this,msg,true,true);

//通过 context 弹出进度条
gloablDialog=   StytledDialog.showMdLoading(getApplicationContext(),msg,true,true);

//meterial design 样式的 alertdialog:
 StyledDialog.showMdAlert(this, "title", msg, "sure", "cancle", "think about", true,     true, new MyDialogListener() {
                    @Override
                    public void onFirst() {
                        showToast("onFirst");
                    }

                    @Override
                    public void onSecond() {
                        showToast("onSecond");
                    }

                    @Override
                    public void onThird() {
                        showToast("onThird");
                    }


                });

 //ios 样式的提示框:( StytledDialog.showIosAlertVertical(...)为按钮竖直方向上排列的对话框)

StyledDialog.showIosAlert(this, "title", msg, "sure", "cancle", "think about", true, true, new MyDialogListener() {
                    @Override
                    public void onFirst() {
                        showToast("onFirst");
                    }

                    @Override
                    public void onSecond() {
                        showToast("onSecond");
                    }

                    @Override
                    public void onThird() {
                        showToast("onThird");
                    }


                });

  //底部弹出的带或不带取消按钮的弹窗

   final List<String> strings = new ArrayList<>();
                strings.add("1");
                strings.add("2");
                strings.add(msg);

    StyledDialog.showBottomItemDialog(activity, strings, "cancle", true, true, new MyItemDialogListener() {
                    @Override
                    public void onItemClick(String text,int position) {
                        showToast(text);
                    }

                    @Override
                    public void onBottomBtnClick() {
                        showToast("onItemClick");
                    }
                });}
   //输入框:

     StyledDialog.ShowNormalInput(activity, "登录", "请输入用户名", "请输入密码", "登录", "取消", true, new MyDialogListener() {
                   @Override
                   public void onFirst() {

                   }

                   @Override
                   public void onSecond() {

                   }

                   @Override
                   public void onGetInput(CharSequence input1, CharSequence input2) {
                       super.onGetInput(input1, input2);
                       showToast("input1:"+ input1 +"--input2:"+input2);
                   }
               });


  //中间弹出的条目弹窗

   final List<String> strings = new ArrayList<>();
                strings.add("1");
                strings.add("2");
                strings.add(msg);

   StyledDialog.showIosSingleChoose(activity, strings, true, true, new MyItemDialogListener() {
                    @Override
                    public void onItemClick(String text,int position) {
                        showToast(text);
                    }

                    @Override
                    public void onBottomBtnClick() {
                        showToast("onItemClick");
                    }
                });

context 弹出 dialog 注意事项

弹出后对后退键的响应需要自己写代码:

Dialog gloablDialog;//用一个统一的变量名存引用

@Override
public void onBackPressed() {

    if (gloablDialog != null && gloablDialog .isShowing()){
        gloablDialog.dismiss();
    }else {
        super.onBackPressed();
    }
}
作者:u014608640 发表于2016/10/27 17:15:45 原文链接
阅读:43 评论:0 查看评论

Android学习笔记之四

$
0
0

基本视图介绍

1.文本 按钮与输入框

文本 按钮 输入框的继承关系

这里写图片描述

TextView:
android:text=”文本”
android:textSize=”20sp”
android:textColor=”#FF0”
android:textStyle=”bold”
android:lines=”3”
android:singleLine=”true”
android:typeface=”monospace” //设置字型。字形有:normal, sans, serif,monospace
android:clickable=””

Button:
属性与TextView基本相似。
不同点:
1. 按钮是自带了背景的控件
2. 按钮是可以点击了

EditText:
android:hint=”请输入QQ”
android:editable=”true”
android:maxLength=”50” 设置最大的字数
android:inputType=”textPassword”

2.是非选择框

ToggleButton:
android:textOn=”开启”
android:textOff=”关闭”
android:checked=”true”

RadioButton(单选框):
RadioGroup 单选组
RadioButton 单选框
一般它们是配合使用的
设置监听器的时候,通过 RadioGroup radioGroup.setOnCheckedChangeListener();
android:checked=”true”

CheckBox:
设置监听器的时候 每个CheckBox都应该设置
android:checked=”false”

附 ToggleButton和CheckBox练习代码:

MainActivity.java

package com.m520it.select;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.Toast;

public class MainActivity extends Activity{
    private List<String> mChooseData = new ArrayList<String>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        CheckBox aCbx = (CheckBox) findViewById(R.id.cbx_apple);
        CheckBox bCbx = (CheckBox) findViewById(R.id.cbx_banance);
        CheckBox cCbx = (CheckBox) findViewById(R.id.cbx_orange);
        myCheckedChangeListener listener = new myCheckedChangeListener();
        aCbx.setOnCheckedChangeListener(listener);
        bCbx.setOnCheckedChangeListener(listener);
    }
    class myCheckedChangeListener implements OnCheckedChangeListener{
        @Override
        public void onCheckedChanged(CompoundButton buttonView,
                boolean isChecked) {
            int CbxId = buttonView.getId();
            if(CbxId==R.id.cbx_apple){
                if(isChecked){
                    mChooseData.add("苹果");
                }else{
                    if(mChooseData.contains("苹果")){
                        mChooseData.remove("苹果");
                    }
                }
            }else if(CbxId==R.id.cbx_banance){
                if(isChecked){
                    mChooseData.add("香蕉");
                }else{
                    if(mChooseData.contains("香蕉")){
                        mChooseData.remove("香蕉");
                    }
                }
            }else if(CbxId==R.id.cbx_orange){
                if(isChecked){
                    mChooseData.add("橘子");
                }else{
                    if(mChooseData.contains("橘子")){
                        mChooseData.remove("橘子");
                    }
                }
            }
        }
    }
    public void myClick(View v){
        String result = "";
        for (int i = 0; i < mChooseData.size(); i++) {
            result+=mChooseData.get(i);
        }
        Toast.makeText(this, result, Toast.LENGTH_SHORT).show();
    }
}

布局文件activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:checked="true"
        android:textOff="关"
        android:textOn="开" />

    <CheckBox
        android:id="@+id/cbx_apple"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="苹果" />

    <CheckBox
        android:id="@+id/cbx_banance"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="香蕉" />

    <CheckBox
        android:id="@+id/cbx_orange"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="橘子" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="myClick"
        android:text="button" />

</LinearLayout>

附 RadioGroup和RadioButton练习代码:

MainActivity.java

package com.m520it.select;
import android.app.Activity;
import android.os.Bundle;

import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.Toast;

public class MainActivity extends Activity implements OnCheckedChangeListener {

    private RadioButton mARg;
    private RadioButton mBRg;
    private RadioButton mCRg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.radiogroup);
        radioEvent();
    }

    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        if (R.id.rg_a == checkedId) {
            Toast.makeText(this, mARg.getText().toString(), Toast.LENGTH_SHORT)
                    .show();
        } else if (R.id.rg_b == checkedId) {
            Toast.makeText(this, mBRg.getText().toString(), Toast.LENGTH_SHORT)
                    .show();
        } else if (R.id.rg_c == checkedId) {
            Toast.makeText(this, mCRg.getText().toString(), Toast.LENGTH_SHORT)
                    .show();
        }
    }

    public void radioEvent() {
        RadioGroup rg = (RadioGroup) findViewById(R.id.rg);
        mARg = (RadioButton) findViewById(R.id.rg_a);
        mBRg = (RadioButton) findViewById(R.id.rg_b);
        mCRg = (RadioButton) findViewById(R.id.rg_c);
        rg.setOnCheckedChangeListener(this);
    }
}

布局文件R.layout.radiogroup:

<?xml version="1.0" encoding="utf-8"?>
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" 
    android:id="@+id/rg"
    >

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="苹果" 
        android:id="@+id/rg_a"
        />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="香蕉"
        android:id="@+id/rg_b" 
         />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="西瓜" 
         android:id="@+id/rg_c"
        />
</RadioGroup>

3.进度条

没有进度的进度条

  • 大进度条
    style=”?android:attr/progressBarStyleLarge”
  • 普通进度条
    style=”?android:attr/progressBarStyle”
  • 小进度条
    style=”?android:attr/progressBarStyleSmall”

有进度的进度条

style=”?android:attr/progressBarStyleHorizontal”
android:progress=”80”
android:max=”100”

3.可拖动的进度条

SeekBar:可拖动的进度条
android:max=”100”
android:progress=”50”

4.星星进度条

RatingBar:星星进度条 基本单位为半颗星
android:numStars=”4”
android:rating=”3.5”
android:stepSize=”0.5”

练习代码:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <!-- 没有进度的进度条 -->

    <ProgressBar
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ProgressBar
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <ProgressBar
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <!-- 有进度的进度条 -->

    <ProgressBar
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:max="100"
        android:progress="80" />

    <!-- 可以拖动的进度条 -->

    <SeekBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content" 
        android:max="100"
        android:progress="40"
        />
    <!--星星进度条  -->
    <RatingBar 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:numStars="4"
        android:rating="3.5"
        android:stepSize="0.5"
        />
</LinearLayout>

3.图片控件(ImageView)

设置图片源
android:src=”@drawable/ic_launcher”

代码设置图片源
imageView.setImageResource(resId)
imageView.setImageBitmap(bm);
imageView.setImageDrawable(drawable);

设置缩放模式
android:scaleType=”“

这里写图片描述

练习demo:

MainActivity.java

package com.m520it.imageView;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ImageView imageView = (ImageView) findViewById(R.id.iv2);
        /*imageView.setImageResource(resId)
        imageView.setImageBitmap(bm);
        imageView.setImageDrawable(drawable);*/
        imageView.setImageResource(R.drawable.flower);
    }
}

布局文件activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <ImageView
        android:id="@+id/iv1"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:scaleType="centerCrop"
        android:src="@drawable/flower" />

    <ImageView 
        android:id="@+id/iv2"
        android:layout_width="150dp"
        android:layout_height="150dp"
        />
</LinearLayout>

4.滑动控件

  1. 手机界面无法容纳更多的控件的时候,就需要滚动界面。
  2. ScrollView :控制上下滑动的效果
  3. HorizontalScrollView: 控制左右滑动的效果
  4. ScrollView与HorizontalScrollView只能容纳一个子控件。
HorizontalScrollView的布局demo
<?xml version="1.0" encoding="utf-8"?>
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!"
            android:textSize="30sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello world!Hello world!Hello world!Hello world!Hello world!Hello world!"
            android:textColor="#F00"
            android:textSize="30sp" />

    </LinearLayout>

</HorizontalScrollView>


ScrollView的布局demo
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

         <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello world"
            android:lines="10"
            android:textSize="30sp" />

        <TextView
            android:text="Hello world"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#F00"
            android:lines="10"
            android:textSize="30sp" />

        <TextView
            android:text="Hello world"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#F00"
            android:lines="10"
            android:textSize="30sp" />

    </LinearLayout>

</ScrollView>

5.日期时间选择器

直接敲一个demo就理解了

MainActivity.java

package com.m520it.dateAndTime;

import java.util.Calendar;
import java.util.Locale;

import android.app.Activity;
import android.os.Bundle;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;
import android.widget.TimePicker;
import android.widget.TimePicker.OnTimeChangedListener;
import android.widget.Toast;

public class MainActivity extends Activity implements OnDateChangedListener {

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

        //获取日期控件
        DatePicker datePicker = (DatePicker) findViewById(R.id.datePicker);
        Calendar calendar = Calendar.getInstance(Locale.CHINESE);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        //init用来初始化改变后的日期
        //this 日期改变的监听器
        datePicker.init(year, month, day,this);

        //获取时间控件
        TimePicker timePicker = (TimePicker) findViewById(R.id.timePicker);
        //设置24小时
        timePicker.setIs24HourView(true);
        //设置监听器
        timePicker.setOnTimeChangedListener(new OnTimeChangedListener() {
            @Override
            public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
                System.out.println(hourOfDay+"..."+minute);
                Toast.makeText(MainActivity.this,hourOfDay+"..."+minute,Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onDateChanged(DatePicker view, int year, int monthOfYear,
            int dayOfMonth) {
        Toast.makeText(this,"year:"+year+" mothOfYear:"+(monthOfYear+1)+" dayOfMonth:"+dayOfMonth, Toast.LENGTH_SHORT).show();
    }
}

布局文件activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.m520it.dateAndTime.MainActivity" 
    android:orientation="vertical"
    >

    <DatePicker
        android:id="@+id/datePicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
      />
    <TimePicker 
        android:id="@+id/timePicker"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</LinearLayout>
作者:tyhp1 发表于2016/10/27 17:28:39 原文链接
阅读:12 评论:0 查看评论

Android:IPC之AIDL的学习和总结

$
0
0

为了使得一个程序能够在同一时间里处理许多用户的要求。即使用户可能发出一个要求,也肯能导致一个操作系统中多个进程的运行(PS:听音乐,看地图)。而且多个进程间需要相互交换、传递信息,IPC方法提供了这种可能。IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字(Socket)。

Android中的IPC方式有Bundle、文件共享、Messager、AIDL、ContentProvider和Socket。
这次我们学习的是Android中的AIDL。

概述

AIDL(Android接口描述语言)是一个IDL语言,它可以生成一段代码,可以是一个在Android设备上运行的两个进程使用内部通信进程进行交互。在Android上,一个进程通常无法访问另一个进程的内存。所以说,如果你想在一个进程中(例如在一个Activity中)访问另一个进程中(例如service)某个对象的方法,你就可以使用AIDL来生成这样的代码来伪装传递各种参数。

语法

AIDL它和Java基本上类似,只是有一些细微的差别(PS:可能Google为了方便Android程序猿使用)。AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。重要的是必须导入所有非内置类型,哪怕是这些类型是在与接口相同的包中。

下边说说AIDL的一些特点:

  • 通常引引用方式传递的其他AIDL生成的接口,必须要import 语句声明。
  • Java编程语言的主要类型 (int, boolean等) —不需要 import 语句。
  • 在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件中支持哪些数据类型呢?
    如下所示:
    1、基本数据类型(int,long,char,boolean,float,double,byte,short八种基本类型);
    2、String和CharSequence;
    3、List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
    4、Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;
    5、Parcelable:所有实现了Parcelable接口的对象;
    6、AIDL:所有的AIDL接口本身也可以在AIDL文件中使用;
    以上6中数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。

需要注意的地方:
AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout;
(PS:假若传递一个Book对象且没有加指向tag时,则会抛出aidl.exe E 4928 5836 type_namespace.cpp:130] 'Book' can be an out type, so you must declare it as in, out or inout. 异常)
- in表示输入型参数(Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改)
- out表示输出型参数(Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改)
- inout表示输入输出型参数(Server可以获取到Client传递过去的数据,但是能对Client端的数据进行修改)。
更多tag相关的内容:AIDL源码解析in、out和inout

使用AIDL实现IPC

实现步骤 (官网AIDL样例

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

这是官网创建的一个简单的AIDL文件。

这次我们自己声明一个包含非默认支持类型的AIDL文件。
AIDL要跨进程通信,其所携带的数据也需要跨进程传输。所以我们首先需要自定自己想要传输的数据类必须其必须实现Parcelable接口从而可以被序列化。
为什么需要序列化呢,为什么不适用Serializable,不知道的同学可以看下这篇文章: Serializable和Parcelable的再次回忆
所以我们先创建需要传输的数据所对应的aidl文件,然后再相同目录下创建对应的Java类文件。这里可能有些同学会疑惑,不是直接创建Java类么。AIDL文件有两种类型,一种是我们上边定义的接口,而另外一种就是非常规类型的数据对象文件。即:如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。详细的使用我们看下边例子:

创建一个Book.aidl文件

在Android Studio的项目中先创建对应的aidl包,然后右击选择创建aidl文件,so easy。

// Book.aidl
package com.tzx.aidldemo.aidl;

// Declare any non-default types here with import statements
//所有注释掉的内容都是Android Studio帮你写的,但是我们不需要。
//我们创建的是aidl数据对象,所以我们只需写出parcelable 后面跟对象名。
//parcelabe前的字母‘p’是小写的哦~
parcelable Book;
//interface Book {
//
//    /**
//     * Demonstrates some basic types that you can use as parameters
//     * and return values in AIDL.
//     */
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);
//}

在Book.aidl的包下创建Book.java类文件

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {
    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    //从序列化后的对象中创建原始对象
    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从序列化后的对象中创建原始对象
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        //指定长度的原始对象数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    //返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0,几乎所有情况都返回0
    @Override
    public int describeContents() {
        return 0;
    }
    //将当前对象写入序列化结构中,其flags标识有两种(1|0)。
    //为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况下都为0.
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    @Override
    public String toString() {
        return "[bookId=" + bookId + ",bookName='" + bookName + "']";
    }
}

在Android Studio中如果先创建Java类文件,然后创建AIDL文件则会提示命名重复,但顺序反过来就可以。

创建aidl接口文件IBookManager.aidl

// IBookManager.aidl
package com.tzx.aidldemo.aidl;
//通常引用方式传递自定义对象,必须要import语句声明
import com.tzx.aidldemo.aidl.Book;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

这样所有aidl相关的文件就定义完了,我们可以写客户端和服务端了么。然而实际结果表明我们还是无法在客户端或服务费使用aidl类。在这里说一下其实aidl方式只不过是为我们提供模板自动创建aidl对应的Java类文件,只有生成了对应的Java文件之后我们才可以在客户端或服务端使用。Android studio中make一下当前的project就会在项目的app/build/source/aidl/包名/debug这个目录下生成对应的aidl类文件(PS:只有aidl接口文件才会生成java类文件)。

make的时候可能提示找不到对应的Book.java文件,我们可以在build.gradle文件中的android{}标签里面添加:

sourceSets{
    main{
        aidl.srcDirs = ['src/main/java']
    }
}

这种情况只适合aidl类文件和对应的java类文件在同一个包下。

好了,现在所有的aidl文件都有了,我们开始写我们的服务交互了~!~!

服务端:

Service服务

/**
 * Created by tanzhenxing
 * Date: 2016/10/17.
 * Description:远程服务
 */
public class BookManagerService extends Service {
    //支持并发读写
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    //服务端定义Binder类(IBookManager.Stub)
    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

并在Manifest文件中声明,将它放在一个新的进程中,这样方便我们演示跨进程通信。

<service android:name=".BookManagerService"
    android:process=":server"/>

客户端:

/**
 * Created by tanzhenxing
 * Date: 2016/10/17.
 * Description:主界面
 */
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText bookNameTV;
    private Button bookAddTV;
    private Button bookCountTV;
    private TextView bookInfoTV;
    private Intent bookManagerIntent;
    private boolean mBound = false;
    private IBookManager bookManager;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //客户端获取代理对象
            bookManager = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        bookManagerIntent = new Intent(this, BookManagerService.class);
        bindService(bookManagerIntent, mConnection, Context.BIND_AUTO_CREATE);
        mBound = true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initListener();

    }


    private void initView() {
        bookNameTV = (EditText) findViewById(R.id.book_name);
        bookAddTV = (Button) findViewById(R.id.book_add);
        bookCountTV = (Button) findViewById(R.id.book_count);
        bookInfoTV = (TextView) findViewById(R.id.book_info);
    }

    private void initListener() {
        bookAddTV.setOnClickListener(this);
        bookCountTV.setOnClickListener(this);
    }


    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            mBound = false;
            unbindService(mConnection);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.book_add:
                addBook();
                break;
            case R.id.book_count:
                getBookList();
                break;
        }
    }

    private void addBook() {
        if (bookManager != null && !TextUtils.isEmpty(bookNameTV.getText().toString())) {
            Book book = new Book((int) System.currentTimeMillis(), bookNameTV.getText().toString());
            try {
                bookManager.addBook(book);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    public void getBookList() {
        try {
            if (bookManager != null) {
                List<Book> list = bookManager.getBookList();
                if (list != null && list.size() > 0) {
                    StringBuilder builder = new StringBuilder();
                    for (Book book : list) {
                        builder.append(book.toString());
                        builder.append('\n');
                    }
                    bookInfoTV.setText(builder.toString());
                } else {
                    bookInfoTV.setText("Empty~!");
                }
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

运行结果

AIDLDEMO

解析aidl生成的java类

public interface IBookManager extends android.os.IInterface {
    //根据aidl文件中定义的方法,进行接口声明
    public java.util.List<com.tzx.aidldemo.aidl.Book> getBookList()
        throws android.os.RemoteException;
    //根据aidl文件中定义的方法,进行接口声明
    public void addBook(com.tzx.aidldemo.aidl.Book book)
        throws android.os.RemoteException;

    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.tzx.aidldemo.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.tzx.aidldemo.aidl.IBookManager";
        //定义方法执行code,与客户端同步
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            1);

        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.tzx.aidldemo.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.tzx.aidldemo.aidl.IBookManager asInterface(
            android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }

            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

            if (((iin != null) &&
                    (iin instanceof com.tzx.aidldemo.aidl.IBookManager))) {
                return ((com.tzx.aidldemo.aidl.IBookManager) iin);
            }
            //生成代理对象
            return new com.tzx.aidldemo.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data,
            android.os.Parcel reply, int flags)
            throws android.os.RemoteException {
            switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);

                return true;
            }

            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);
                //调用服务端getBookList()
                java.util.List<com.tzx.aidldemo.aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);

                return true;
            }

            case TRANSACTION_addBook: {
                data.enforceInterface(DESCRIPTOR);

                com.tzx.aidldemo.aidl.Book _arg0;

                if ((0 != data.readInt())) {
                    _arg0 = com.tzx.aidldemo.aidl.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                //调用服务端addBook()
                this.addBook(_arg0);
                reply.writeNoException();

                return true;
            }
            }

            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.tzx.aidldemo.aidl.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.tzx.aidldemo.aidl.Book> getBookList()
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.tzx.aidldemo.aidl.Book> _result;

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //调用远程服务addBook()
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data,
                        _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.tzx.aidldemo.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }

            @Override
            public void addBook(com.tzx.aidldemo.aidl.Book book)
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);

                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //调用远程服务addBook
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }
    }
}

用关系图表示比较清楚些。。

IBookManager.java

每个文件结构我们都解析完了,那么aidl到底是怎么实现通信的呢,要让我们自己写一套类似于aidl的那么应该怎么去设计呢?

我们仿aidl画一幅结构图:
AIDL

根据上面这个图,我们就可以写出自己的aidl。

//方法接口
interface IBookManager {
    final int CHANGE_MSG = 1;
    Book change(Book book);
}
//实现方法的代理类
public class Proxy implements IBookManager{
  private static IBinder mRemote;
  private static Proxy asInterface(IBinder service) {
    this.mRemote = service;
    return new Proxy();
  }

  public Book add(Book book) {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    try {
        data.writeInt(1);
        data.writeToParcel(message);
        mRemote.transact(CHAT, data, reply, CHANGE_MSG);
        reply.readException();
        if(0 != reply.readInt()) {
            return Book.CREATOR.createFromParcel(_reply);
        }
    } catch (RemoteException e) {
        e.printStackTrace();
    }finally {
        data.recycle();
        reply.recycle();
    }
    return null;
  }
}
//Binder远端实现类
public class Stub extends Binder implements IBookManager{
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case CHANGE_MSG:
                if(0 != data.readInt()){
                    Book book = Book.CREATOR.createFromParcel(data)
                }
                book.name = "Server";
                reply.writeNoException();
                reply.writeInt(1);
                book.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                return true;
            default:
                return super.onTransact(code, data, reply, flags);
        }
    }
}

看完以上针对aidl的文字和图片结合方式讲解我想你应该能熟练的开发aidl了吧,如果还有问题可以给我留言哦~!

GitHubDemo地址

更多文章–>我的个人博客下雨天要逛街

作者:stven_king 发表于2016/10/27 17:33:34 原文链接
阅读:113 评论:1 查看评论

AIDL源码解析in、out和inout

$
0
0

为什么会想写这篇文章,只因为一个error idl.exe E 4928 5836 type_namespace.cpp:130] 'Book' can be an out type, so you must declare it as in, out or inout. 看过上一篇文章Android:IPC之AIDL的学习和总结的同学都知道这是因为在AIDL文件中使用非常规类型作为参数传递的时候没有标记指向tag。

介绍

官网介绍AIDL的时候上有这么一段话

  • All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout (see the example below).
  • Primitives are in by default, and cannot be otherwise.
  • Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.

大概意思是非默认类型的参数都需要添加指向标签in,out或inout。根据自己的需求去添加,因为实现是有代价的。

已知结论

看过Android:IPC之AIDL的学习和总结的同学都知道:

  • in表示输入型参数(Server可以获取到Client传递过去的数据,但是不能对Client端的数据进行修改)
  • out表示输出型参数(Server获取不到Client传递过去的数据,但是能对Client端的数据进行修改)
  • inout表示输入输出型参数(Server可以获取到Client传递过去的数据,但是能对Client端的数据进行修改)。

提出问题

下边我们就研究一个in,out或inout为什么能代表不同的传输方式,为什么实现的代价不一样。

过程验证

创建Book.aidl文件

package com.tzx.aidldemo.aidl;
parcelable Book;

创建Book.java文件

package com.tzx.aidldemo.aidl;
public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {
    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    //从序列化后的对象中创建原始对象
    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从序列化后的对象中创建原始对象
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        //指定长度的原始对象数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    //返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0,几乎所有情况都返回0
    @Override
    public int describeContents() {
        return 0;
    }
    //将当前对象写入序列化结构中,其flags标识有两种(1|0)。
    //为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况下都为0.
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    @Override
    public String toString() {
        return "[bookId=" + bookId + ",bookName='" + bookName + "']";
    }
}

创建aidl接口文件IBookManager.aidl文件

package com.tzx.aidlinout.aidl;
import com.tzx.aidlinout.aidl.Book;
interface IBookManager {
    Book addInBook(in Book book);
    Book addOutBook(out Book book);
    Book addInoutBook(inout Book book);
}

创建远程服务

//将bookId都改为-1,在bookName后面都添加参数的tag标记
public class BookManagerService extends Service {
    private CopyOnWriteArrayList list = new CopyOnWriteArrayList();
    private IBinder mBinder = new IBookManager.Stub(){

        @Override
        public Book addInBook(Book book) throws RemoteException {
            book.bookId = -1;
            book.bookName = book.bookName + "-in";
            list.add(book);
            return book;
        }

        @Override
        public Book addOutBook(Book book) throws RemoteException {
            book.bookId = -1;
            book.bookName = book.bookName + "-out";
            list.add(book);
            return book;
        }

        @Override
        public Book addInoutBook(Book book) throws RemoteException {
            book.bookId = -1;
            book.bookName = book.bookName + "-inout";
            list.add(book);
            return book;
        }

        @Override
        public List<Book> getBookList() throws RemoteException {
            return list;
        }
    };
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

在创建上面的文件的过程中,遇到不太清楚的或者编译出现Error的,可以参考Android:IPC之AIDL的学习和总结

具体方法调用的Activity就不写全部代码了,我们看看三种方法的调用

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.book_in:
                try {
                    int bookId = Integer.parseInt(bookIdET.getText().toString());
                    String bookName = bookNameET.getText().toString();
                    if (bookId <= 0 || TextUtils.isEmpty(bookName)) return;
                    StringBuilder builder = new StringBuilder();
                    //LogUtils.d("-----------book_in-----------------");
                    Book book0 = new Book(bookId, bookName);
                    String source = "source:" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    builder.append('\n');
                    String result = "result:" + bookManager.addInBook(book0).toString();
                    //LogUtils.d(result);
                    builder.append(result);
                    builder.append('\n');
                    source = "source" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    //LogUtils.d("**************book_in****************");
                    bookinfoTV.setText(builder.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case R.id.book_out:
                try {
                    int bookId = Integer.parseInt(bookIdET.getText().toString());
                    String bookName = bookNameET.getText().toString();
                    if (bookId <= 0 || TextUtils.isEmpty(bookName)) return;
                    StringBuilder builder = new StringBuilder();
                    //LogUtils.d("-----------book_out-----------------");
                    Book book0 = new Book(bookId, bookName);
                    String source = "source:" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    builder.append('\n');
                    String result = "result:" + bookManager.addOutBook(book0).toString();
                    //LogUtils.d(result);
                    builder.append(result);
                    builder.append('\n');
                    source = "source" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    //LogUtils.d("**************book_out****************");
                    bookinfoTV.setText(builder.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            case R.id.book_inout:
                try {
                    int bookId = Integer.parseInt(bookIdET.getText().toString());
                    String bookName = bookNameET.getText().toString();
                    if (bookId <= 0 || TextUtils.isEmpty(bookName)) return;
                    StringBuilder builder = new StringBuilder();
                    //LogUtils.d("-----------book_inout-----------------");
                    Book book0 = new Book(bookId, bookName);
                    String source = "source:" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    builder.append('\n');
                    String result = "result:" + bookManager.addInoutBook(book0).toString();
                    //LogUtils.d(result);
                    builder.append(result);
                    builder.append('\n');
                    source = "source" + book0.toString();
                    //LogUtils.d(source);
                    builder.append(source);
                    //LogUtils.d("**************book_inout****************");
                    bookinfoTV.setText(builder.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
        }
    }

创建好上面三个文件后,我们编译整个项目工程(PS:生成aidl接口实现类)。

运行结果
AIDLinout运行结果

下边是与结果相对应的Log输出

14962-14962/com.tzx.aidlinout D/xxx: -----------book_in-----------------
14962-14962/com.tzx.aidlinout D/xxx: source:[bookId=1212,bookName=C++]
14962-14962/com.tzx.aidlinout D/xxx: result:[bookId=-1,bookName=C++-in]
14962-14962/com.tzx.aidlinout D/xxx: source[bookId=1212,bookName=C++]
14962-14962/com.tzx.aidlinout D/xxx: **************book_in****************
14962-14962/com.tzx.aidlinout D/xxx: -----------book_out-----------------
14962-14962/com.tzx.aidlinout D/xxx: source:[bookId=1212,bookName=C++]
14962-14962/com.tzx.aidlinout D/xxx: result:[bookId=-1,bookName=null-out]
14962-14962/com.tzx.aidlinout D/xxx: source[bookId=-1,bookName=null-out]
14962-14962/com.tzx.aidlinout D/xxx: **************book_out****************
14962-14962/com.tzx.aidlinout D/xxx: -----------book_inout-----------------
14962-14962/com.tzx.aidlinout D/xxx: source:[bookId=1212,bookName=C++]
14962-14962/com.tzx.aidlinout D/xxx: result:[bookId=-1,bookName=C++-inout]
14962-14962/com.tzx.aidlinout D/xxx: source[bookId=-1,bookName=C++-inout]
14962-14962/com.tzx.aidlinout D/xxx: **************book_inout****************

实际结果与我们已知结论一致~!~!

但问题我们还没有解决,我们继续看代码,其实所有的实现都是在改接口实现类中IBookManager.java

package com.tzx.aidlinout.aidl;
public interface IBookManager extends android.os.IInterface {
    public com.tzx.aidlinout.aidl.Book addInBook(
        com.tzx.aidlinout.aidl.Book book) throws android.os.RemoteException;

    public com.tzx.aidlinout.aidl.Book addOutBook(
        com.tzx.aidlinout.aidl.Book book) throws android.os.RemoteException;

    public com.tzx.aidlinout.aidl.Book addInoutBook(
        com.tzx.aidlinout.aidl.Book book) throws android.os.RemoteException;

    public java.util.List<com.tzx.aidlinout.aidl.Book> getBookList()
        throws android.os.RemoteException;

    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements com.tzx.aidlinout.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.tzx.aidlinout.aidl.IBookManager";
        static final int TRANSACTION_addInBook = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            0);
        static final int TRANSACTION_addOutBook = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            1);
        static final int TRANSACTION_addInoutBook = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            2);
        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION +
            3);

        /** Construct the stub at attach it to the interface. */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.tzx.aidlinout.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.tzx.aidlinout.aidl.IBookManager asInterface(
            android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }

            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);

            if (((iin != null) &&
                    (iin instanceof com.tzx.aidlinout.aidl.IBookManager))) {
                return ((com.tzx.aidlinout.aidl.IBookManager) iin);
            }

            return new com.tzx.aidlinout.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data,
            android.os.Parcel reply, int flags)
            throws android.os.RemoteException {
            switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);

                return true;
            }

            case TRANSACTION_addInBook: {
                data.enforceInterface(DESCRIPTOR);
                //声明输入的参数_arg0的引用
                com.tzx.aidlinout.aidl.Book _arg0;
                //并根据输入的数据为其创建对象
                if ((0 != data.readInt())) {
                    _arg0 = com.tzx.aidlinout.aidl.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                //获取调用this.addInBook方法返回的_result
                com.tzx.aidlinout.aidl.Book _result = this.addInBook(_arg0);
                reply.writeNoException();
                //并向reply中写入返回值_result
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply,
                        android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }

                return true;
            }

            case TRANSACTION_addOutBook: {
                data.enforceInterface(DESCRIPTOR);
                //声明输入的参数_arg0的引用
                com.tzx.aidlinout.aidl.Book _arg0;
                //并为其创建新的对象
                _arg0 = new com.tzx.aidlinout.aidl.Book();
                //获取调用this.addOutBook方法返回的_result
                com.tzx.aidlinout.aidl.Book _result = this.addOutBook(_arg0);
                reply.writeNoException();
                //并向reply中写入返回值_result
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply,
                        android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                //再将参数_arg0写入reply中,至于为什么写入,我们看看客户端Proxy中的读取
                if ((_arg0 != null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply,
                        android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }

                return true;
            }

            case TRANSACTION_addInoutBook: {
                data.enforceInterface(DESCRIPTOR);
                //声明输入的参数_arg0的引用
                com.tzx.aidlinout.aidl.Book _arg0;
                //并根据输入的数据为其创建对象
                if ((0 != data.readInt())) {
                    _arg0 = com.tzx.aidlinout.aidl.Book.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                //获取调用this.addInoutBook方法返回的_result
                com.tzx.aidlinout.aidl.Book _result = this.addInoutBook(_arg0);
                reply.writeNoException();
                //并向reply中写入返回值_result
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply,
                        android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                //再将参数_arg0写入reply中,至于为什么写入,我们看看客户端Proxy中的读取
                if ((_arg0 != null)) {
                    reply.writeInt(1);
                    _arg0.writeToParcel(reply,
                        android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }

                return true;
            }

            case TRANSACTION_getBookList: {
                data.enforceInterface(DESCRIPTOR);

                java.util.List<com.tzx.aidlinout.aidl.Book> _result = this.getBookList();
                reply.writeNoException();
                reply.writeTypedList(_result);

                return true;
            }
            }

            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.tzx.aidlinout.aidl.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public com.tzx.aidlinout.aidl.Book addInBook(
                com.tzx.aidlinout.aidl.Book book)
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.tzx.aidlinout.aidl.Book _result;

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //将客户端调用时传入的参数写入_data中
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //将_data、_reply序列化对象和Stub.TRANSACTION_addInBook指令传递到Server端
                    mRemote.transact(Stub.TRANSACTION_addInBook, _data, _reply,
                        0);
                    _reply.readException();
                    //读取Server端返回的序列化_reply中的对象
                    if ((0 != _reply.readInt())) {
                        _result = com.tzx.aidlinout.aidl.Book.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                    //然后直接将_result返回
                    //我们发现整个方法调用期间传入的对象book只是将数据写入到Server,它的值进行并没有任何修改。
                    //总结:in类型的参数,它向服务端传入数据,但是却不接受Server返回的值。
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }

            @Override
            public com.tzx.aidlinout.aidl.Book addOutBook(
                com.tzx.aidlinout.aidl.Book book)
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.tzx.aidlinout.aidl.Book _result;

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //将_data、_reply序列化对象和Stub.TRANSACTION_addInBook指令传递到Server端
                    //_data和_reply序列化对象并没有进行写入
                    mRemote.transact(Stub.TRANSACTION_addOutBook, _data,
                        _reply, 0);
                    _reply.readException();
                    //读取Server端返回的序列化_reply中的对象,写入到_result
                    if ((0 != _reply.readInt())) {
                        _result = com.tzx.aidlinout.aidl.Book.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                    //读取Server端返回的序列化_reply中的对象,写入到传入的book对象中
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                    //然后直接将_result返回
                    //我们发现整个方法调用期间传入的对象book并没有将数据写入到Server,它的值确实是Server返回的。
                    //总结:out类型的参数,它并不向服务端传入数据,但是却接受Server返回的值。
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }

            @Override
            public com.tzx.aidlinout.aidl.Book addInoutBook(
                com.tzx.aidlinout.aidl.Book book)
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.tzx.aidlinout.aidl.Book _result;

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //将客户端调用时传入的参数写入_data中
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    //将_data、_reply序列化对象和Stub.TRANSACTION_addInoutBook指令传递到Server端
                    mRemote.transact(Stub.TRANSACTION_addInoutBook, _data,
                        _reply, 0);
                    _reply.readException();
                    //读取Server端返回的序列化_reply中的对象,写入到_result
                    if ((0 != _reply.readInt())) {
                        _result = com.tzx.aidlinout.aidl.Book.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                    //读取Server端返回的序列化_reply中的对象,写入到传入的book对象中
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                    //然后直接将_result返回
                    //我们发现整个方法调用期间传入的对象book将其数据写入到Server,并且它的值被Server返回的数据修改。
                    //总结:inout类型的参数,它既向服务端传入数据,也却接受Server返回的值。
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }

            @Override
            public java.util.List<com.tzx.aidlinout.aidl.Book> getBookList()
                throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.tzx.aidlinout.aidl.Book> _result;

                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data,
                        _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.tzx.aidlinout.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }

                return _result;
            }
        }
    }
}

看了这么多代码是不是感觉脑袋大了,没事接下来一张图帮你理的清清楚楚的:
aidl-tag-type

经过两篇文章对aidl的讲解,我想你已经把它理解的透透的了,如果还有什么问题可以给我留言哦~!

GitHubDemo地址

更多文章–>我的个人博客下雨天要逛街

作者:stven_king 发表于2016/10/27 17:35:59 原文链接
阅读:83 评论:0 查看评论

Intent的FLAG标志详解

$
0
0

在Android开发中,Intent想必大家经常用。Intent本意为目的、意向、意图。在Android中,Intent是系统各组件(或应用程序)之间进行数据传递的数据附载者,Intent不仅可以用于应用程序之间的交互,也可以用于应用程序内部的Activity、Service和Broadcast Receiver之间的交互。 解读Android Intent。本文主要说的是Intent的Flag标志。

Task

Task就是一个任务栈,里面用来存放Activity,第一个进去的(Activity)处于栈的最下面,而最后创建的(Activity)则处于栈的最上面。从Task中取出(Activity)是从最顶端取出,也就是说先进后出,后进先出。而Activity在Task中的顺序是可以控制的,在Activity跳转时用到Intent Flag可以设置新建Activity的创建方式。

FLAG_ACTIVITY_BROUGHT_TO_FRONT

这个Flag的意思,比如我现在有一个A,然后在A中启动B,并设置FLAG_ACTIVITY_BROUGHT_TO_FRONT这个启动标记,那么B就是以FLAG_ACTIVITY_BROUGHT_TO_FRONT启动的。然后在B中启动C,此时栈就是A,B,C。如果这个时候在C中启动B,那么栈的情况会是A,C,B。

FLAG_ACTIVITY_REORDER_TO_FRONT

如果在栈中有A,B,C三个Activity,并且是正常启动的,此时在C中启动B的话,还是会变成A,C,B的。 如果使用了标志 FLAG_ACTIVITY_CLEAR_TOP,那这个FLAG_ACTIVITY_REORDER_TO_FRONT标志会被忽略。

FLAG_ACTIVITY_NEW_TASK

假设现在有一个栈1,里面是A,B,C。此时,在C中启动D的时候,设置FLAG_ACTIVITY_NEW_TASK标记,此时会有两种情况:

1.如果D这个Activity在Manifest.xml中的声明中添加了Task Affinity,系统首先会查找有没有和D的Task Affinity相同的Task栈存在,如果有存在,将D压入那个栈
2.如果D这个Activity在Manifest.xml中的Task Affinity默认没有设置,则会把其压入栈1,变成:A B C D,这样就和标准模式效果是一样的了。

也就是说,设置了这个标志后,新启动的Activity并非就一定在新的Task中创建,如果A和B在属于同一个package,而且都是使用默认的Task Affinity,那B还是会在A的task中被创建。 所以,只有A和B的Task Affinity不同时,设置了这个标志才会使B被创建到新的Task。注意如果试图从非Activity的非正常途径启动一个Activity,比如从一个Receiver中启动一个Activity,则Intent必须要添加FLAG_ACTIVITY_NEW_TASK标记。

FLAG_ACTIVITY_CLEAR_TASK

如果Intent中设置了这个标志,会导致含有待启动Activity的Task在Activity被启动前清空。也就是说,这个Activity会成为一个新的root,并且所有旧的activity都被finish掉。这个标志只能与FLAG_ACTIVITY_NEW_TASK 一起使用。

FLAG_ACTIVITY_SINGLE_TOP

这个FLAG就相当于加载模式中的singleTop,比如说原来栈中情况是A,B,C,D在D中启动D,栈中的情况还是A,B,C,D。

FLAG_ACTIVITY_CLEAR_TOP

这个FLAG就相当于加载模式中的SingleTask,如果启动的Activity存在当前的栈中,系统会把要启动的Activity之上的Activity全部弹出栈空间,然后把Intent作为一个新的Intent传给这个Activity。

例如:原来栈中的情况是A,B,C,D这个时候从D中跳转到B,这个时候栈中的情况就是A,B了。上面例子中运行的B activity既可以在onNewIntent()中接收新的Intent,也可以将自己finish掉然后使用新的Intent重启。如果在它的launch mode中设置了”multiple”(默认),并且intent中没有设置 FLAG_ACTIVITY_SINGLE_TOP 标志,那它就会被finish掉然后重新创建。如果是其它的launchMode或者是设置了FLAG_ACTIVITY_SINGLE_TOP 属性,那就会使用现有的实例的OnNewIntent()方法来接受Intent。

这种启动模式也可以与 FLAG_ACTIVITY_NEW_TASK 一起使用:如果用来启动一个任务的root activity,它会将这个任务中现在运行的实例调到前台,然后将任务清空至只有根Activity的状态。这很有用,例如要从通知中心里启动一个Activity时。

FLAG_ACTIVITY_NO_HISTORY

用这个FLAG启动的Activity,一但退出,就不会存在于栈中。栈中是A,B,C 这个时候再C中以这个FLAG启动D的,D再启动E,这个时候栈中情况为A,B,C,E。简而言之,跳转到的activity不压在栈中。

FLAG_ACTIVITY_NO_USER_ACTION

如果设置了这个标志,可以在避免用户离开当前Activity时回调到 onUserLeaveHint()。通常,Activity可以通过这个回调表明有明确的用户行为将当前Activity切出前台。这个回调标记了Activity生命周期中的一个恰当的点,可以用来“在用户看过通知之后”将它们清除,如闪烁LED灯。

如果Activity是由非用户驱动的事件(如电话呼入或闹钟响铃)启动的,那这个标志就应该被传入Context.startActivity,以确保被打断的Activity不会认为用户已经看过了通知。

以下内容是根据文档和网上查看的FLAG(目的是为了把FLAG总结在一起)

FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

设置这个标志意味着在activity栈中做一个标记,在Task重置的时候栈就把从标记往上的activity都清除。也就是说,下次这个Task被通过FLAG_ACTIVITY_RESET_TASK_IF_NEEDED调到前台时(通常是由于用户从桌面重新启动),这个activity和它之上的activity都会被finish掉,这样用户就不会再回到他们,而是直接回到在它们之前的activity。

这在应用切换时非常有用。比如,Email应用会需要查看附件,就要调用查看图片的Activity来显示,那这个查看图片的Activity就会成为Email应用任务里的一部分。但是,如果用户离开了Email的任务,过了一会儿由通过Home来选择Email应用,我们会希望它回到查看邮件会话的页面,而不是浏览图片附件的页面,不然就感觉太诡异了。如果在启动查看图片Activity时设置了这个标志,那这个Activity及由它启动的Activity在下一次用户返回邮件时都会被清除。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

如果设置这个标志,这个Activity就不会在近期任务中显示。

FLAG_ACTIVITY_FORWARD_RESULT

如果Activity A 在启动 Activity B时设置了这个标志,那A的答复目标目标会传递给B,这样一来B就可以通过调用setResult(int) 将返回结果返回给A的答复目标。

FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

这个标志通常情况下不会通过应用的代码来设置,而是在通过最近任务启动activity时由系统设置的。

FLAG_ACTIVITY_NO_ANIMATION

禁用掉系统默认的Activity切换动画。

FLAG_ACTIVITY_TASK_ON_HOME

这个标志可以将一个新启动的任务置于当前的home任务(home activity task)之上(如果有的话)。也就是说,在任务中按back键总是会回到home界面,而不是回到他们之前看到的activity。这个标志只能与FLAG_ACTIVITY_NEW_TASK标志一起用。

比如,A->B->C->D,如果在C启动D的时候设置了这个标志,那在D中按Back键则是直接回到桌面,而不是C。

注意:只有D是在新的task中被创建时(也就是D的launchMode是singleInstance时,或者是给D指定了与C不同的taskAffinity并且加了FLAG_ACTIVITY_NEW_TASK标志时),使用 FLAG_ACTIVITY_TASK_ON_HOME标志才会生效。

感觉实际使用效果和用 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK 的效果一样。

FLAG_FROM_BACKGROUND

可以给调用者用来标识这个Intent是来自后台操作,而不是用户的交互行为。

FLAG_RECEIVER_FOREGROUND

当发送广播的时候设置了这个标志,会允许接收者以前台的优先级运行,有更短的时间间隔。正常广播的接受者是后台优先级,不会被自动提升。

FLAG_RECEIVER_REPLACE_PENDING

如果在发送广播时设置了这个标志,那新的广播会替换掉那些已存在的相同广播。相同的定义是通过Intent.filterEquals方法对两个广播的Intent处理返回true。 当匹配到相同的,新的广播和对应的接收器会将待发送的广播列表中已存在的替换掉,在列表中保留同样的位置。这个标志通常被粘性广播(Sticky Broadcast)使用,只保证将最新的广播的值传递给接收器。

以上内容为个人理解,大家有好的解释或者疑问,欢迎一起讨论学习。

作者:liu_ling1216 发表于2016/10/27 17:41:23 原文链接
阅读:42 评论:0 查看评论

吐血整理:VMware安装Linux系统以及运行Java项目

$
0
0

一、安装Linux系统

      工具:VMware  虚拟机

                 Linux镜像ISO文件

  1.点击创建新的虚拟机,如图操作,一直点击下一步;

















2.完成后,点击编辑虚拟机设置,加入镜像文件,然后开启虚拟机;







一路next,完成Linux系统初步安装。

3.Linux系统IP配置

打开终端,修改配置:

vi /etc/sysconfig/network-scripts/ifcfg-eth0,按照下图进行修改,IPADDR地址要与你本地IP在同一网段,比如我本地IP是192.168.10.1;修改完保存,

service network restart 重启网络,测试ping通即可



4.安装mysql(利用secureFXPortable工具有本地向虚拟机进行拷贝)

1、拷贝mysql-5.6.31-linux-glibc2.5-x86_64.tar.gz到/opt目录下



2、将压缩包解夺到/usr/local目录下

# tar -zxvf mysql-5.6.31-linux-glibc2.5-x86_64.tar.gz -C /usr/local

查看解压后的目录



修改文件夹名

# mv mysql-5.6.31-linux-glibc2.5-x86_64/ mysql

3、创建mysql账户(如已存mysql账户可先删除:# userdel -rf mysql)

# useradd mysql

4、修改mysql密码



5、更改/usr/local/mysql的拥有者权限

# chown -R mysql:mysql mysql/

6、切换到mysql账户

# su - mysql

7、安装数据库

$ cd /usr/local/mysql/scripts/

$ ./mysql_install_db --user=mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data

8、配置环境变量(/etc/profile)



# source /etc/profile

9、修改配置文件

$su - root

# cp /usr/local/mysql/support-files/my-default.cnf /etc/my.cnf

修改/etc/my.cnf,如下图:



配置系统服务

# cp /usr/local/mysql/support-files/mysql.server /etc/init.d/mysql

# chkconfig --add mysql

# chkconfig mysql on

10、启动mysql服务



11、设置mysql的root登录密码

# /usr/local/mysql/bin/mysqladmin -u root password 'root'

12、登录测试

# su - mysql

$ mysql -u root –p

出现如下图所示提示符,表示登录成功:



授权从本机以外的机器访问mysql



至此mysql安装配置完毕,并且可以从本机以外的任何机器以’root’密码访问mysql数据库。

5.创建软件所需数据库(利用Navicat创建数据库)

1、连接mysql数据库(如果此处链)



2、创建软件数据库



3、导入SQL文件

4、查看软件所需数据表的列表

6.JDK安装

  拷贝jdk-7u71-linux-x64.tar.gz到Linux系统,如下图:



1、创建JAVA目录

# mkdir java

# cd java

2、将JDK环境解压到/opt/java目录下

# tar -zxvf ../jdk-7u71-linux-x64.tar.gz

3、修改JDK文件访问权限

# chown -R root:root jdk1.7.0_71/

4、查看JDK版本



5、修改JAVA_HOME变量



6、让profile生效

# source/etc/profile

# echo $JAVA_HOME

7.Tomcat安装

1、拷贝apache-tomcat-7.0.67.zip到/opt下,如下图:

2、创建tomcat目录


# mkdir tomcat

# cd tomcat

3、将tomcat环境解压到/opt/tomcat目录下

# unzip ../apache-tomcat-7.0.67.zip

4、修改CATALINA_HOME变量



5、让profile生效

# source/etc/profile

# echo $CATALINA_HOME

6、修改/opt/tomcat/apache-tomcat-7.0.67/bin目录权限

# cd/opt/tomcat/apache-tomcat-7.0.67/bin

# chmod 755 *

8.将项目制作成war包部署上去即可运行。

作者:shaoshuai1989 发表于2016/10/27 17:49:42 原文链接
阅读:33 评论:0 查看评论

android launchMode理解以及应用场景

$
0
0

在我们写应用的时候,常常涉及多个activity组件之间的跳转。比如说某个资讯的页面中,点击下一篇资讯跳转相同的页面,只有页面的数据不一样。一般情况下我不会注意launchMode 这个属性,只会使用默认的,这样会产生大量重复的activity。那是因为之前不了解,所以特此研究学习。

1.如何指定launchMode

基本上我们可以直接指定一个launchMode属性在AndroidManifest.xml 文件中

<activity
    android:name=".views.MainActivity"
    android:screenOrientation="portrait"
    android:launchMode="singleTop"/>

存在4中类型的launchMode

这里写图片描述


2.standard 模式介绍

这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。

activity设置为这个模式的行为是一个新的活动,每一个意图发送,将总是被创建的工作分开。想象一下,如果有10的意图去写邮件,应该有10个活动的开展为每一个单独的意图。因此,有可能是一个无限数量的这种活动在一个设备启动。

2.1 5.0 版本之前

这种activity将被创建,并放置在堆栈的顶部,在同一个task,发送一个Intent。

图片链接

下面的一个图片显示当我们将一个图像分享到一个标准的activity时会发生什么。它将被堆叠在同一个任务中,虽然他们是从不同的应用程序。

图片链接

这是你将在任务管理器中看到的。(可能有点奇怪)

图片链接

如果我们换到另一个应用程序,然后再切换回Gallery,我们仍将看到顶上画廊的任务launchMode标准的地方。因此,如果我们需要做任何与Gallery相关的操作,我们必须完成当前activity的事情或者关闭当前的activity,才可以回到原来的地方。

2.2 5.0版本之后

如果这些活动都来自同一个应用程序,它能够像5.0之前一样,堆叠的任务

这里写图片描述

但是在某些情况下,我们发送的Intent 来自不同的intent,新的task将被创建,新创建的activity将被放置在下面的根activity中。

这里写图片描述

下面是我们重任务管理器中看到的和5.0之前是有区别的

这里写图片描述

这是因为任务管理系统的改进Lollipop使它更有意义。Lollipop,你可以切换回画廊,因为它们是不同的任务。你可以发射另一个意图,一个新的任务将被创建,以服务一个与前一个相同的意图。

这里写图片描述

2.3 应用场景举例

这种activity的一个例子是一个组成电子邮件activity或社交网络的状态张贴activity。如果你考虑一个可以单独工作的活动,为一个单独的意图服务。

举例引用:

AlarmClock uses standard. The user can launch multiple instances of this activity and these instances can be part of any task and anywhere in the activity stack. As a fairly simple application it doesn’t really demand tight control of its activity

闹钟的使用 standard。用户可以启动此activity的多个实例,这些实例可以是任何任务的一部分,也可以是活动堆栈中的任何地方的一部分。作为一个相当简单的应用,它并不真的需要它的activity的严格控制


3.singleTop 模式介绍

singleTop模式。它的作用几乎和standard一样。唯一不同的是,如果已经存在在栈顶在对方的任务一个同类型的活动实例,不会有任何新的activity创造,而是被发送到一个存在的activity实例通过onNewIntent() 方法的意图,即会重用该实例调用当前activity的onNewIntent() 方法。

这里写图片描述

在singleTop模式,对于新的Intent,你要负责onCreate() 和 onNewIntent() 来控制使它适用于所有的情况。

3.1 应用场景举例

这个模式的一个示例用例是一个搜索功能。我们想创造一个搜索框,它会带你到一个SearchActivity看到搜索结果。为了更好的用户体验,我们通常总是把一个搜索框,在搜索结果页面以及让用户做另一个搜索无压回。

现在想象一下,如果我们总是推出服务新的搜索结果的新searchactivity,10次搜索将产生10个新activity。这将是非常奇怪的,当你按下回来,因为你必须要10次,通过这些搜索结果activity,以获得回你的根activity。相反,如果在栈顶 searchactivity,我们最好送一个Intent的一个存在的activity实例,让它更新搜索结果。现在只会有一个searchactivity放在栈顶,你可以简单地按下按钮就回一次回到以前的活动。现在有更多的意义。

反正singleTop 作用相同的任务栈。如果你期望一个Intent被发送到一个存在的活动放置在任何其他任务的顶部,我必须让你失望,说它不工作。如果Intent是从另一个应用程序发送到singleTop activity,新的activity将推出在同一方面作为standard launchMode。

注意:5.0之前:放在对方的任务,5.0以及之后(Lollipop):一个新的任务被创建。

举例引用:

BrowserBookmarksPage uses singleTop. While there can be multiple instances of this activity, if there is already one at the top of the task’s activity stack it will be reused and onNewIntent() will be called. This way you only have to hit back once to return to the browser if the bookmarks activity is started multiple times.

浏览书签页面使用singleTop。虽然有可能是这一活动的多个实例,如果已经有一个在任务栈顶的活动将被重用和onnewintent()将被调用。这样,你只需要返回一次返回到浏览器,如果书签activity是开始多次。


4.singleTask 模式介绍

这种模式和 standard singleTop完全不同。采用singleTask launchMode 的activity是允许在系统中只有一个实例(又名Singleton)。如果系统中有一个存在的活动实例,整个任务将实例将被移动到顶部,Intent将通过onnewintent()方法交付。否则,新的activity将被创建并放置在适当的任务中。

4.1 在同一个应用中

如果没有在系统中还存在singleTask活动实例,新一将要建立简单的放在栈顶在同一个任务。

singleTask

但如果有一个存在的singleTask activity实例放在上面,会自动地破坏(以恰当的方式 生命周期触发)其他的activity,让该实例出现在栈顶。同时,一个Intent是通过的onnewintent()方法送到singleTask activity。

singleTask

在用户体验上没有一个很好的感觉,但它是这样设计的…

The system creates a new task and instantiates the activity at the root of the new task.
注意:系统创建一个新任务并实例化活动新任务的根。

但从实验中,它似乎不工作。单一任务活动仍栈顶上的任务的活动堆栈。从中我们可以看到什么dumpsys活动命令显示。

这里写图片描述


如果你想让一个活动就像描述singleTask文档:创建一个新的任务,把活动作为一根活动。你需要指定的taskAffinity属性为singleTask这样的活动。

这里写图片描述

下面是启动SingleTaskActivity 的结果示例

这里写图片描述

这里写图片描述

考虑是否使用taskAffinity的行为,这是你的工作。


4.2 与另一个应用合作

一旦一个 Intent 是从另一个应用程序发送,并且没有在系统中创建的任何活动实例,新的任务将创建一个新创建的活动作为一个根活动。

这里写图片描述

这里写图片描述

除非有一个应用程序,是一个拥有调用singleTask activity存在的任务,创造了新的活动会放在上面。

这里写图片描述

如果有任何任务存在的活动实例,整个任务将被移动到顶部和每个单独的activity在singleTask activity上的将会被销毁按照正常的生命周期。如果按下后退按钮,用户必须在返回到调用方任务之前通过堆栈中的活动进行。即先关闭图中task#1 中的activity,再回到task#2.

这里写图片描述


4.3 应用场景示例

这种模式的一个示例用例是任何一个入口点活动例如电子邮件客户端的收件箱页面或社交网络的时间轴。无论如何,你必须明智地使用这个模式,在这种模式下活动可能会被破坏。

BrowserActivity uses singleTask. There is only one browser activity at a time and it doesn’t become part tasks that send it intents to open web pages. While it might return to whatever most recently launched it when you hit back it is actually fixed at the bottom of its own task activity stack. It will share its task with activities that it launches like bookmarks

browseractivity使用singleTask。只有一个浏览器的活动的时间,它不成为一部分的任务,把它试图打开网页。虽然它可能会返回到任何最近推出的它,当你回击它实际上是固定在其自己的任务活动栈的底部。它将分享它的任务与活动,它推出像书签.


5.singleInstance 模式介绍

这种模式是相当接近singleTask,单个activity实例可以存在于系统中。不同的是任务举行这次活动,只能有一个活动。如果这种活动调用另一个活动,一个新的任务将被自动创建,以放置新的活动。同样,如果singleInstance活动被被调用,新的任务将会被创建放置这个activity。

无论如何,结果是相当奇怪的。从dumpsys提供的信息,它在系统中有两个任务,但只有一个出现在任务管理器中最新的一个决定,移动到顶部。因此,虽然有一个任务,仍然在后台工作,但我们不能切换到前台。根本没有任何意义。

这是singleInstance活动被调用同时一个activity已经存在在task 中。

singleInstance

但是在任务管理器中看不到新的task

singleInstance2

由于这项任务可能只有一个活动,我们无法切换回任务# 1了。这样做的唯一方法是重新启动的应用程序,但看来singleInstance任务将被隐藏在后台。

简单地分配taskAffinity属性到singleInstance活动使任务管理多个任务。

<activity
    android:name=".SingleInstanceActivity"
    android:label="singleInstance launchMode"
    android:launchMode="singleInstance"
    android:taskAffinity="">

这样将看到被隐藏的task

这里写图片描述

5.1 应用场景示例

AlarmAlert uses singleInstance. Only one alert activity at a time and it is always its own task. Anything it launches (if anything) becomes part of its own new task

alarmalert使用singleInstance。只有一个警报活动在一个时间,它总是自己的任务。它启动的任何东西(如果有的话)成为它自己的新任务的一部分

这种模式很少被使用。一些真正的用例是一个用于启动或应用程序的活动,你是100%肯定只有一个活动。总之,我建议你不要使用这种模式,除非它是真的有必要。


6.Intent Flags

launchMode是规定你自己的Activity启动的行为模式,而Intent.Flag是你期望由你启动的其他的Activity是什么样的行为模式

除了分配启动模式直接在AndroidManifest.xml,我们也能够通过所谓的意图标志更多的行为分配,例如:

Intent intent = new Intent(StandardActivity.this, StandardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);

7.总结以及参考资料

7.1 总结
[1] standard 模式
这是默认模式,每次激活Activity时都会创建Activity实例,并放入任务栈中。

[2] singleTop 模式
如果在任务的栈顶正好存在该Activity的实例,就重用该实例( 会调用实例的 onNewIntent() ),否则就会创建新的实例并放入栈顶,即使栈中已经存在该Activity的实例,只要不在栈顶,都会创建新的实例。

[3] singleTask 模式
如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的 onNewIntent() )。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移出栈。如果栈中不存在该实例,将会创建新的实例放入栈中。

[4] singleInstance 模式
在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。

最后,资料什么的都看完了,学习到了很多。最近在写项目会用到launchMode,将以前模糊不明白的地方,搞懂一些。磨刀不误砍柴工说的很对,所以先看资料学习,再通过项目练手加深印象。

7.2 参考资料

1.Understand Android Activity’s launchMode: standard, singleTop, singleTask and singleInstance

2.http://stackoverflow.com/questions/2626218/examples-for-android-launch-modes

3.基础总结篇之二:Activity的四种launchMode

4. android launchmode(四种启动模式)应用场景及实例

5.Android activity launchMode与Intent.Flag关系

作者:android_freshman 发表于2016/10/27 17:51:33 原文链接
阅读:136 评论:2 查看评论

Android Java / Kotlin 程序员开发调试工具。Debug Bottle 的所有功能均建立在 App 的 debug 版本中,不会对 release 版本产生任何影响。Debug Bo

$
0
0

debug-bottle

项目地址:kiruto/debug-bottle
简介:Android Java / Kotlin 程序员开发调试工具。Debug Bottle 的所有功能均建立在 App 的 debug 版本中,不会对 release 版本产生任何影响。Debug Bottle 旨在提高开发效率,把控 App 质量。

An Android debug / develop tools written using Kotlin language. All the features in Debug bottle are only available on debug build version with your app, it doesn't has an impact on release version.

Demo App is now available at Google Play:

Get it on Google Play

  

What can I do with Debug Bottle?

OkHttp Sniffer

Enable "Network Listener" at Settings, then you can see all network traffics what requested by your app.

 

Scalpel Viewer

Enable "3D View", then you can view your Activity. When interaction is enabled the following gestures are supported:

  • Single touch: Controls the rotation of the model.
  • Two finger vertical pinch: Adjust zoom.
  • Two finger horizontal pinch: Adjust layer spacing.

 

Shared Preferences editor

Preview and edit the Shared Preferences of app more simply.

 

Strict Mode

Enable or disable Android strict mode at runtime. StrictMode is a developer tool which detects things you might be doing by accident and brings them to your attention so you can fix them. StrictMode is most commonly used to catch accidental disk or network access on the application's main thread, where UI operations are received and animations take place. For more information, see Android Developers.

Crash Log

List all crash stacktrace logs.

Leak Canary

Leak Canary is fully imported. Leak Canary is a memory leak detection library for Android and Java. More about using Leak Canary by visiting Leak Canary wiki.

Block Canary

Detect UI blocks at runtime.

 

Development Entries

Launch any Activity with custom Intents, or Runnable you want.

 

How do I use it?

After installing Debug Bottle Demo app, you'll find there are two app icons appears to launcher. Click bottle icon to run Debug Bottle.

Setting up

1. Configure your Gradle project

Add snapshot of maven central repository to primary Gradle file:

allprojects {
    repositories {
        ...
        mavenCentral()
    }
}

Edit and add dependencies in your app module:

dependencies {
    debugCompile 'com.exyui.android:debug-bottle-runtime:1.0.4'

    // Use this in your Java project
    releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4'
    testCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4'

    // Use this in your Kotlin project
    releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4'
    testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4'

    compile 'com.android.support:appcompat-v7:23.2.0+'
}

Specially, Debug Bottle not only support API 23+, but also 22. To support API 22, please add dependencies like this:

dependencies {
    debugCompile 'com.exyui.android:debug-bottle-runtime:1.0.4-support22'

    // Use this in your Java project
    releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4-support22'
    testCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4-support22'

    // Use this in your Kotlin project
    releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4-support22'
    testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4-support22'

    compile 'com.android.support:appcompat-v7:22+'
}

To support API 23, add dependencies like this:

dependencies {
    debugCompile 'com.exyui.android:debug-bottle-runtime:1.0.4-support23'

    // Use this in your Java project
    releaseCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4-support23'
    testCompile 'com.exyui.android:debug-bottle-noop-java:1.0.4-support23'

    // Use this in your Kotlin project
    releaseCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4-support23'
    testCompile 'com.exyui.android:debug-bottle-noop-kotlin:1.0.4-support23'

    compile 'com.android.support:appcompat-v7:23+'
}

2. Edit Manifest

Add Debug Bottle main Activity in your Manifest:

<activity
    android:name="com.exyui.android.debugbottle.components.DTDrawerActivity"
    android:theme="@style/Theme.AppCompat.Light"
    android:label="The Name You Like"/>

The value of tag label, will display to your android launch pad.

3. Inject Debug Bottle into your Application

First, you may implement Block Canary Context:

public class AppBlockCanaryContext extends BlockCanaryContext {...}

Now you could inject Debug Bottle in your Application like:

public class MyApplication extends Application{
    @Override
    public void onCreate() {
        super.onCreate();
        DTInstaller.install(this)
            .setBlockCanary(new AppBlockCanaryContext(this))
            .setOkHttpClient(httpClient)
            .setInjector("your.package.injector.ContentInjector")
            .setPackageName("your.package")
            .enable()
            .run();
    }
}

Or if you use Kotlin, you can inject like:

class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        DTInstaller.install(this)
            .setBlockCanary(AppBlockCanaryContext(this))
            .setOkHttpClient(httpClient)
            .setInjector("your.package.injector.ContentInjector")
            .setPackageName("your.package")
            .enable()
            .run()
    }
}

License

Debug Bottle

Copyright 2016 Yuriel (http://exyui.com).

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Debug Bottle required features are based on or derives from projects below:

作者:u014608640 发表于2016/10/27 18:19:06 原文链接
阅读:50 评论:0 查看评论

AppBarLayout 介绍和简单实用

$
0
0

关于Android Design Support Library

​ 在Android 5.0 时出现了 Material Design 。瞬时感觉Android 更加牛B哄哄了,可是其它老版本的Android 怎么办呢?它们也行这么牛B哄哄,走到哪里都耀眼夺目。怎么办呢?Google很贴心的提供了Android Design Support Library,这样就可以支持 Android 2.1 以上的设备了。

​ Android Design Support Library 其实就是一个AAR包,里面包含了navigation drawer view, TextInputEditText,FloatingActionButton, Snackbar, TabLayout, CoordinatorLayout 等。

​ 今天我们介绍的是AppBarLayout这个控件。

AppBarLayout

​ AppBarLayout 继承自LinearLayout,子控件默认为竖直方向显示,可以用它实现Material Design 的Toolbar;它支持滑动手势;它的子控件可以通过在代码里调用setScrollFlags(int)或者在XML里app:layout_scrollFlags来设置它的滑动手势。当然实现这些的前提是它的根布局必须是 CoordinatorLayout。这里的滑动手势可以理解为:当某个可滚动View的滚动手势发生变化时,AppBarLayout内部的子View实现某种动作。

​ AppBarLayout的子控件不仅仅可以设置为Toolbar,也可以包含其他的View。

​ 下面我们具体来看看它的使用方法,我们先看以下的布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            app:title="标题"
            app:layout_scrollFlags="scroll|enterAlways"/>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/text"/>
    </<android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

可以看到在Toolbar上添加了一个app:layout_scrollFlags,并且把它的值设置为scroll|enterAlways 我们不说为什么我们先看一下实现的效果:

我们可以看到当我向上文字时,Toolbar移出屏幕,向下滑动Toolbar进入屏幕。这就是之前的说的滑动手势。

scroll flags

下面我们就看一下scroll flag 都包含哪些:

  1. scroll:设置这个flag之后 Toolbar会滚出屏幕外部,如果不设置这个Toolbar 会固定在顶部不动。

  2. enterAlways:这个flag跟scroll一块使用时,向上滑动时ToolBar移出屏幕,我们向下滑动时Toolbar进入屏幕。上面的图给出的就是scroll|enterAlways这个两个flag的效果。

  3. enterAlwaysCollapsed:enterAlways的附加值。这里涉及到Child View的高度和最小高度,向下滚动时,Child View先向下滚动最小高度值,然后Scrolling View开始滚动,到达边界时,Child View再向下滚动,直至显示完全。我们看一下效果:

具体的设置如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
            app:title="标题"
            />
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:minHeight="?android:attr/actionBarSize"
            android:background="@drawable/mt_head"
            app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
            />
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/text"/>

    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

首先它会先去找AppBarLayout的直接子View是否设置了minHeight;如果设置了我们的View下拉时,首先AppBarLayout会下拉到我们设定的minHeight,当我们下拉的View完全下拉完毕了,才会把AppBarLayout剩下的拉下来。

4.exitUntilCollapsed:这个跟上面的enterAlwaysCollapsed相反;它也涉及到minHeight,当发生向上滚动事件时,AppLayout向上滚动,直到我们设置的minHeight,然后我们的滑动View才开始滚动。就算我们滑动的view完全上滑完毕,我们的AppBarLayout也会一直保留我们设置的民Height显示在屏幕的上方。这个效果,我们一般会跟CollapsingToolbarLayout一块使用。具体我们看一下代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="?attr/colorPrimary"
            android:minHeight="?android:attr/actionBarSize"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:title="标题"
            />
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

然后我们再看一下显示效果:

5.snap:与scroll一起使用时,我们只需要滑动一小段距离,AppBarLayout就会随着我们滑动的View向上滑出屏幕。AppBarLayout会跟随着我们滑动View的滑动,当AppBarLayout滑出屏幕的部分大于显示区域,我们松开手指,AppBarLayout就会自动滑出屏幕;当AppBarLayout滑出屏幕的部分小于显示区域,我们松开手指,AppBarLayout就会自动滑进屏幕。

代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|snap"
            app:title="标题"
            />
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/nested_scrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

效果图如下:

注意:我们在使用这些flag时,一定要加上scroll,否则不会有效果。我们只需要在xml里设置就OK了,Java代码不需要设置就可以达到上面的效果。

作者:litengit 发表于2016/10/27 18:45:41 原文链接
阅读:28 评论:0 查看评论

CollapsingToolbarLayout 介绍和简单使用

$
0
0

​ 上次我们介绍了AppBarLayout,这一次我们介绍CollapsingToolbarLayout。

CollapsingToolbarLayout 介绍

​ 顾名思义,这是一个可折叠的Toolbar;不过它的使用必须在AppBarLayout的基础之上,它必须作为AppBarLayout的直接子类元素使用;否则起不到应用的效果。

​ 在Android Studio 里创建module/Activity时,就会提供一个ScrollActivity的模板,这个模板就使用了AppBarLayout 和 CollapsingToolbarLayout 实现了可折叠的Toolbar。这里就不在具体介绍如何去创建这个模板了。我们先看一下这个模板实现的大概效果:

ScrollingActivity模板效果

xml的设置如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:fitsSystemWindows="true"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:title="标题"
                />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"/>

</android.support.design.widget.CoordinatorLayout>  

首先我们看一下AppBarLayout ,它设置了app:layout_scrollFlags=”scroll|exitUntilCollapsed”,关于’exitUntilCollapsed’,会涉及到minHeight;当发生向上滚动事件时,AppLayout向上滚动,直到我们设置的minHeight,然后我们的滑动View才开始滚动。就算我们滑动的view完全上滑完毕,我们的AppBarLayout也会一直保留我们设置的民Height显示在屏幕的上方。但是我们并没有去设置minHeight呀?从表面上看确实是这样的。我们去看一下CollapsingToolbarLayout的源码来一探究竟。在onLayout方法里我们可以看到这么一段代码:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);

    // ……省略若干代码
    // Finally, set our minimum height to enable proper AppBarLayout collapsing
    if (mToolbar != null) {
        if (mCollapsingTitleEnabled && TextUtils.isEmpty(mCollapsingTextHelper.getText())) {
            // If we do not currently have a title, try and grab it from the Toolbar
            mCollapsingTextHelper.setText(mToolbar.getTitle());
        }
        if (mToolbarDirectChild == null || mToolbarDirectChild == this) {
            setMinimumHeight(getHeightWithMargins(mToolbar));
            mToolbarDrawIndex = indexOfChild(mToolbar);
        } else {
            setMinimumHeight(getHeightWithMargins(mToolbarDirectChild));
            mToolbarDrawIndex = indexOfChild(mToolbarDirectChild);
        }
    } else {
        mToolbarDrawIndex = -1;
    }
    updateScrimVisibility();
}

在上面代码里我们可以看到它Toolbar的高度作为minHeight了,当我们上滑时,滑到只剩下Toolbar高度时,CollapsingToolbarLayout 就不会再折叠了,会一直保持在屏幕的顶端。

app:layout_collapseMode

​ 除此之外,我们还可以发下在Toolbar里还设置了app:layout_collapseMode="pin",这里涉及到了折叠模式。我们依次了解一下.

  1. none:这个是默认属性,Toolbar会正常显示,不会有折叠行为;
  2. pin : Toolbar 折叠以后会放到屏幕顶端,固定不动;
  3. parallax:意为视差,当我们上滑时会有视差效果,但是我们之前设置在Toolbar上的东西会滑出屏幕。

具体我们示例:

当我们把折叠模式设置为 pin 时:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:fitsSystemWindows="true"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorAccent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:navigationIcon="@mipmap/ic_launcher"
                />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"/>

</android.support.design.widget.CoordinatorLayout>

为了看Toolbar的效果,我在Toolbar上加了一个navigationIcon;效果如下:

这里ToolBar的标题会有折叠效果,但是Toolbar会一直在屏幕的顶端不动。

接下来我们看 parallax:

上代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:fitsSystemWindows="true"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorAccent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="parallax"
                app:title="标题"
                app:navigationIcon="@mipmap/ic_launcher"
                />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"/>

</android.support.design.widget.CoordinatorLayout>

这里我们只改了一个app:layout_collapseMode,我们看一下效果:

这里只有折叠的效果,但是Toolbar会滑出屏幕。至于还有 none,就不在介绍了,没有任何效果。

app:contentScrim

​ 不知道有有没有细心同学看到这个app:contentScrim属性。这个是用来设置折叠时Toolbar所在位置的颜色,并且在滑动的时候会有一定的过渡。

刚开始研究的时候,这个属性老是不起作用,后来发现,当CollapsingToolbarLayout里只有的一个Toolbar时,会使用Theme指定的值。当我们在CollapsingToolbarLayout里添加其它的控件,并且这个控件必须是在Toolbar的上方时,这个属性才会起作用。 接下来我们看一下这个属性的效果。

先上代码:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorAccent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >
            <ImageView
                android:background="@drawable/header_bar"
                android:layout_width="match_parent"
                android:layout_height="260dp"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:title="标题"
                app:navigationIcon="@mipmap/ic_launcher"
                />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/text"/>
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@android:drawable/ic_dialog_email"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end"/>

</android.support.design.widget.CoordinatorLayout>

在Toolbar上面加了一个ImageView,然后在CollapsingToolbarLayout上设置了app:contentScrim=”?attr/colorAccent”,是一个粉红色。我们看效果:

这里写图片描述

关于CollapsingToolbarLayout的基本的几个点就差不多完了,我们需要注意的是:

  1. app:layout_scrollFlags 要放到AppBarLayout的直接子控件上
  2. app:contentScrim 要放到CollapsingToolbarLayout上
  3. app:contentScrim 这个属性,需要我们在CollapsingToolbarLayout里添加其它的控件,并且这个控件必须是在Toolbar的上方时,这个属性才会起作用
作者:litengit 发表于2016/10/27 18:52:59 原文链接
阅读:28 评论:0 查看评论

安卓开发之基于AccessibilityService实现聊天机器人对其他应用的调起

$
0
0

前言

前几天看到一个很有趣的应用视频“小不点”交互机器人,其中有一段是用户给它发一段文字/语音,譬如“我想在美团点一份鸡排”,然后“小不点”自动将美团应用弹出,并进行“鸡排”搜索等操作,如下图进行简化后的demo所示。

当时感觉到这样子的交互方式挺有趣的,在安卓上也有一定的方案可以实现,今天就基于AccessibilityService来实现了一下。(demo中省去一些自然语言处理的应用,最近也在学习这方面的知识)。

这里写图片描述

一、demo的简化

在demo中,省去了原本与机器人交互的输入框和对用户的自然语言处理,而直接摆放一个按钮来直接实现在美团应用中搜索鸡排。

  1. 简单的布局文件:按钮点击后会调用Activity中的 meituan 函数

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        tools:context="com.cxmscb.cxm.arobot.MainActivity">
    
        <Button
            android:onClick="meituan"
            android:textSize="16sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="我想在美团买鸡排" />
    </LinearLayout>
    
  2. 布局对应的Activity :

    public class MainActivity extends AppCompatActivity {
    
        // 用来获取获取打开美团的intent信使
        PackageManager packageManager ;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 获取安装包管理器
            packageManager = this.getPackageManager();
            // 用来让用户打开应用的辅助功能,这样辅助服务才会有效
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            startActivity(intent);
        }
    
        public void meituan(View view) {
            // 设置辅助服务为运行状态。这样做主要为了不破坏用户对应用的自主操作
            MyApplication.getInstance().setFlag(true);
            // 设置美团搜索的信息变量
            MyApplication.getInstance().setParams("鸡排");
    
            // 获取启动美团的intent
            Intent intent = packageManager.getLaunchIntentForPackage("com.sankuai.meituan");//"jp.co.johospace.jorte"就是我们获得要启动应用的包名
            // 每次启动美团应用时,但是以重新启动应用的形式打开
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
            // 跳转
            startActivity(intent);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            // 设置辅助服务为不运行状态。
            MyApplication.getInstance().setFlag(false);
        }
    }
    

二、AccessibilityService的应用

在 MainAcitivity 中,我们请求用户打开应用的ACCESSIBILITY服务,主要是为了应用的辅助服务能够生效。为了避免辅助服务随时随刻地运行影响到美团应用的正常使用,我们还在上面设置了个Flag。

  1. 为自定义的AccessibilityService的xml配置

    <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
        android:description="@string/accessibility_service_description"
        android:accessibilityEventTypes="typeAllMask"
        android:accessibilityFlags="flagDefault"
        android:accessibilityFeedbackType="feedbackGeneric"
        android:canRetrieveWindowContent="true"
        />
    

    其中 description 为 用户允许应用的辅助功能的说明字符串,这里没有指定所要辅助的应用 packageNames,当没有指定时,默认辅助所有的应用。typeAllMask 是设置响应事件的类型,feedbackGeneric 是设置回馈给用户的方式,有语音播出和振动。

  2. 对自定义的AccessibilityService在mainfest清单文件中的注册

       <service
            android:name=".MyAccessibilityService"
            android:label="服务机器人"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
    
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_service_config" />
        </service>
    

    其中label为用户在辅助功能列表中所看到的名称,注意加上对应的权限和action,并在meta-data中添加对AccessibilityService的配置。

  3. 自定义的AccessibilityService中的具体实现

    当启动应用的辅助功能后,有个后台服务将会手机界面进行事件监听,在进行一些函数回调,如 onAccessibilityEvent 函数,其中传入的就是当前发生的窗口事件,我们需要在其中对事件类型判断和对窗口中的布局进行解析获取指定布局中的控件。

    对于美团应用中的布局控件,我们可以使用DDMS来获取到,如下图以美团的第一个页面布局为例:

    这里写图片描述

    打开美团应用后,我们进行搜索时需要点击上方的“搜索商家…”控件。所以我们需要指定页面到来时,解析到控件,并对该控件(或其父布局)进行点击。其他页面类似。(也有一些页面中的控件需要输入文件)。

    从图中我们可以这个“搜索商家…”控件的类为TextView,我们可以指定“文字”和类来对该控件进行搜索。

    public class MyAccessibilityService extends AccessibilityService {
    
    
        // 当前事件发生的包名
        String nowPackageName;
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
        @Override
        public void onAccessibilityEvent(AccessibilityEvent event) {
    
            // 获取当前事件的包名
            nowPackageName = event.getPackageName().toString();
    
            // 判断是否为美团应用、并判断当前状态是否为运行状态
            if(nowPackageName.equals("com.sankuai.meituan")&&MyApplication.getInstance().getFlag()){
                // 判断是否为我们所需的窗口状态变化
                if(event.getEventType()==AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
                    // 获取事件活动的窗口布局根节点 
                    AccessibilityNodeInfo rootNode = this.getRootInActiveWindow();
                    // 解析根节点
                    handle(rootNode);
                }
            }
    
        }
    
        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
        private boolean handle(AccessibilityNodeInfo info) {
    
            // 判断节点是否有子控件
            if (info.getChildCount() == 0) {
                // 判断节点是否有文字并且有“搜索”文字
                if(info.getText() != null&&info.getText().toString().contains("搜索")){
    
                    // 美团的第一个页面布局:判断节点是否为TextView,文字是否匹配
                    if("搜索商家、品类或商圈".equals(info.getText().toString())&&"android.widget.TextView".equals(info.getClassName())){
                        // 判断节点控件是否可点击,不可点击则点击其父布局,以此类推。
                        AccessibilityNodeInfo parent = info;
                        while (parent!=null){
                            if(parent.isClickable()){
                                // 模拟点击,跳出循环
                                parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                                break;
                            }
                            parent = parent.getParent();
                        }
    
                    }  // 对第二个布局进行判断:判断是否为一个EidtText对象 
                    else if("搜索商家、品类或商圈".equals(info.getText().toString())&&"android.widget.EditText".equals(info.getClassName())){
    
                        // 以剪贴板的形式进行文字输入
                        ClipboardManager clipboardManager = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
                        ClipData clipData =ClipData.newPlainText("scb", MyApplication.getInstance().getParams());
                        clipboardManager.setPrimaryClip(clipData);
    
                        // 模拟粘贴
                        info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
    
                    } // 对第三个布局进行判断:判断是否为一个文字为“搜索”的TextView
                    else if("搜索".equals(info.getText().toString())&&"android.widget.TextView".equals(info.getClassName())){
    
                        // 以同样的原理进行控件点击
                        AccessibilityNodeInfo parent = info;
                        while (parent!=null){
                            if(parent.isClickable()){
                                parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                                break;
                            }
                            parent = parent.getParent();
                        }
    
                        // 服务进行结束后,设置Flag
                        MyApplication.getInstance().setFlag(false);
                        return true;
                    }else {
                        // 其他条件下设置Flag
                        MyApplication.getInstance().setFlag(false);
                    }
                }
    
            } else {
    
                // 当 当前节点 有子控件时,解析它的孩子,以此递归
                for (int i = 0; i < info.getChildCount(); i++) {
                    if(info.getChild(i)!=null){
                        handle(info.getChild(i));
                    }
                }
            }
            return false;
        }
    
        @Override
        public void onInterrupt() {
            Toast.makeText(this,"zz",Toast.LENGTH_SHORT).show();
        }
    }
    

    可以看出,上面的代码是按照美团的搜索过程理顺下来的逻辑。

三、思考

  1. 从代码中我们可以看出,对其他应用的调用过于依赖其应用,依赖于其布局,当其他应用的布局发生改变时,功能便无法生效了。

  2. 从代码中我们还可以看出,这种对其他应用的调用很难进行模块化,很容易导致代码过多,当支持调用的应用多时,代码也会增加很多。

  3. 对美团应用的调起过程中,太多中间的页面环节。

四、demo 代码

Github

作者:cxmscb 发表于2016/10/27 18:58:50 原文链接
阅读:135 评论:0 查看评论

开源App之MyHearts(二)

$
0
0

开源App之MyHearts(二)

前言

小弟技术有限,有的地方也是自己摸索出来的,可能和大神们写的好的代码没法比,但是我会努力的。要对自己说下,加油!!

此次更新

1、集成QQ登录完成

集成QQ登录网上写的介绍已经很多了,这里就不详细介绍,就写下自己遇到的坑。

  //QQ的初始化
        mTencent = Tencent.createInstance("app_id(申请的)", this.getApplicationContext());
        mInfo = new UserInfo(this, mTencent.getQQToken());

在点击登录按钮调用以下代码:

  mTencent.login(this, "all", loginListener);

    IUiListener loginListener = new BaseUiListener() {
        @Override
        protected void doComplete(JSONObject values) {
            Log.d(TAG, "ruolanmingyue:" + values);
            Log.d("SDKQQAgentPref", "AuthorSwitch_SDK:" + SystemClock.elapsedRealtime());
            initOpenidAndToken(values);

            //下面的这个必须放到这个地方,要不然就会出错   哎,,,,,调整了近一个小时,,,,我是服我自己了
            updateUserInfo();
        }
    };


    public static void initOpenidAndToken(JSONObject jsonObject) {
        try {
            String token = jsonObject.getString(Constants.PARAM_ACCESS_TOKEN);
            String expires = jsonObject.getString(Constants.PARAM_EXPIRES_IN);
            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) {
        }
    }

    private class BaseUiListener implements IUiListener {

        @Override
        public void onComplete(Object response) {
            if (null == response) {
                Util.showResultDialog(LoginActivity.this, "返回为空", "登录失败");
                return;
            }
            JSONObject jsonResponse = (JSONObject) response;
            if (null != jsonResponse && jsonResponse.length() == 0) {
                Util.showResultDialog(LoginActivity.this, "返回为空", "登录失败");
                return;
            }
            doComplete((JSONObject) response);
        }

        @Override
        public void onError(UiError e) {
            Util.toastMessage(LoginActivity.this, "onError: " + e.errorDetail);

        }

        @Override
        public void onCancel() {
            Util.toastMessage(LoginActivity.this, "onCancel: ");

        }

        protected void doComplete(JSONObject values) {

        }
    }

以上就能够吊起QQ客户端登录,然后获取用户信息(具体可以查看demo,最后会贴上地址的,不要急哈)

2、注册登录实现(利用Bmob作为后台,mob的短信验证)

在这里,mob的短信验证集成就不多说了,他们官方的demo也是比较详细的,代码量也比较多,贴在这里不雅,还不如直接git呢,哈哈,是不。
注册,我这里使用的是Bmob后台管理,详情可以查看,这里看下代码,应该是很好理解的。

 /**
     * 进行注册
     */
    private void doRegister() {
        String userName = mEditName.getText().toString().trim();
        final MyUser myUser = new MyUser();
        if (RegularUtils.isUsername(userName)) {
            myUser.setUsername(userName);
            myUser.setPassword(pwd);
            myUser.setMobilePhoneNumber(phone);
            addSubscription(myUser.signUp(new SaveListener<MyUser>() {
                @Override
                public void done(MyUser myUser, BmobException e) {
                    if (e == null) {
                        Toast.makeText(RegisterSecondActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
                        PreferencesUtils.putString(RegisterSecondActivity.this,Contants.USER_NAME,userName);
                        PreferencesUtils.putString(RegisterSecondActivity.this,Contants.USER_PASSWORD,pwd);
                        startActivity(new Intent(RegisterSecondActivity.this, LoginActivity.class));
                    } else {
                        //注册失败
                    }
                }
            }));
        }
    }
3、用户详情界面

用户详情界面,除了UI界面代码比较多,逻辑还是挺简单的,就是获取到当前用户。

每当你应用的用户注册成功或是第一次登录成功,都会在本地磁盘中有一个缓存的用户对象,这样,你可以通过获取这个缓存的用户对象来进行登录:
mCurrentUser = MyUser.getCurrentUser(MyUser.class);

然后就是根据这个用户对象,获取你需要展示的逻辑了。
这里就不多说,代码还是直接取git,down下来运行,会受益颇多,git界面最后又怎么导入项目的指导。

4、更新用户信息界面

对于更新用户界面,在这里,字段定义的比较多。大家可以看下。

  private Integer age;  //年龄
    private Integer num;  //
    private Boolean sex;  //性别

    private String imgurl;  //图片地址
    private String instance;  //地址
    private String profession;  //职业
    private String bloodtype;  //血型
    private String love;  //爱好
    private String des;  //描述
    private int userfans;  //fans数量
    private String constellation;  //星座
    private String label;  //标签

当然,对于用户名,密码,是在他的父类中进行了定义,(MyUser这个类要继承与BmobUser才能使用的哈)
对于年龄、性别这里使用了第三方弹框。项目地址
compile 'cn.qqtheme.framework:WheelPicker:1.1.2'
职业采用的第三方,依赖地址:

compile 'com.afollestad.material-dialogs:commons:0.9.0.2'

再次再次感谢此次用到的开源的项目的作者,没有他们的开源,也不会有这个MyHearts开源。
当获取到所有的已经编辑好的用户信息的时候,调用以下代码就可以完成更新

 String objectId = mCurrentUser.getObjectId();

  user.update(objectId, new UpdateListener() {
            @Override
            public void done(BmobException e) {
                if (e == null) {
                    CustomPrograss.disMiss();
                }
            }
        });

对于更新用户头像,可以查看下我之前的一个圆形图片,里面有启用相册或者相机的https://github.com/wuyinlei/CircleImgae
这里更新用户头像,使用的是Bmob的另一个类。BmobFile,这里看下代码

            //头像本地地址
            final BmobFile bmobFile = new BmobFile(new File(path));
            //
            bmobFile.uploadblock(new UploadFileListener() {

                @Override
                public void done(BmobException e) {
                    if (e == null) {
                        Toast.makeText(UserActivity.this, "pic is success", Toast.LENGTH_SHORT).show();
                        // MyUser myUser =MyUser.getCurrentUser(MyUser.class);
                        //得到上传的图片地址
                        String fileUrl = bmobFile.getFileUrl();
                        mCurrentUser.setImgurl(fileUrl);
                        //更新图片地址
                        mCurrentUser.update(mCurrentUser.getObjectId(), new UpdateListener() {
                            @Override
                            public void done(BmobException e) {
                                if (e == null) {
                                    Toast.makeText(UserActivity.this, "update", Toast.LENGTH_SHORT).show();

                                }
                            }
                        });
                    }
                }
            });

在这里有个比较重要的,本来用户城市,想要整一个三级联动,但是,想着是不是可以尝试以下,选择城市的那种,右侧有侧边栏的指引的,然后就实现了,不过在前期数据库写入本地的时候,出现了一些问题,造成写入,找不到数据库文件。还好最后解决了。代码过多,就不贴了。还是那一句话,down代码,自己运行,这样看着代码,运行着程序,如有不明白的自己调试,也可以和我交流。虽然这更新的不是挺多,但是代码量还是挺可观的(。。。。)

看下最近更新的功能吧,图形应该比较直观




代码传送门

https://github.com/wuyinlei/MyHearts

写在最后

基本上,本项目也就差不多了,其他的除了(即时通讯、直播),其他的页面逻辑都差不多,即时通讯,接下来就是自己学习的一个重点了,等学好了,有时间就会在次app基础上进行增加功能。直播也在以后接触之后,会慢慢更新。这也算是对自己的一个小总结,也是接下来重大任务的起始点。加油。如果有任何疑问,都可以进行交流。

作者:wuyinlei 发表于2016/10/27 19:16:12 原文链接
阅读:36 评论:0 查看评论

Android代码混淆ProGuard工作原理简介

$
0
0

ProGuard能够对Java类中的代码进行压缩(Shrink),优化(Optimize),混淆(Obfuscate),预检(Preveirfy)。
   1. 压缩(Shrink): 在压缩处理这一步中,用于检测和删除没有使用的类,字段,方法和属性。
   2. 优化(Optimize): 在优化处理这一步中,对字节码进行优化,并且移除无用指令。
   3. 混淆(Obfuscate): 在混淆处理这一步中,使用a,b,c等无意义的名称,对类,字段和方法进行重命名。
   4. 预检(Preveirfy): 在预检这一步中,主要是在Java平台上对处理后的代码进行预检。
   对于ProGuard执行流程图如下图所示: 这里写图片描述

ProGuard使用:

ProGuard已集成到Android构建系统中,所以我们不用手动调用这个工具。我们可以选择在只发布模式下构建系统的时候再去运行ProGuard。
  在AndroidStudio中我们需要将Proguard添加到gradle.build文件的构建类型当中。不过在我们创建一个Android工程的时候,系统已经自动为我们添加到了gradle.build中。

buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

minifyEnabled:开启混淆,我们新建的工程默认为false,因此,如果我们需要开启混淆的话就需要手动设为true。
proguardFiles:这部分有两段,前一段代表系统默认的android程序的混淆文件,该文件已经包含了基本的混淆声明,免去了我们很多事,这个文件的目录在/tools/proguard/proguard-android.txt , 后一部分是我们项目里的自定义的混淆文件,目录就在 app/proguard-rules.pro,在这个文件里我们可以声明一些我们所需要的定制的混淆规则。
 下面我们就来看一下这个默认的android程序混淆文件proguard-android.txt。对于下面一些指令与代码的含义在后面会进行说明:

# This is a configuration file for ProGuard.
 # http://proguard.sourceforge.net/index.html#manual/usage.html

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.

-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
    native ;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static ;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
-dontwarn android.support.**

# Understand the @Keep support annotation.
-keep class android.support.annotation.Keep

-keep @android.support.annotation.Keep class * {*;}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}

-keepclasseswithmembers class * {
    @android.support.annotation.Keep (...);
}

proguard-rules.pro配置

ProGuard基本指令

下面列举了我们开发中需要使用的一些指令:

#代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5

#混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames

#指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses

#这句话能够使我们的项目混淆后产生映射文件
#包含有类名->混淆后类名的映射关系
-verbose

#指定不去忽略非公共库的类
-dontskipnonpubliclibraryclassmembers

#不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify

#保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses

#避免混淆泛型
-keepattributes Signature

#抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

#指定混淆是采用的算法,后面的参数是一个过滤器
#这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*

需要保留的代码

下面列举了一些在我们的app开发中需要保留的内容:

#保留我们使用的四大组件,自定义的Application等等这些类不被混淆
#因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService

#保留support下的所有类及其内部类
-keep class android.support.** {*;}

#保留R下面的资源
-keep class **.R$* {*;}

#保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native ;
}

#保留在Activity中的方法参数是view的方法,
#这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

#保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}

#保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

#保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

#对于带有回调函数的onXXEvent的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
}

针对我们的App进行配置

1. 对于实体类我们不能混淆
  对于实力类我们不能进行混淆,我们需要保留他们的set和get方法。对于boolean类型的get方法为isXXX,不能够遗漏。在开发的时候我们可以将所有的实体类放在一个包内,这样我们写一次混淆就行了。

-keep public class com.ljd.example.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}

2. 在我们的app中使用了webView需要进行特殊处理
  在我们的项目中经常会嵌入一些webview做一些复杂的操作,这时候我们能够添加如下代码。

-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

3. 在app中与HTML5的JavaScript的交互进行特殊处理
  在我们的App中有时候需要与Html5中的JavaScript进行交互。例如:

package com.ljd.example;

public class JSInterface {
    @JavascriptInterface
    public void callAndroidMethod(){
        // do something
    }
}

我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:

-keepclassmembers class com.ljd.example.JSInterface {
    ;
}

4. 对含有反射类的处理

-keep class 类所在的包.** { *; }

5.对于第三方依赖库的处理
  这个取决于第三方的混淆策略。下面列举一些第三方依赖库的混淆策略。具体还以官方给出的为准:

#支付宝
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.**  { *; }

# Retrolambda
-dontwarn java.lang.invoke.*

#realm
-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep @io.realm.internal.Keep class * { *; }
-dontwarn javax.**
-dontwarn io.realm.**

# OrmLite
-keep class com.j256.**
-keepclassmembers class com.j256.** { *; }
-keep enum com.j256.**
-keepclassmembers enum com.j256.** { *; }
-keep interface com.j256.**
-keepclassmembers interface com.j256.** { *; }

#极光推送
-dontoptimize
-dontpreverify

-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }

#EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
    (java.lang.Throwable);
}

#retroift
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions

#ButterKnife
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }

-keepclasseswithmembernames class * {
    @butterknife.* ;
}

-keepclasseswithmembernames class * {
    @butterknife.* ;
}

#fastjson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }

#rxjava
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
 long producerIndex;
 long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
 rx.internal.util.atomic.LinkedQueueNode consumerNode;
}

#jackson
-dontwarn org.codehaus.jackson.**
-dontwarn com.fasterxml.jackson.databind.**
-keep class org.codehaus.jackson.** { *;}
-keep class com.fasterxml.jackson.** { *; }

#Facebook
-keep class com.facebook.** {*;}
-keep interface com.facebook.** {*;}
-keep enum com.facebook.** {*;}

#Fresco
-keep class com.facebook.fresco.** {*;}
-keep interface com.facebook.fresco.** {*;}
-keep enum com.facebook.fresco.** {*;}

在Android中配置ProGuard模板代码

对于ProGuard的配置在下面给出代码的通用部分,在使用的时候直接复制即可,之后根据自己的项目进行添加一些自己项目中所需要保留的内容,以及所依赖第三方库的处理即可。在下面代码中对于在proguard-android.txt中已经添加过得代码在这里可以选择忽略:

#############################################
#
# 对于一些基本指令的添加
#
#############################################
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-dontskipnonpubliclibraryclassmembers
-dontpreverify
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
-optimizations !code/simplification/cast,!field/*,!class/merging/*


#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
-keep class **.R$* {*;}
-keepclasseswithmembernames class * {
    native ;
}
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
-keepclassmembers class * {
    void *(**On*Event);
}

#webView处理,项目中没有使用到webView忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}

#############################################
#
# 项目中特殊处理部分
#
#############################################

#-----------处理反射类---------------

......

#-----------处理js交互---------------

......

#-----------处理实体类---------------

......

#-----------处理第三方依赖库---------

......

注意事项

1. 确保混淆不会对项目产生影响
  对于我们的Android项目最好是能够在一开始就是用ProGuard进行处理。并且在debug模式下也进行ProGuard处理,这样就能够在我们的开发中进行处理ProGuard所造成的影响。
   2. 打包时忽略警告
  在我们打包的时候,会发现很多could not reference class之类的warning信息。如果我们能够确认App在运行中和那些引用没有什么关系,可以添加-dontwarn标签。如-dontwarn org.apache.harmony.**。
  我们不要使用-ignorewarnings语句,这个会忽略所有警告,会有很多潜在的风险。

总结

对于ProGuard的使用不是很复杂,但是在我们的项目中使用ProGuard是必不可少的,它不仅仅能够对代码进行混淆,还能够优化我们的代码,减少我们apk文件的体积,从而优化我们的代码.

作者:ljw124213 发表于2016/10/27 19:39:24 原文链接
阅读:8 评论:0 查看评论

Java 集合深入理解(15):AbstractMap

$
0
0

点击查看 Java 集合框架深入理解 系列, - ( ゜- ゜)つロ 乾杯~

今天来了解下 AbstractMap。


这里写图片描述

什么是 AbstractMap

AbstractMap 是 Map 接口的的实现类之一,也是 HashMap, TreeMap, ConcurrentHashMap 等类的父类。

这里写图片描述

AbstractMap 提供了 Map 的基本实现,使得我们以后要实现一个 Map 不用从头开始,只需要继承 AbstractMap, 然后按需求实现/重写对应方法即可。

AbstarctMap 中唯一的抽象方法:

public abstract Set<Entry<K,V>> entrySet();

当我们要实现一个 不可变的 Map 时,只需要继承这个类,然后实现 entrySet() 方法,这个方法返回一个保存所有 key-value 映射的 set。 通常这个 Set 不支持 add(), remove() 方法,Set 对应的迭代器也不支持 remove() 方法。

如果想要实现一个 可变的 Map,我们需要在上述操作外,重写 put() 方法,因为 默认不支持 put 操作:

public V put(K key, V value) {
    throw new UnsupportedOperationException();
}

而且 entrySet() 返回的 Set 的迭代器,也得实现 remove() 方法,因为 AbstractMap 中的 删除相关操作都需要调用该迭代器的 remove() 方法。

正如其他集合推荐的那样,比如 AbstractCollection 接口 ,实现类最好提供两种构造方法:

  • 一种是不含参数的,返回一个空 map
  • 一种是以一个 map 为参数,返回一个和参数内容一样的 map

AbstractMap 的成员变量

transient volatile Set<K>        keySet;
transient volatile Collection<V> values;

有两个成员变量:

  • keySet, 保存 map 中所有键的 Set
  • values, 保存 map 中所有值的集合

他们都是 transient, volatile, 分别表示不可序列化、并发环境下变量的修改能够保证线程可见性。

需要注意的是 volatile 只能保证可见性,不能保证原子性,需要保证操作是原子性操作,才能保证使用 volatile 关键字的程序在并发时能够正确执行。

AbstractMap 的成员方法

AbstractMap 中实现了许多方法,实现类会根据自己不同的要求选择性的覆盖一些。

接下来根据看看 AbstractMap 中的方法。

1.添加

public V put(K key, V value) {
    throw new UnsupportedOperationException();
}

public void putAll(Map<? extends K, ? extends V> m) {
    for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
        put(e.getKey(), e.getValue());
}

可以看到默认是不支持添加操作的,实现类需要重写 put() 方法。

2.删除

public V remove(Object key) {
    //获取保存 Map.Entry 集合的迭代器
    Iterator<Entry<K,V>> i = entrySet().iterator();
    Entry<K,V> correctEntry = null;
    //遍历查找,当某个 Entry 的 key 和 指定 key 一致时结束
    if (key==null) {
        while (correctEntry==null && i.hasNext()) {
            Entry<K,V> e = i.next();
            if (e.getKey()==null)
                correctEntry = e;
        }
    } else {
        while (correctEntry==null && i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                correctEntry = e;
        }
    }

    //找到了,返回要删除的值
    V oldValue = null;
    if (correctEntry !=null) {
        oldValue = correctEntry.getValue();
        //调用迭代器的 remove 方法
        i.remove();
    }
    return oldValue;
}

//调用 Set.clear() 方法清除
public void clear() {
    entrySet().clear();
}

3.获取

//时间复杂度为 O(n)
//许多实现类都重写了这个方法
public V get(Object key) {
    //使用 Set 迭代器进行遍历,根据 key 查找
    Iterator<Entry<K,V>> i = entrySet().iterator();
    if (key==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (e.getKey()==null)
                return e.getValue();
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                return e.getValue();
        }
    }
    return null;
}

4.查询状态

//是否存在指定的 key
//时间复杂度为 O(n)
//许多实现类都重写了这个方法
public boolean containsKey(Object key) {
    //还是迭代器遍历,查找 key,跟 get() 很像啊
    Iterator<Map.Entry<K,V>> i = entrySet().iterator();
    if (key==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            //getKey()
            if (e.getKey()==null)
                return true;
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (key.equals(e.getKey()))
                return true;
        }
    }
    return false;
}


//查询是否存在指定的值
public boolean containsValue(Object value) {
    Iterator<Entry<K,V>> i = entrySet().iterator();
    if (value==null) {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            //getValue()
            if (e.getValue()==null)
                return true;
        }
    } else {
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            if (value.equals(e.getValue()))
                return true;
        }
    }
    return false;
}


public int size() {
    //使用 Set.size() 获取元素个数
    return entrySet().size();
}

public boolean isEmpty() {
    return size() == 0;
}

5.用于比较的 equals(), hashCode()

//内部用来测试 SimpleEntry, SimpleImmutableEntry 是否相等的方法
private static boolean eq(Object o1, Object o2) {
    return o1 == null ? o2 == null : o1.equals(o2);
}

//判断指定的对象是否和当前 Map 一致
//为什么参数不是泛型而是 对象呢
//据说是创建这个方法时还没有泛型 - -
public boolean equals(Object o) {
    //引用指向同一个对象
    if (o == this)
        return true;

    //必须是 Map 的实现类
    if (!(o instanceof Map))
        return false;
    //强转为 Map
    Map<?,?> m = (Map<?,?>) o;
    //元素个数必须一致
    if (m.size() != size())
        return false;

    try {
        //还是需要一个个遍历,对比
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            //对比每个 Entry 的 key 和 value
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                //对比 key, value
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

//整个 map 的 hashCode() 
public int hashCode() {
    int h = 0;
    //是所有 Entry 哈希值的和
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();
    return h;
}

6.获取三个主要的视图

获取所有的键:

public Set<K> keySet() {
    //如果成员变量 keySet 为 null,创建个空的 AbstractSet
    if (keySet == null) {
        keySet = new AbstractSet<K>() {
            public Iterator<K> iterator() {
                return new Iterator<K>() {
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public K next() {
                        return i.next().getKey();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object k) {
                return AbstractMap.this.containsKey(k);
            }
        };
    }
    return keySet;
}

获取所有的值:

public Collection<V> values() {
    if (values == null) {
        //没有就创建个空的 AbstractCollection 返回
        values = new AbstractCollection<V>() {
            public Iterator<V> iterator() {
                return new Iterator<V>() {
                    private Iterator<Entry<K,V>> i = entrySet().iterator();

                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    public V next() {
                        return i.next().getValue();
                    }

                    public void remove() {
                        i.remove();
                    }
                };
            }

            public int size() {
                return AbstractMap.this.size();
            }

            public boolean isEmpty() {
                return AbstractMap.this.isEmpty();
            }

            public void clear() {
                AbstractMap.this.clear();
            }

            public boolean contains(Object v) {
                return AbstractMap.this.containsValue(v);
            }
        };
    }
    return values;
}

获取所有键值对,需要子类实现:

public abstract Set<Entry<K,V>> entrySet();

AbstractMap 中的内部类

正如 Map 接口 中有内部类 Map.Entry 一样, AbstractMap 也有两个内部类:

  • SimpleImmutableEntry, 表示一个不可变的键值对
  • SimpleEntry, 表示可变的键值对

SimpleImmutableEntry,不可变的键值对,实现了 Map.Entry < K,V> 接口:

public static class SimpleImmutableEntry<K,V>
    implements Entry<K,V>, java.io.Serializable
{
    private static final long serialVersionUID = 7138329143949025153L;
    //key-value
    private final K key;
    private final V value;

    //构造函数,传入 key 和 value
    public SimpleImmutableEntry(K key, V value) {
        this.key   = key;
        this.value = value;
    }

    //构造函数2,传入一个 Entry,赋值给本地的 key 和 value
    public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
        this.key   = entry.getKey();
        this.value = entry.getValue();
    }

    //返回 键
    public K getKey() {
        return key;
    }

    //返回 值
    public V getValue() {
        return value;
    }

    //修改值,不可修改的 Entry 默认不支持这个操作
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    //比较指定 Entry 和本地是否相等
    //要求顺序,key-value 必须全相等
    //只要是 Map 的实现类即可,不同实现也可以相等
    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        return eq(key, e.getKey()) && eq(value, e.getValue());
    }

    //哈希值
    //是键的哈希与值的哈希的 异或
    public int hashCode() {
        return (key   == null ? 0 :   key.hashCode()) ^
               (value == null ? 0 : value.hashCode());
    }

    //返回一个 String
    public String toString() {
        return key + "=" + value;
    }

}

SimpleEntry, 可变的键值对:

public static class SimpleEntry<K,V>
    implements Entry<K,V>, java.io.Serializable
{
    private static final long serialVersionUID = -8499721149061103585L;

    private final K key;
    private V value;

    public SimpleEntry(K key, V value) {
        this.key   = key;
        this.value = value;
    }

    public SimpleEntry(Entry<? extends K, ? extends V> entry) {
        this.key   = entry.getKey();
        this.value = entry.getValue();
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    //支持 修改值
    public V setValue(V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        return eq(key, e.getKey()) && eq(value, e.getValue());
    }

    public int hashCode() {
        return (key   == null ? 0 :   key.hashCode()) ^
               (value == null ? 0 : value.hashCode());
    }

    public String toString() {
        return key + "=" + value;
    }

}

SimpleEntry 与 SimpleImmutableEntry 唯一的区别就是支持 setValue() 操作。

总结

AbstractCollection 接口AbstractList 接口 作用相似, AbstractMap 是一个基础实现类,实现了 Map 的主要方法,默认不支持修改。

常用的几种 Map, 比如 HashMap, TreeMap, LinkedHashMap 都继承自它。

Thanks

https://docs.oracle.com/javase/8/docs/api/java/util/AbstractMap.html

还有肉肉做的紫菜包饭,让我今天能量十足!

作者:u011240877 发表于2016/10/27 19:50:43 原文链接
阅读:23 评论:0 查看评论

7CollapsingToolbarLayout

$
0
0

7CollapsingToolbarLayout

CollapsingToolbarLayout是Toolbar的一个包装,可以做出很多很炫的折叠效果。

toolbar伸缩

toolbar伸展开加入图片背景,收缩时变会普通toolbar

Toolbar伸展

先从最简单的看起

   <android.support.design.widget.AppBarLayout
        android:fitsSystemWindows="true"
        android:layout_width="match_parent"
        android:layout_height="256dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsingToolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:minHeight="?attr/actionBarSize"
                app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

效果如下所示,toolbar可以伸展

如果想要最后的时候toolbar也留着,只要在app:layout_scrollFlags里加一个exitUntilCollapsed,效果如下,exitUntilCollapsed意思就是滑出直到折叠状态,即滑出的时候最多到折叠状态,无法完全滑出

此时有个问题,右上角菜单不见了,实际上是滑出去了,我们要想保留,就得让toolbar不滑动,pin住就行了(Toolbar设置app:layout_collapseMode=”pin”),让toolbar不滑动。效果如下,此时效果已经不错了,上滑过程中,标题逐渐变小,然后跑到toolbar里去.此时如果要修改伸展状态下text的位置可以用app:expandedTitleMargin, app:expandedTitleMarginBottom, app:expandedTitleMarginEnd and app:expandedTitleMarginStart。

Toolbar背景图

case1

如何为Toolbar添加个背景图呢?
因为CollapsingToolbarLayout是FrameLayout所以,直接在里面加ImageView就可以了(加backgroud行不行?)
注意此时把toolbar背景去掉,删掉这句android:background=”?attr/colorPrimary”
然后CollapsingToolbarLayout内加入加入app:contentScrim=”?attr/colorPrimary”
这么做,伸展开的时候背景就是图片,收缩的时候背景就是纯色

case2

此时发现图片不会滑到状态栏上,那是因为状态栏没有设置透明,给activity换个style用AppTheme.NoActionBar之后,就可以滑到状态栏了。效果如下

case3

此时,初始态未覆盖状态栏,滑动的时候可以覆盖到状态栏,想要初始态就覆盖状态栏,怎么办?
给ImageView加上fitSystemWindow,效果如下

case4

app:layout_collapseMode=”parallax” 可以让图片和AppBarLayout一起滑动

最后xml代码如下

“` xml

<android.support.design.widget.AppBarLayout
    android:fitsSystemWindows="true"
    android:layout_width="match_parent"
    android:layout_height="256dp">

    <android.support.design.widget.CollapsingToolbarLayout
        android:id="@+id/collapsingToolbarLayout"
        app:contentScrim="?attr/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">

        <ImageView
            android:src="@drawable/boygirl"
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            app:layout_collapseMode="parallax" />

        <android.support.v7.widget.Toolbar
            app:layout_collapseMode="pin"
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:minHeight="?attr/actionBarSize"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    </android.support.design.widget.CollapsingToolbarLayout>

</android.support.design.widget.AppBarLayout>

“`

上面这2个gif,style都是AppTheme,如果换成AppTheme.NoActionBar,就会导致状态栏透明,但是此时初始态,ImageView不会占据状态栏,此时如果把CollapsingToolbarLayout和ImageView设置为fitsystemwindow那就可以占据状态栏了,真神奇。

enterAlways

enterAlways: 一旦下滑就会让view逐步显示出来. 当我们滑到一个list的底部的时候,想要下滑尽快看到toolbar的时候,用这个模式很有用。

一般情况下,我们想要toolbar显示出来,得先滑到list的顶部才行,如下所示

如果设置了enterAlways,就会如下所示,可以看到toolbar立刻就出来了

enterAlwaysCollapsed

但是如果使用enterAlwaysCollapsed,注意此时的layout_scrollFlags必须写了scroll、enterAlways、enterAlwaysCollapsed,
这样可以实现,一旦下滑就把我们的view拉出minimum height,然后list到顶的时候,可以把整个view拉出来。如下所示。(我试了下没做出这样的效果)

snap

snap的作用就是让CollapsingToolbarLayout要么完全展开,要么完全收缩,根据手指抬起的时候,滚动的距离有没有一半来判断

收缩态

CollapsingToolbarLayout有收缩状态和伸展状态。默认为伸展状态,可以任意写布局。收缩状态只有3个元素,一条伪状态栏,一条content,一段文字。可以用contentScrim指定收缩时内容的颜色,statusBarScrim可以指定收缩时状态栏的颜色(也不能说是状态栏,是在CollapsingToolbarLayout可见区域的顶部绘制一条bar,我们要用代码控制保证和状态栏重叠)。

           app:contentScrim="@color/color_accent_pink"
           app:statusBarScrim="@color/cardview_light_background"

参考资料

http://blog.csdn.net/u010687392/article/details/46906657
https://guides.codepath.com/android/Handling-Scrolls-with-CoordinatorLayout
https://developer.android.com/reference/android/support/design/widget/CollapsingToolbarLayout.html

作者:litefish 发表于2016/10/27 19:52:11 原文链接
阅读:7 评论:0 查看评论

android 特卖列表倒计时卡顿问题

$
0
0

在Android的开发中,我们经常遇见倒计时的操作,通常使用Timer和Handler共同操作来完成。当然也可以使用Android系统控件CountDownTimer,这里我们封装成一个控件,也方便大家的使用。

首先上一张效果图吧:


说一下造成卡顿的原因,由于滑动的时候,adapter的getView频繁的创建和销毁,就会出现卡顿和数据错位问题,那么我们每一个item的倒计时就需要单独维护,这里我用的Handler与timer及TimerTask结合的方法,我们知道TimerTask运行在自己子线程,然后通过Timer的schedule()方法实现倒计时功能,最后通过Hander实现View的刷新,其核心代码如下:

public class CountDownView extends LinearLayout {

    @BindView(R.id.tv_day)
    TextView tvDay;
    @BindView(R.id.tv_hour)
    TextView tvHour;
    @BindView(R.id.tv_minute)
    TextView tvMinute;
    @BindView(R.id.tv_second)
    TextView tvSecond;

    @BindView(R.id.day)
    TextView day;
    @BindView(R.id.hour)
    TextView hour;
    @BindView(R.id.minute)
    TextView minute;

    private Context context;

    private int viewBg;//倒计时的背景
    private int cellBg;//每个倒计时的背景
    private int cellTextColor;//文字颜色
    private int textColor;//外部:等颜色
    private int textSize = 14;//外部文字大小
    private int cellTextSize = 12;//cell文字大小

    private TimerTask timerTask = null;
    private Timer timer = new Timer();
    private Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            countDown();
        }
    };

    public CountDownView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
        this.context = context;
    }

    public CountDownView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.context = context;
        initAttrs(attrs, defStyleAttr);
        initView(context);
    }

    private void initAttrs(AttributeSet attrs, int defStyle) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CountDownView, defStyle,0);
        viewBg = typedArray.getColor(R.styleable.CountDownView_viewBg, Color.parseColor("#FFFFFF"));
        cellBg = typedArray.getColor(R.styleable.CountDownView_cellBg, Color.parseColor("#F4F4F4"));
        cellTextColor = typedArray.getColor(R.styleable.CountDownView_cellTextColor, Color.parseColor("#646464"));
        textColor = typedArray.getColor(R.styleable.CountDownView_TextColor, Color.parseColor("#B3B3B3"));
        textSize = (int) typedArray.getDimension(R.styleable.CountDownView_TextSize, UIUtils.dp2px(getContext(), 14));
        cellTextSize = (int) typedArray.getDimension(R.styleable.CountDownView_cellTextSize, UIUtils.dp2px(getContext(), 12));
        typedArray.recycle();

    }

    private void initView(Context context) {
        LayoutInflater inflater = (LayoutInflater) context
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.layout_countdown_layout, this);
        ButterKnife.bind(view);

        initProperty();
    }

    private void initProperty() {
        tvDay.setBackgroundColor(cellBg);
        tvHour.setBackgroundColor(cellBg);
        tvMinute.setBackgroundColor(cellBg);
        tvSecond.setBackgroundColor(cellBg);

        tvDay.setTextColor(cellTextColor);
        tvHour.setTextColor(cellTextColor);
        tvMinute.setTextColor(cellTextColor);
        tvSecond.setTextColor(cellTextColor);

        day.setTextColor(textColor);
        hour.setTextColor(textColor);
        minute.setTextColor(textColor);
    }


    public void setLeftTime(long leftTime) {
        if (leftTime <= 0) return;
        long time = leftTime / 1000;
        long day = time / (3600 * 24);
        long hours = (time - day * 3600 * 24) / 3600;
        long minutes = (time - day * 3600 * 24 - hours * 3600) / 60;
        long seconds = time - day * 3600 * 24 - hours * 3600 - minutes * 60;

        setTextTime(time);
    }


    public void start() {
        if (timerTask == null) {
            timerTask = new TimerTask() {
                @Override
                public void run() {
                    handler.sendEmptyMessage(0);
                }

            };
            timer.schedule(timerTask, 1000, 1000);
//            timer.schedule(new TimerTask() {
//                @Override
//                public void run() {
//                    handler.sendEmptyMessage(0);
//                }
//            }, 0, 1000);
        }
    }

    public void stop() {
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    //保证天,时,分,秒都两位显示,不足的补0
    private void setTextTime(long time) {
        String[] s = TimeUtils.formatTimer(time);
        tvDay.setText(s[0]);
        tvHour.setText(s[1]);
        tvMinute.setText(s[2]);
        tvSecond.setText(s[3]);
    }

    private void countDown() {
        if (isCarry4Unit(tvSecond)) {
            if (isCarry4Unit(tvMinute)) {
                if (isCarry4Unit(tvHour)) {
                    if (isCarry4Unit(tvDay)) {
                        stop();
                    }
                }
            }
        }
    }

    private boolean isCarry4Unit(TextView tv) {
        int time = Integer.valueOf(tv.getText().toString());
        time = time - 1;
        if (time < 0) {
            time = 59;
            tv.setText(time + "");
            return true;
        } else if (time < 10) {
            tv.setText("0" + time);
            return false;
        } else {
            tv.setText(time + "");
            return false;
        }
    }
}
移动技术交流(Android,ios,RactNtive),请加群:278792776
附上源码地址:点击打开链接


作者:xiangzhihong8 发表于2016/10/27 20:36:09 原文链接
阅读:0 评论:0 查看评论

iOS 10适配以及Xcode8兼容问题总结

$
0
0

http://www.cnblogs.com/godlovexq/p/5885212.html
代码注释不能用的解决办法
这个是因为苹果解决xcode ghost,把插件屏蔽了。
解决方法
打开终端,命令运行: sudo /usr/libexec/xpccachectl
然后必须重启电脑后生效

注意:Xcode8内置了开启注释的功能,位置在这里


屏蔽杂乱无章的bug
更新Xcode8之后,新建立工程,都会打印一堆莫名其妙看不懂的Log.
如这些

subsystem: com.apple.UIKit, category: HIDEventFiltered, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1,
屏蔽的方法如下:
Xcode8里边 Edit Scheme-> Run -> Arguments, 在Environment Variables里边添加字典:
(name)OS_ACTIVITY_MODE = (value:)Disable
这里写图片描述
在设置log选项的时候,发现可以通过在Arguments中设置参数,打印出App加载的时长,包括整体加载时长,动态库加载时长等。
在Environment Variables中添加DYLD_PRINT_STATISTICS字段,并设置为YES,在控制台就会打印加载时长。
考虑到添加上述内容在Xcode8后,真机调试可能出现异常,大家可以自定义一个宏定义,来做日志输出。

#ifdef DEBUG
#define DDLOG(…) printf(” %s\n”,[[NSString stringWithFormat:VA_ARGS]UTF8String]);

#define DDLOG_CURRENT_METHOD NSLog(@”%@-%@”, NSStringFromClass([self class]), NSStringFromSelector(_cmd))

#else

#define DDLOG(…) ;
#define DDLOG_CURRENT_METHOD ;

#endif


字体适配的问题

ios 9 之前的lab 字体可以显示全,但是到了ios10 发觉字体显示不全了.得适配啊.app 会跟随手机系统字体大小而改变了.

简单粗暴地方法就是不让他跟着手机系统的字体改变而改变.
myLabel.font =[UIFont preferredFontForTextStyle: UIFontTextStyleHeadline];
// UIFont 的preferredFontForTextStyle: 意思是指定一个样式,并让字体大小符合用户设定的字体大小。
/*
Indicates whether the corresponding element should automatically update its font when the device’s UIContentSizeCategory is changed.
For this property to take effect, the element’s font must be a font vended using +preferredFontForTextStyle: or +preferredFontForTextStyle:compatibleWithTraitCollection: with a valid UIFontTextStyle.
*/
//是否更新字体的变化
label.adjustsFontForContentSizeCategory = YES;
字体变大,原有frame需要适配
发现程序内Label标签原来2个字的宽度是24,现在2个字需要27的宽度来显示了。。


iOS 10 开始的通知
1.所有相关通知被统一到了UserNotifications.framework框架中。
2.增加了撤销、更新、中途还可以修改通知的内容。
3.通知不在是简单的文本了,可以加入视频、图片,自定义通知的展示等等。
4.iOS 10相对之前的通知来说更加好用易于管理,并且进行了大规模优化,对于开发者来说是一件好事。
5.iOS 10开始对于权限问题进行了优化,申请权限就比较简单了(本地与远程通知集成在一个方法中)。

所有的推送平台,不管是极光还是什么的,要想收到推送,这个是必须打开的
这里写图片描述

  • (void)userNotificationCenter:(UNUserNotificationCenter )center willPresentNotification:(UNNotification )notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    //应用在前台收到通知 NSLog(@”========%@”, notification);
    }
    推送的代理iOS10收到通知不再是在[application: didReceiveRemoteNotification:]方法去处理, iOS10推出新的代理方法,接收和处理各类通知(本地或者远程)
  • (void)userNotificationCenter:(UNUserNotificationCenter )center didReceiveNotificationResponse:(UNNotificationResponse )response withCompletionHandler:(void (^)())completionHandler {
    //点击通知进入应用 NSLog(@”response:%@”, response);
    }


    xcode8的注释快捷键注释不能用了, command+/ 不行了
    解决办法:
    因为苹果解决xcode ghost。把插件屏蔽了。解决方法
    命令运行: sudo /usr/libexec/xpccachectl
    然后必须重启电脑后生效
    这里写图片描述


颜色问题, iOS 10 苹果官方建议我们使用sRGB,因为它性能更好,色彩更丰富。
UIColor类中新增了两个Api如下:
+ (UIColor *)colorWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);
- (UIColor *)initWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);


判断版本问题
判断系统版本是我们经常用到的,尤其是现在大家都有可能需要适配iOS 10,那么问题就出现了,如下图:
我们得到了答案是:
//值为 1
[[[[UIDevice currentDevice] systemVersion] substringToIndex:1] integerValue]
//值为10.000000
[[UIDevice currentDevice] systemVersion].floatValue,
//值为10.0
[[UIDevice currentDevice] systemVersion]
所以说判断系统方法最好还是用后面的两种方法,哦~我忘记说了[[UIDevice currentDevice] systemVersion].floatValue这个方法也是不靠谱的,好像在8.3版本输出的值是8.2,记不清楚了反正是不靠谱的,所以建议大家用[[UIDevice currentDevice] systemVersion]这个方法!
Swift判断如下:
if #available(iOS 10.0, *) {
print(“iOS 10.0”); // iOS 10.0啊
} else{ };


https的问题
iOS 9中默认非HTTS的网络是被禁止的,当然我们也可以把NSAllowsArbitraryLoads设置为YES禁用ATS。不过iOS 10从2017年1月1日起苹果不允许我们通过这个方法跳过ATS,也就是说强制我们用HTTPS,如果不这样的话提交App可能会被拒绝。但是我们可以通过NSExceptionDomains来针对特定的域名开放HTTP可以容易通过审核。


隐私权限

iOS 10 开始对隐私权限更加严格,如果你不设置就会直接崩溃,现在很多遇到崩溃问题了,一般解决办法都是在info.plist文件添加对应的Key-Value就可以了。
这里写图片描述


NSPhotoLibraryUsageDescription
App需要您的同意,才能访问相册

NSCameraUsageDescription
App需要您的同意,才能访问相机

NSMicrophoneUsageDescription
App需要您的同意,才能访问麦克风

NSLocationUsageDescription
App需要您的同意,才能访问位置

NSLocationWhenInUseUsageDescription
App需要您的同意,才能在使用期间访问位置

NSLocationAlwaysUsageDescription
App需要您的同意,才能始终访问位置

NSCalendarsUsageDescription
App需要您的同意,才能访问日历

NSRemindersUsageDescription
App需要您的同意,才能访问提醒事项

NSMotionUsageDescription App需要您的同意,才能访问运动与健身

NSHealthUpdateUsageDescription
App需要您的同意,才能访问健康更新

NSHealthShareUsageDescription
App需要您的同意,才能访问健康分享

NSBluetoothPeripheralUsageDescription
App需要您的同意,才能访问蓝牙

NSAppleMusicUsageDescription
App需要您的同意,才能访问媒体资料库


权限以及相关设置

iOS10调用相册会Crash下面信息:

This app has crashed because it attempted to access privacy-sensitive data without a usage description.
The app’s Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data.
大体意思就是这个App缺少一个获取私有(敏感)数据的权限描述,需要我们在info.plist文件中必须含有一个名字叫做NSPhotoLibraryUsageDescription的值来解释为什么应用需要使用这个数据,没错,获取相册资源的键值就是NSPhotoLibraryUsageDescription

去plist文件中添加了下面的键值:
这里写图片描述
再点击获取图片资源,就弹出了一个获取权限的问候,不会发生崩溃了:
这里写图片描述

Privacy - Microphone Usage Description //麦克风权限
Privacy - Contacts Usage Description //通讯录权限
Privacy - Camera Usage Description //摄像头权限
Privacy - NSSiriUsageDescription //Siri的权限
Privacy - Bluetooth Peripheral Usage Description //蓝牙
Privacy - Reminders Usage Description //提醒事项
Privacy - Motion Usage Description //运动与健康
Privacy - Media Libaray Usage Description //媒体资源库
Privacy - Calendars Usage Description //日历


xib设定好固定尺寸在代码中获取控件尺寸都变成(0,0,1000,1000)
UIView中要从- (void)updateConstraints或者- (void)drawRect:(CGRect)rect获取控件尺寸。

  • (void)updateConstraints
    {
    [super updateConstraints];
    }

  • (void)drawRect:(CGRect)rect
    {
    [super drawRect:rect];
    }
    UIViewController中要从- (void)viewDidLayoutSubviews获取控件尺寸。

  • (void)viewDidLayoutSubviews
    {
    [super viewDidLayoutSubviews];
    }


Xcode 8使用Xib awakeFromNib的警告问题

在Xcode 8之前我们使用Xib初始化- (void)awakeFromNib {}都是这么写也没什么问题,但是在Xcode 8会有如下警告:
这里写图片描述
如果不喜欢这个警告的话,应该明确的加上[super awakeFromNib];我们来看看官方说明:


隐藏状态栏的功能坏掉:
升级到 iOS 10.0后,在查看全屏图片的时候,需要在 Present 之前给要 present 的 view controller 设置 modalPresentationCapturesStatusBarAppearance = true。然后就好啦
TestViewController *testVC = [[TestViewController alloc] init];
testVC.modalPresentationCapturesStatusBarAppearance = true;
[self presentViewController:testVC animated:YES completion:nil];


//iOS 10 状态栏的设置
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault;
}
************iOS 10 UITextContentType
// The textContentType property is to provide the keyboard with extra information about the semantic intent of the text document.

@property(nonatomic,copy) UITextContentType textContentType NS_AVAILABLE_IOS(10_0); // default is nil
在iOS 10 UITextField添加了textContentType枚举,指示文本输入区域所期望的语义意义。

使用此属性可以给键盘和系统信息,关于用户输入的内容的预期的语义意义。例如,您可以指定一个文本字段,用户填写收到一封电子邮件确认 uitextcontenttypeemailaddress。当您提供有关您期望用户在文本输入区域中输入的内容的信息时,系统可以在某些情况下自动选 择适当的键盘,并提高键盘修正和主动与其他文本输入机会的整合。


UIScrollView自带刷新功能
这里写图片描述
iOS 10 以后只要是继承UIScrollView那么就支持刷新功能:

@property (nonatomic, strong, nullable) UIRefreshControl *refreshControl NS_AVAILABLE_IOS(10_0) __TVOS_PROHIBITED;


ImagePickerController.cameraViewTransform问题
(本条更新于:2016-09-21) 很多人反映自定义相机出现了问题,cameraViewTransform不能用了,其实网上关于这个的资料不是很多,在这里提供参考办法如下:

通过监听AVCaptureSessionDidStartRunningNotification来解决

//#import

作者:qq_24976099 发表于2016/10/28 16:42:17 原文链接
阅读:20 评论:0 查看评论

推送技术哪家强?

$
0
0
各位看官,
  目前我家App的推送功能在Android平台一直表现不佳,目前有3家名气还不错的推送单位待选。

       个推(杭州本土企业,推送行业里历史悠久)
  
       极光(号称中国最大推送平台)

       友盟(一个做统计起家的后起之秀)


       个个都吹的好牛逼,在下表示好迷茫。
       
       到底哪家强?咱们不妨都拉出来做几组测试来看下
   

 首先,我们来做一下以下3种场景的测试。
   1.App至于前台进行推送测试。
   2.App切到后台但不锁屏进行推送测试。
   3.App切到后台切锁屏5分钟后进行推送测试。

   测试机器: 
   1. 小米max Android6.0系统,最新版MUI
   2. 华为荣耀 Android5.0系统,较旧版华为ROM

   在这里要表扬一下个推同学,在网页端创建应用后立即生成demo apk,调试起来很方便,一百个赞

   好了,接下来,我们先进行一下第一轮测试。在两台手机中都打开推送demo

   测试结果一切正常,均能正常收到推送消息,不错。目前来看表现都很牛逼
   
  

   No.2  我们来进行第二轮测试,将2台手机中的推送进程切换到后台

  
   
   
  very good!,正常的两组场景下的测试双方皆表现不错。
  
      


   好的,考验水平的时候到了,我们来一下第三轮测试。将两台手机中的推送进程皆切换到后台,切锁屏5分钟后进行推送测试。
   
  差距出来了,look
  

     


   惊呼! 友盟在第三轮测试中虽然华为荣耀没有收到,但在小米MAX上的坚挺另在下实在佩服佩服。
   至此,友盟在推送的性能上已经迈出了人类的一大步。

   友盟君,why are you so diao ?
   
   查看了一下友盟的最近更新说明
   

     

    
   嗦嘎,原来友盟做了3个比较牛逼的更新
   1.对推送通道进行了多进程管理,相互独立,性能稳定提升。
   2.与国内安卓平台合作,将推送进程与定制厂商的系统进程绑定,提高坚挺性能。
   3.针对Android高版本的GC回收机制做了优化,减少进程被误杀的概率。

   
    而且,仔细查看友盟推送的混淆文档发现如下内容:
    

    厉害了我的哥!看来友盟成为小米和华为的亲儿子是真事。
    
    这是近期与华为小米合作的表现,可能第三轮测试中华为荣耀没有收到友盟 推送的原因是因为ROM太旧,
    华为还未将友盟推送进程加进自己的白名单中,这个是猜测,接下来将会用华为最新版ROM来验证这个问题。
 
    目前只做了锁屏5分钟的测试,小米上友盟推送是坚挺的,接下来会做10分钟,20分钟,30分钟,1小时锁屏甚至更久的锁屏推送测试。

    现在看来友盟推送已经有了突破。3家当中我给友盟投一票,大家给谁投一票呢?
作者:soulline 发表于2016/10/28 16:55:32 原文链接
阅读:118 评论:0 查看评论

IO流之字符流

$
0
0

学习导航

第二节:IO流之字节流http://blog.csdn.net/bobo8945510/article/details/52958491


IO流之基础讲解

一、什么是IO流?

这里写图片描述

二、字节流和字符流的区别?

这里写图片描述

三、IO流的分叉图!

这里写图片描述


四、字符流InputStreamReader和OutputStreamWriter!

一。InputStreamReader 是字符流Reader的子类,是字节流通向字符流的桥梁。你可以在构造器重指定编码的方式,如
果不指定的话将采用底层操作系统的默认编码方式,例如 GBK 等

1、要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
2、为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
3、BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

二。OutputStreamWriter 是字符流Writer的子类,是字符流通向字节流的桥梁。

1、每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积
2、为了获得最高效率,可考虑将 OutputStreamWriter 包装到 BufferedWriter 中,以避免频繁调用转换器。例如:
3、BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

四—1:字符流之:使用InputStreamReader读取一个txt文档到控制台

前提你创建好了这个文档

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;


public class TextRWByteStream04 {
    /*
     * 使用字符流,读取一个txt文档到控制台
     * */
    public static void main(String[] args) {

        try {
            //指定我次路径的一个txt文档
            File file = new File("d://data//newhello.txt");
            FileInputStream fin = new FileInputStream(file);

            //我们需要把子节流转换为字符流
            InputStreamReader  ird = new InputStreamReader(fin,"gbk");

            //创建一个数组,注意,字符数组用的是char存放每次读取的字节
            char input[] = new char[200];
            //有时候当你剩余字节,不足100的时候,如果不定义偏移量,在读取文件的时候就容易多出
            int num = 0;
            while ((num = ird.read(input))!=-1) {
                //这样就能表示,数组中有多少读取多少。而不会当【200】不满的时候用空字符代替
                System.out.println(new String(input,0,num));
            }
            System.out.println("读取完毕");
            ird.close();
            fin.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果
这里写图片描述

四—2: 字符流之:使用字符流复制文件(InputStreamReader和InputStreamReader)

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;


public class TextRWByteStream05 {
    /*
     * 使用字符流,读取一个txt文档到控制台并复制一份新的txt文档,
     * */
    public static void main(String[] args) {

        try {
            //创建一个字符流,指定我次路径的一个txt文档
            FileInputStream fin = new FileInputStream(new File("d://data//newhello.txt"));
            FileOutputStream outf= new FileOutputStream("d://data//newhello_two.txt");

            //我们需要把子节流转换为字符流
            InputStreamReader  ird = new InputStreamReader(fin,"GBK");
           //字符流通向字节流的桥梁。
            OutputStreamWriter iwd = new OutputStreamWriter(outf,"GBK");

            //创建一个数组,注意,字符数组用的是char存放每次读取的字节
            char input[] = new char[200];
            //有时候当你剩余字节,不足100的时候,如果不定义偏移量,在读取文件的时候就容易多出
            int num = 0;
            while ((num = ird.read(input))!=-1) {
                //这样就能表示,数组中有多少读取多少。而不会当【200】不满的时候用空字符代替
                System.out.println(new String(input,0,num));
                iwd.write(input,0,num);
            }
            System.out.println("复制完毕");
            iwd.close();
            outf.close();
            ird.close();
            fin.close();


        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果:

这里写图片描述

四—3: 字符流之:使用字符流复制文件带缓存(InputStreamReader和InputStreamReader)

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;


public class TextRWByteStream06 {
    /*
     * 使用带缓冲的字符流,读取一个txt文档并复制到一个新的txt文档
     * */
    public static void main(String[] args) {

        try {
            //创建一个字符流,指定我次路径的一个txt文档
            FileInputStream fin = new FileInputStream(new File("d://data//newhello.txt"));
            FileOutputStream outf= new FileOutputStream("d://data//newhello_two.txt");

            //InputStreamReader 是字符流Reader的子类,是字节流通向字符流的桥梁。
            InputStreamReader  ird = new InputStreamReader(fin,"GBK");
            //OutputStreamWriter 是字符流Writer的子类,是字符流通向字节流的桥梁。
            OutputStreamWriter iwd = new OutputStreamWriter(outf,"GBK");

            //用BufferedReader 和 BufferedWriter 可以提高读写的效率
            //但是有一个弊端,就是写入到新txt文件的时候,他不会自动换行。所以这里大多建议使用
            BufferedReader bfd = new BufferedReader(ird);
            BufferedWriter bw = new BufferedWriter(iwd);

            String value = "";
            while ((value =bfd.readLine())!=null) {
                System.out.println(value);
                //添加“\r\n”才可以换行
                bw.write(value);

            }

            System.out.println("复制完毕");
            bfd.close();
            bw.close();
            iwd.close();
            outf.close();
            ird.close();
            fin.close();


        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

结果也是复制成功,下面我没有添加“\r\n”,所以没有换行

这里写图片描述


四—4: 字符流之:打印流PrintWriter 好处,就是可以更简便的处理缓存区的内容

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;


public class TextRWByteStream07 {
    /*
     * 使用带缓冲的字符流,读取一个txt文档并复制到一个新的txt文档
     * //用BufferedReader 和 BufferedWriter 可以有效的提高读写的效率
     * 
     * 但是BufferedWriter有一个弊端,就是写入到新txt文件的时候,他不会自动换行。!
     * 所以这里大多建议这里使用打印流,所以这里大多建议这里使用打印流,PrintWriter
     * */
    public static void main(String[] args) {

        try {
            //创建一个字符流,指定我次路径的一个txt文档
            FileInputStream fin = new FileInputStream(new File("d://data//newhello.txt"));
            FileOutputStream outf= new FileOutputStream("d://data//newhello_two.txt");

            InputStreamReader  ird = new InputStreamReader(fin,"GBK");
            OutputStreamWriter iwd = new OutputStreamWriter(outf,"GBK");

            BufferedReader bfd = new BufferedReader(ird);
//            BufferedWriter bw = new BufferedWriter(iwd);

            //这里大多建议这里使用打印流,因为他比BufferedWriter 更有用些,
            //后面跟一个true表示,当缓存区没有填满数据是,不调用true,剩下的数据就会被遗漏。造成复制内容不全。
            PrintWriter pw = new PrintWriter(iwd,true);

            String value ;
            while ((value =bfd.readLine())!=null) {
                System.out.println(value);
//              bw.write(value);
                pw.write(value+"\r\n");

            }

            System.out.println("复制完毕");
            pw.close();
            bfd.close();
//          bw.flush();
//          bw.close();
            iwd.close();
            outf.close();
            ird.close();
            fin.close();


        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果:成功
这里写图片描述

五、FileReader与FileWriter纯文本操作,直接操作文件数据流,并包装为缓冲流进行文件拷贝

如果在进行文本操作的时候,有乱码出现。请查看自己的eclipse编码配置,或者留言给我

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;


public class TextRWByteStream08 {
    /*
     * 如果是纯文本操作,我们一般会用到FileReader与FileWriter
     * 他们的好处就是,相对于使用字节流或字符流。更简单
     * */
    public static void main(String[] args) {

        try {
         FileReader fReader = new FileReader("d://data//newhello.txt");
         BufferedReader bf = new BufferedReader(fReader);

         FileWriter fWriter =new FileWriter("d://data//newhello_two.txt");
         BufferedWriter bw = new BufferedWriter(fWriter);



         String ling ;
         while ((ling = bf.readLine())!=null) {
             System.out.println(ling);
             bw.write(ling+"\r\n");

        }
         System.out.println("写入完毕");
         bw.flush();
         bw.close();
         bf.close();
         fWriter.close();
         fReader.close();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

运行结果:成功
这里写图片描述

demo地址:http://download.csdn.net/detail/bobo8945510/9667082

作者:bobo8945510 发表于2016/10/28 17:05:25 原文链接
阅读:23 评论:0 查看评论

Android 微信分享开发问题汇总

$
0
0

申请应用的时候要填一个签名,这个签名是由应用的签名文件keystore决定的,那么你在填这个签名的时候,一定要把你的应用用正式的keyStore生成apk,安装到手机,然后用微信提供的获取应用签名的apk工具获取你应用的签名,然后这会生成的这个签名才是正确的,千万记得,不要使用dubug的ketStore测试,不然后面虽然可以修改,修改了后要审核,但是审核也是需要时间的,会很麻烦。

还有一点,你在测试微信分享的时候可能会直接在Eclipse好或者Studio运行项目,那样使用的肯定是dubug的keyStore了,这样分享的时候会被微信拒绝,微信会生成缓存,就算你这会换了正式的keystore来分享显示的还会是被微信拒绝,就算重启微信重启手机也不管用,那你就要清空微信的数据


首先就是要去开放平台申请应用,审核通过后会分配给你一个AppID:

然后,要下载微信开放平台的SDk,下载页面地址:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN,然后把libammsdk.jar这个包放在lib下;

下面我做了一个图片、文字、URL的封装,因为这几个是最常用的对吧,如果还需要其他的分享类型,那么你在这里添加几个方法就可以了,很方便修改:(往下看)

import android.app.Application;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.tencent.mm.sdk.modelmsg.SendMessageToWX;
import com.tencent.mm.sdk.modelmsg.WXImageObject;
import com.tencent.mm.sdk.modelmsg.WXMediaMessage;
import com.tencent.mm.sdk.modelmsg.WXTextObject;
import com.tencent.mm.sdk.modelmsg.WXWebpageObject;
import com.tencent.mm.sdk.openapi.IWXAPI;

import java.io.ByteArrayOutputStream;

public abstract class ShareWechat {

    private ShareWechat() {
    }//abstract不可实例化也不可继承

    /**
     * 压缩图的大小
     **/
    private static final int THUMB_SIZE = 150;

    /**
     * 分享文字
     *
     * @param wxApi        微信分享对象
     * @param shareContent 分享内容
     * @param isToFriend   是否分享到朋友圈
     */
    public static void shareText(IWXAPI wxApi, String shareContent, boolean isToFriend) {
        WXTextObject textObj = new WXTextObject();
        textObj.text = shareContent;


        WXMediaMessage msg = new WXMediaMessage();
        msg.mediaObject = textObj;
// 发送文本类型的消息时,title字段不起作用
// msg.title = "Title";
        msg.description = shareContent;


// 构造一个Req
        SendMessageToWX.Req req = new SendMessageToWX.Req();
        req.transaction = buildTransaction("text"); // transaction字段用于唯一标识一个请求
        req.message = msg;
        req.scene = isToFriend ? SendMessageToWX.Req.WXSceneTimeline : SendMessageToWX.Req.WXSceneSession;
        wxApi.sendReq(req);
    }

    /**
     * 分享一个图片
     *
     * @param wxApi
     * @param shareBitmap 要分享的图片
     * @param isToFriend  是否是分享到朋友圈
     * @author YOLANDA
     */
    public static void shareImg(IWXAPI wxApi, Bitmap shareBitmap, boolean isToFriend) {
        WXImageObject imgObj = new WXImageObject(shareBitmap);

        WXMediaMessage msg = new WXMediaMessage();
        msg.mediaObject = imgObj;

        Bitmap thumbBmp = Bitmap.createScaledBitmap(shareBitmap, THUMB_SIZE, THUMB_SIZE, true);
        msg.thumbData = bmpToByteArray(thumbBmp);  // 设置缩略图

        SendMessageToWX.Req req = new SendMessageToWX.Req();
        req.transaction = buildTransaction("img");
        req.message = msg;
        req.scene = isToFriend ? SendMessageToWX.Req.WXSceneTimeline : SendMessageToWX.Req.WXSceneSession;
        wxApi.sendReq(req);
    }

    /**
     * 分享一个网页
     *
     * @param wxApi
     * @param httpUrl     要分享的连接
     * @param isToFriend  是否是分享到朋友圈
     * @param iconRes     ICON
     * @param title       标题
     * @param description 描述
     * @author YOLANDA
     */
    public static void shareWebPage(IWXAPI wxApi, String httpUrl, boolean isToFriend, int iconRes, String title, String description) {
        Bitmap icon = BitmapFactory.decodeResource(Application.getInstance().getResources(), iconRes);
        shareWebPage(wxApi, httpUrl, isToFriend, icon, title, description);
    }

    /**
     * 分享一个网页
     *
     * @param wxApi
     * @param httpUrl
     * @param isToFriend
     * @param icon
     * @param title
     * @param description
     * @author YOLANDA
     */
    public static void shareWebPage(IWXAPI wxApi, String httpUrl, boolean isToFriend, Bitmap icon, String title, String description) {
        WXWebpageObject webpage = new WXWebpageObject();
        webpage.webpageUrl = httpUrl;
        WXMediaMessage msg = new WXMediaMessage(webpage);
        msg.title = title;
        msg.description = description;
        msg.thumbData = bmpToByteArray(icon);

        SendMessageToWX.Req req = new SendMessageToWX.Req();
        req.transaction = buildTransaction("webpage");
        req.message = msg;
        req.scene = isToFriend ? SendMessageToWX.Req.WXSceneTimeline : SendMessageToWX.Req.WXSceneSession;
        wxApi.sendReq(req);
    }

    /**
     * 得到Bitmap的byte
     *
     * @param bmp
     * @param needRecycle
     * @return
     * @author YOLANDA
     */
    private static byte[] bmpToByteArray(Bitmap bmp) {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        bmp.compress(CompressFormat.PNG, 100, output);

        byte[] result = output.toByteArray();
        try {
            output.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 构建一个唯一标志
     *
     * @param type
     * @return
     * @author YOLANDA
     */
    private static String buildTransaction(String type) {
        return (type == null) ? String.valueOf(System.currentTimeMillis()) : (type + System.currentTimeMillis());
    }

}
如果你看了上面的方法的封装,相比你一定看到了分享的方法都需要一个IWXAPI的参数,那么现在就是要生成这个参数,在你分享之前或者在OnCreate的方法中可以:

IWXAPI wxapi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID);

注意,应该使用

IWXAPI wxapi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID);


而不是

mWxApi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID, false);

为什么呢,原因在下面会解释到。

其实现在已经可以分享成功了,但是我们怎么知道是不是分享成功了呢?那就是要接受微信的分享结果回调了,我们需要提供一个专门的Activity,并且实现微信SDK的IWXAPIEventHandler接口

收不到微信的分享结果回调?很多人在这里就出问题了,我们的Activity实现了微信的IWXAPIEventHandler接口,但是收不到微信的回调,那么问题出在哪里呢?且听我细细道来

看过微信分享的demo的人就知道,微信接受的入口类在packagename.wxapi包下,它的分享结果回调也在这个类,那么我们实现这个IWXAPIEventHandler接口怎么就不行呢?往下看

原来,我们在开放平台注册应用的时候要填包名,然后微信会在packagename.wxapi找这个回调接口的类,并且这个类必须是集成了Activity的类,并且实现IWXAPIEventHandler接口,而且最重要的是:这个类的名字一定要是WXEntryActivity.java;这样,你就可以接受到微信回调结果了:

那么注意的几点总结出来就是:

1、我们必须有一个类继承Activity,且实现微信SDK提供的IWXAPIEventHandler接口

2、实现IWXAPIEventHandler接口的Activity的文件名称必须是:WXEntryActivity.java

3、这个WXEntryActivity.java类必须在packagename.wxapi包下,比如说我的程序包名是com.yoalnda.wechat,那么这个文件就放在com.yolanda.wechat.wxapi下

4、这个类WXEntryActivity.java在onCreate中

mWxApi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID, false);

  mWxApi.handleIntent(getIntent(), this);

上边是生成解析回调结果的wxapi对象,下面就是把接受到的Intent给wxapi这个对象,它会解析回调结果,通过我们实现的IWXAPIEventHandler接口回调给我们,这个接口有两个方法,大家可以看我下面的代码就清楚了
5、不要忘记了onNewIntent这个方法,也要写上,为了防止这个Activity处于栈顶的时候微信回调我们

刚才 说到不能使用

IWXAPI wxapi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID, false);

下面就是原因,WXAPIFactory提供了两个实例化WXAPI的方法,含有第三个Boolean参数的这个是接受回调结果的时候用的,虽然前面用这个也可以成功。

public class WXEntryActivity extends BaseActivity implements IWXAPIEventHandler {

	/**分享到微信接口**/
	private IWXAPI mWxApi;
	/**分享结果信息**/
	private TextView txtShareResult;
	/**分享结果图片**/
	private ImageView imgShareResult;
	
	@Override
	protected void onActivityCreate(Bundle savedInstanceState) {
		setContentLayout(R.layout.activity_share2wechat_result);
		setBackButtonVisibility(true);
		
		mWxApi = WXAPIFactory.createWXAPI(context, Constants.WECHAT_APP_ID, false);
		mWxApi.registerApp(Constants.WECHAT_APP_ID);
		mWxApi.handleIntent(getIntent(), this);
	}
	
	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		setIntent(intent);
		mWxApi.handleIntent(intent, this);
	}

	/***
	 * 请求微信的相应码
	 * @author YOLANDA
	 * @param arg0
	 */
	@Override
	public void onResp(BaseResp baseResp) {
		txtShareResult = (TextView) findViewById(R.id.txt_share2wechat_result);
		imgShareResult = (ImageView) findViewById(R.id.img_share2wechat_result);
		imgShareResult.setImageResource(R.drawable.operation_failed);
		setTitle("分享失败");
		int result = 0;
		Log.i("错误号:" + baseResp.errCode + ";信息:" + baseResp.errStr);
		switch (baseResp.errCode) {
		case BaseResp.ErrCode.ERR_OK:
			setTitle("分享成功");
			result = R.string.sharewechat_success;//成功
			imgShareResult.setImageResource(R.drawable.operation_succeed);
			break;
		case BaseResp.ErrCode.ERR_USER_CANCEL:
			result = R.string.sharewechat_cancel;//取消
			break;
		case BaseResp.ErrCode.ERR_AUTH_DENIED:
			result = R.string.sharewechat_deny;//被拒绝
			break;
		default:
			result = R.string.sharewechat_back;//返回
			break;
		}
		txtShareResult.setText(result);
	}

	/**微信主动请求我们**/
	@Override
	public void onReq(BaseReq baseResp) {
		try {
			Intent intent = new Intent(Application.getInstance(), MainActivity.class);
			intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			Application.getInstance().startActivity(intent);
		} catch (Exception e) {
		}
	}
}


作者:qq569699973 发表于2016/10/28 17:17:05 原文链接
阅读:39 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live