Dev
S
O
N
G
主页
分类
时间轴
涂鸦
关于
留言
工具箱
Dev
S
O
N
G
Dev
S
O
N
G
一个理想主义者的博客
主页
分类
时间轴
涂鸦
关于
留言
工具箱
TimeClip——一个实用的剪贴板小工具
附带源码,欢迎交流
......
It's
欢迎访问本站,欢迎留言、分享、点赞。愿您阅读愉快!
https://www.devsong.org
Warning-Unresolved function or method $()
描述:Warning:Unresolved function or method $() 在使用Idea或者Webstorm开发前端内容时,在使用Jquery的情况下,我们一般都会下载好自己的Jquery库,然后拷贝到libs目录引入页面使用,但是在开发过程中,Idea或者Webstrom可能会提示Unresolved function or method $(),虽然这并不影响我们开发和运行,但是如此多密密麻麻的警告还是很碍眼的。 出现以上问题是因为未在工程的依赖下面添加Jquery库,以至于Idea误以为我们没有引入Jquery,也就不认识 $ 符号。解决方法自然是配置依赖:File -> settings -> Languages & Framework,然后找到Javascript库,进行配置,步骤如图所示: 配置完成后点击OK,问题解决,其他类似的库也是按照这个方法来配置。
2018-07-31
JavaScript
Node.js简单安装和配置(Windows)
Node.js不用多介绍,在准备安装使用之前肯定对其有了一定的了解。废话不多说,进入正题: Step 1.下载 官网:https://nodejs.org/ 进入后直接可以看到下载按钮,建议下载长期支持版(即LTS版本)。 Step 2.安装 下载好之后得到msi格式的安装文件,双击安装,一路常规操作,到了选择安装路径的时候,选择自己想要安装的位置,然后到了组件选择页面同样默认,如下图,直接下一步。 Step3.个性化配置 根据自己的需要建立存放全局文件和缓存文件的位置,以方便管理,比如在node.js安装目录下建立node_global和node_cache分别存放下载安装的全局文件和缓存文件,新建好之后,在CMD中进行配置,指令分别为: npm config set prefix "你的node_global路径" npm config set cache "你的node_cachel路径" 最后,在环境变量配置中将用户变量中的path中npm对应路径更改为前面新建的node_cache缓存路径,保存即可。 Step 4.配置源 默认的源由于你懂的原因,速度奇慢,跟换为阿里云的源会好很多,查看当前源对应的命令: npm config get registry 更改为阿里云源: npm config set registry https://registry.npm.taobao.org Step 5. High起来 完成上面的操作,你就可以尽情享用Node.js大餐了,可以根据需要安装一些常用的工具,如http-server,express等。尽情high吧。
2018-07-30
JavaScript
Javascript是玩具语言?在Javascript越来越流行的今天......
Javascript诞生之初,因为其自身的一些弱点和简单易上手的特点,曾被戏称为玩具语言,得不到重视。但是,正如我们所见,在8102年的今天,Javascript已经是一门合格程序员必备的技能,而在Javascript发挥其巨大影响力的时候,我们就更需要把其列入学习清单。写Javascript代码很简单,写高质量高性能的javascript却一点儿也不简单。说了那么多,其实我实在凑字数<-_<-。 进入正题,在越来越离不开Javascript的今天,我看到了一张图片,在感叹图片制作者闲得“但疼”的时候,还得默念:有意思。没错,说了那么多,主角就是一张图。
2018-07-30
JavaScript
失踪人口回归
忙忙碌碌转啊转,忙忙碌碌从早到晚。 为了顺利完成毕业设计,为了不让大学留下更多的遗憾,我离开了博客两个月。 这期间攒了不少东西想写,但又没那个时间。 好在结果证明之前付出的时间是值得的,拿了一个优秀毕业论文,虽说是校级的,但我还是很高兴。 现在毕业了,刚出校门的我最大的感想:学校的饭菜真便宜,学校的打印店真便宜,学校的理发店真便宜,学校食堂的牛肉面肉真多,食堂的鱼粉真是便宜实惠,不就是有一次吃到过一个合格证吗,有什么大不了的<>_<>, 不合格还不正常了,学校的。。。 再见,我的大学时光。 毕业了,隐匿了两个月,现在博客也该跟着起来了。但是一想,马上就要工作了,我的博客还是时间更新吗? ——我会尽力,博客君,你挺住!。 此外,今天回归,对博客进行了简单配置,添加了https协议的支持,也建议使用https访问,保证您看到的内容不被篡改。 https://devsong.org
2018-07-03
随笔
安卓动态权限申请
安卓开发避不开的一个点就是权限申请,从以前的只需简单声明所用权限到Android M开始的动态权限申请,安卓变得更安全了,但是开发时候需要考虑的东西也就多了。关于动态权限申请,你可以借助一些第三方的库,使用很简单。当然,动态权限申请其实也不是什么复杂的过程,所以完全可以自己简单地实现。 本笔记主要介绍博主常用的处理方式,附带一个简单的demo,请求存储和相机权限。 下面进入正题。为了操作更直观更方便,首先新建一个工具类:PermissionCheck.java。里面封装了权限检查方法。代码如下: package org.devsong.permissionrequest; import android.content.Context; import android.content.pm.PackageManager; import android.support.v4.content.ContextCompat; public class PermissionCheck { private Context context; public PermissionCheck(Context context){ this.context = context; } /** * 检查是否获取了所有权限 * @param permissions :权限集合 * @return :检查结果 */ public boolean hasPermissions(String...permissions){ for(String permission : permissions){ if(!isGrantedPermission(permission)){ return false; } } return true; } /** * 判断是否获取权限 * @param permission 权限 * @return */ public boolean isGrantedPermission(String permission){ return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; } /** * 从请求结果中判断是否获取了所有权限 * @param res 请求结果集合 * @return */ public boolean hasAllPermission(int[] res){ for(int i : res){ if(i == PackageManager.PERMISSION_DENIED){ return false; } } return true; } } 由于上述工具需要用到运行上下文,所以需要在activity里面初始化一下。下面是MainActivity的代码: package org.devsong.permissionrequest; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private boolean isGotPermission = false; private PermissionCheck permissionCheck; private AlertDialog dialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); permissionCheck = new PermissionCheck(getApplicationContext()); //初始化 } /** * 结合 Activity 的生命周期来看,权限检查放到 onResume 里面是比较合适的 * 因为在引导用户跳转到 App 详情页面后并不能保证用户一定会开启权限,所以当用户返回 App 时需要再次检查权限 */ @Override protected void onResume() { super.onResume(); if (!isGotPermission && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !permissionCheck.hasPermissions(IConst.PERMISSION_ARR)) { if (!shouldAlertPermissionRequestDialog()) { //如果不需要弹出 自定义的引导开启权限窗口 则 用系统默认的处理方式(默认权限请求窗口)请求权限 ActivityCompat.requestPermissions(this, IConst.PERMISSION_ARR, IConst.PERMISSION_REQUEST_CODE); } else if (dialog == null) { //否则如果没有弹出自定义的权限开启引导窗口 则 开启 showPermissionReqDialog(); } } } /** * 权限请求结果处理 * @param requestCode 请求代号 * @param permissions 权限数组 * @param grantResults 请求结果 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == IConst.PERMISSION_REQUEST_CODE && permissionCheck.hasAllPermission(grantResults)) { Toast.makeText(getApplicationContext(), "获取权限成功", Toast.LENGTH_SHORT).show(); isGotPermission = true; } else if (shouldAlertPermissionRequestDialog()) { //如果没有请求成功,则弹出自定义的权限引导对话框,引导用户到设置里面开启权限 showPermissionReqDialog(); } } /** * 自定义的权限请求引导对话框 */ private void showPermissionReqDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("权限申请").setMessage("程序缺少必要权限,请打开点击\"设置\"->开启相关权限。"); builder.setNegativeButton("退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { //如果用户点击退出,则表示用户不想授予必要权限,结束运行 Toast.makeText(getApplicationContext(), "未获取权限,结束运行。", Toast.LENGTH_SHORT).show(); finish(); } }); builder.setPositiveButton("设置", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { //若用户点击了设置,则跳转到app的详情页面 startSetting(); } }); builder.setCancelable(false); dialog = builder.show(); } /** * 跳转到软件详情页面 */ public void startSetting() { dialog.dismiss(); //关闭自定义的权限请求对话框 dialog = null; Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(IConst.PACKAGE_URL_SCHEME + getPackageName())); startActivity(intent); } /** * 判断是否应该弹出用户自定义对话框引导用户开启权限 */ public boolean shouldAlertPermissionRequestDialog() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { for (String permission : IConst.PERMISSION_ARR) { //shouldShowRequestPermissionRationale:默认返回false, 实测对应的权限点击了拒绝就会返回true,并不是网上描述的点击了不再提醒才返回true //可能是安卓太自由,定制化系统太多,产生了差异,不能说谁对谁错 if (shouldShowRequestPermissionRationale(permission)) { return true; } } } return false; } } 最后是一些常量,单独放到一个类里面方便维护:IConst.java package org.devsong.permissionrequest; import android.Manifest; public class IConst { //测试权限:存储、相机 public static final String[] PERMISSION_ARR = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}; public static final int PERMISSION_REQUEST_CODE = 1; public static final String PACKAGE_URL_SCHEME = "package:"; } 一些需要注意的地方代码里也做了说明。按照上面过程可以方便地完成动态权限的申请。下面是运行结果的展示。 测试环境:原生安卓 6.0
2018-05-08
Android
OKHttp Session 持久化
熟悉安卓开发的朋友应该对OKHttp这个网络框架不陌生,使用OKHttp框架可以轻松地进行网络操作。 但是奇怪的一点是OKHttp并未提供Session持久化选项,这就导致如果我们需要用session记录一些东西,如登录状态时,就不行了。所以我们需要手动编写session持久化的代码。 本篇笔记围绕的就是Session的持久化操作,网络上也有很多相关的博文,我整理了一些优秀且实测可用的内容。在文章末尾,你可根据需要下载我写的简单Demo,可以参考操作。 HttpSession 持久化 主要涉及三个类: (1)PersistentCookieStore , 这个类主要是将Cookie缓存在内存中以及从内存中将持久化文件重新转换为Cookie。代码如下: package org.devsong.okhttpsessionpersistent.httputil; import android.content.Context; import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import okhttp3.Cookie; import okhttp3.HttpUrl; /** * Created by SONG on 2018/3/9. */ public class PersistentCookieStore { private static final String LOG_TAG = "PersistentCookieStore"; private static final String COOKIE_PREFS = "Cookies_Prefs"; private final Map<String, ConcurrentHashMap<String, Cookie>> cookies; private final SharedPreferences cookiePrefs; public PersistentCookieStore(Context context) { cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0); cookies = new HashMap<>(); //将持久化的cookies缓存到内存中 即map cookies Map<String, > prefsMap = cookiePrefs.getAll(); for (Map.Entry<String, > entry : prefsMap.entrySet()) { String[] cookieNames = TextUtils.split((String) entry.getValue(), ","); for (String name : cookieNames) { String encodedCookie = cookiePrefs.getString(name, null); if (encodedCookie != null) { Cookie decodedCookie = decodeCookie(encodedCookie); if (decodedCookie != null) { if (!cookies.containsKey(entry.getKey())) { cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>()); } cookies.get(entry.getKey()).put(name, decodedCookie); } } } } } protected String getCookieToken(Cookie cookie) { return cookie.name() + "@" + cookie.domain(); } public void add(HttpUrl url, Cookie cookie) { String name = getCookieToken(cookie); //将cookies缓存到内存中 如果缓存过期 就重置此cookie if (!cookie.persistent()) { if (!cookies.containsKey(url.host())) { cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>()); } cookies.get(url.host()).put(name, cookie); } else { if (cookies.containsKey(url.host())) { cookies.get(url.host()).remove(name); } } //将cookies持久化到本地 SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet())); prefsWriter.putString(name, encodeCookie(new SerializableOkHttpCookies(cookie))); prefsWriter.apply(); } public List<Cookie> get(HttpUrl url) { ArrayList<Cookie> ret = new ArrayList<>(); if (cookies.containsKey(url.host())) ret.addAll(cookies.get(url.host()).values()); return ret; } public boolean removeAll() { SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); prefsWriter.clear(); prefsWriter.apply(); cookies.clear(); return true; } public boolean remove(HttpUrl url, Cookie cookie) { String name = getCookieToken(cookie); if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) { cookies.get(url.host()).remove(name); SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); if (cookiePrefs.contains(name)) { prefsWriter.remove(name); } prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet())); prefsWriter.apply(); return true; } else { return false; } } public List<Cookie> getCookies() { ArrayList<Cookie> ret = new ArrayList<>(); for (String key : cookies.keySet()) ret.addAll(cookies.get(key).values()); return ret; } /** * cookies 序列化成 string * * @param cookie 要序列化的cookie * @return 序列化之后的string */ protected String encodeCookie(SerializableOkHttpCookies cookie) { if (cookie == null) return null; ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ObjectOutputStream outputStream = new ObjectOutputStream(os); outputStream.writeObject(cookie); } catch (IOException e) { Log.d(LOG_TAG, "IOException in encodeCookie", e); return null; } return byteArrayToHexString(os.toByteArray()); } /** * 将字符串反序列化成cookies * * @param cookieString cookies string * @return cookie object */ protected Cookie decodeCookie(String cookieString) { byte[] bytes = hexStringToByteArray(cookieString); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Cookie cookie = null; try { ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); cookie = ((SerializableOkHttpCookies) objectInputStream.readObject()).getCookies(); } catch (IOException e) { Log.d(LOG_TAG, "IOException in decodeCookie", e); } catch (ClassNotFoundException e) { Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e); } return cookie; } /** * 二进制数组转十六进制字符串 * * @param bytes byte array to be converted * @return string containing hex values */ protected String byteArrayToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (byte element : bytes) { int v = element & 0xff; if (v < 16) { sb.append('0'); } sb.append(Integer.toHexString(v)); } return sb.toString().toUpperCase(Locale.US); } /** * 十六进制字符串转二进制数组 * * @param hexString string of hex-encoded values * @return decoded byte array */ protected byte[] hexStringToByteArray(String hexString) { int len = hexString.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return data; } } 另一个重要的类是SerializableOkHttpCookies,也就是序列化Cookie,以便进行网络传输。代码如下: package org.devsong.okhttpsessionpersistent.httputil; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import okhttp3.Cookie; /** * Created by SONG on 2018/3/9. */ public class SerializableOkHttpCookies implements Serializable { private transient final Cookie cookies; private transient Cookie clientCookies; public SerializableOkHttpCookies(Cookie cookies) { this.cookies = cookies; } public Cookie getCookies() { Cookie bestCookies = cookies; if (clientCookies != null) { bestCookies = clientCookies; } return bestCookies; } private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(cookies.name()); out.writeObject(cookies.value()); out.writeLong(cookies.expiresAt()); out.writeObject(cookies.domain()); out.writeObject(cookies.path()); out.writeBoolean(cookies.secure()); out.writeBoolean(cookies.httpOnly()); out.writeBoolean(cookies.hostOnly()); out.writeBoolean(cookies.persistent()); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { String name = (String) in.readObject(); String value = (String) in.readObject(); long expiresAt = in.readLong(); String domain = (String) in.readObject(); String path = (String) in.readObject(); boolean secure = in.readBoolean(); boolean httpOnly = in.readBoolean(); boolean hostOnly = in.readBoolean(); boolean persistent = in.readBoolean(); Cookie.Builder builder = new Cookie.Builder(); builder = builder.name(name); builder = builder.value(value); builder = builder.expiresAt(expiresAt); builder = hostOnly builder.hostOnlyDomain(domain) : builder.domain(domain); builder = builder.path(path); builder = secure builder.secure() : builder; builder = httpOnly builder.httpOnly() : builder; clientCookies = builder.build(); } } 最后是CookiesManager ,这个作为最顶层的管理文件,从response中读取并存储信息到Cookie, 从存储的Cookie文件中加载信息放到Request传输至服务器。代码如下: /** * Cookie 管理类,自动管理Cookie */ public class CookiesManager implements CookieJar { private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext()); @Override public void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> cookies) { if (cookies.size() > 0) { for (Cookie item : cookies) { cookieStore.add(url, item); } } } @Override public List<Cookie> loadForRequest(@NonNull HttpUrl url) { return cookieStore.get(url); } } Demo测试,服务器部分 有了以上三个类,我们就能进行下一步的工作了,首先,得先写一个服务端。这里我只是简单写了一个servlet,当收到请求时,以文本的形式回复客户端。servlet如下: package org.devsong.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(urlPatterns={"/"}, name="SessionIdServlet") public class SessionServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String sessionId = req.getSession().getId(); System.out.println("当前SessionId:" + sessionId); resp.setContentType("text/plain; charseet=utf-8"); PrintWriter writer = resp.getWriter(); writer.print("当前SessionId:" + sessionId); writer.flush(); writer.close(); } } 服务端其他文件就不贴出来,如果您需要,可以到文章末尾选择下载。 安卓客户端Demo 服务器端构建完成,下面就是客户端了,客户端也很简单,就是发送请求,然后将接收到的信息加载到一个TextView。 由于网络操作是一个耗时的操作,直接在ui线程中操作显然是不合理的,所以需要单独创建一个线程:HttpThread,代码如下: HttpThread.java package org.devsong.okhttpsessionpersistent.httputil; import android.util.Log; import java.io.IOException; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class HttpThread extends Thread { private static OkHttpClient okHttpClient; private IDataProcess dataProcess; private String url; public static void initHttpThread(OkHttpClient okHttpClient) { HttpThread.okHttpClient = okHttpClient; } public HttpThread(String url, IDataProcess dataProcess) { this.url = url; this.dataProcess = dataProcess; } @Override public void run() { if (okHttpClient == null) { Log.e("HttpThread", "error: HTTPThread 尚未初始化"); return; } //如果需要传数据,那么只需要新建一个RequestBody,然后build进去就行了 Request.Builder builder = new Request.Builder(); Request request = builder.url(url).build(); Call call = okHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); dataProcess.process("error: 数据请求失败!描述:" + e.getMessage()); } @Override public void onResponse(Call call, Response response) throws IOException { dataProcess.process(response.body().string()); } }); } } 为方便查看,这里也贴出MainActivity.java package org.devsong.okhttpsessionpersistent; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.method.ScrollingMovementMethod; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.devsong.okhttpsessionpersistent.httputil.HttpThread; import org.devsong.okhttpsessionpersistent.httputil.IDataProcess; import org.devsong.okhttpsessionpersistent.httputil.PersistentCookieStore; import org.devsong.okhttpsessionpersistent.utils.MyTools; import java.util.List; import java.util.concurrent.TimeUnit; import okhttp3.Cookie; import okhttp3.CookieJar; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; public class MainActivity extends AppCompatActivity { private TextView tv; private String ip = "172.31.241.22:8080"; private EditText et_ip; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = findViewById(R.id.tv_res_disp); tv.setMovementMethod(new ScrollingMovementMethod()); et_ip = findViewById(R.id.ip); et_ip.setText(ip); //新建OKHttpClient对象 OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS).cookieJar(new CookiesManager()).build(); //初始化HttpThread HttpThread.initHttpThread(client); } /** * Cookie 管理类,自动管理Cookie */ public class CookiesManager implements CookieJar { private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext()); @Override public void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> cookies) { if (cookies.size() > 0) { for (Cookie item : cookies) { cookieStore.add(url, item); } } } @Override public List<Cookie> loadForRequest(@NonNull HttpUrl url) { return cookieStore.get(url); } } public void click(View view){ switch (view.getId()){ case R.id.btn_send: doSend(); break; } } /** * 发送一次请求,将服务器返回结果显示在文本区 */ public void doSend(){ ip = et_ip.getText().toString(); if("".equals(ip.trim())){ Toast.makeText(MainActivity.this, "请输入IP或网址", Toast.LENGTH_SHORT).show(); return; } //访问本机端口8080 //安卓虚拟机访问电脑不能用Localhost:8080, 要将localhost替换为本机IP new HttpThread("http://" + ip + "/", new IDataProcess() { @Override public void process(final String data) { runOnUiThread(new Runnable() { @Override public void run() { tv.setText(String.format("%s%s\n\n%s", MyTools.getDateStr(System.currentTimeMillis(), "yyyy/MM/dd HH:mm:ss ->"), data, tv.getText().toString())); } }); } }).start(); } } 最终测试效果 最后效果如下,可以看到每次请求服务器返回的 SessionId 是一致的: 附件下载: 服务器端工程(Eclipse):点此下载 客户端工程(AndroidStudio):点此下载 客户端APK文件:点此下载
2018-04-11
Android
安卓开发:android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1
错误描述:android.database.CursorIndexOutOfBoundsException: Index -1 requested, with a size of 1 安卓开发中,查询SQLite数据库后会返回一个 Cursor 对象,而出现以上这个错误大多是因为对 Cursor 理解存在一定误解。首先我们先来简单看一下 Cursor 的特点。Cursor 的工作特点: 我们可以简单将查询出来的数据理解为以上图示的结构,而Cursor初始 “指向”的是 -1,每次访问一条数据Cursor位置向后移一位。正因为 Cursor 初始“指向”-1,所以我们查询单条数据时如果直接从Cursor获取数据,就会出现数组越界异常,正如错误提示中说的,size的确为1了,但是你想访问的位置是-1。 想要处理这个错误很简单,Cursor向后移动“一格”就好了嘛。 另外,我上面特别说明访问单条数据会出这个错,并不是说查询多条数据时不会出类似错误,只是出错机会较少,因为查询多条数据时,我们大多是用如下类似的循环结构访问的: while (cursor.moveToNext()){ //获取数据操作 ... ... } 写法比较固定,也和常规的数据库查询结果访问类似,一句cursor.moveToNext()就避免了问题的发生。 而访问单条数据的时候,多数人会认为只有一条数据,那直接访问就好了,于是直接从Cursor中取数据,于是悲剧来了。所以单条数据访问必须要套上if判断语句,这样,如果查询到了数据的话,cursor的位置就会从 -1 变为 0, 如果没有数据的话也不会从cursor中强行获取数据,避免了错误的发生: //moveToNext()一样的效果,总之就是让Cursor指向正确位置而非 -1, 咋操作都行 if(cursor.moveToFirst()){ //单条这个操作不能少,否则必定报错. //获取数据操作 } 关于Android上SQLite的简单查询操作,我也将此次测试的工程文件上传了,需要的友友可自行下载。 APK文件:点此下载 AndroidStudio 工程文件:点此下载
2018-04-10
Android
android.database.sqlite.SQLiteException:AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY (code 1)
错误描述:android.database.sqlite.SQLiteException: AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY (code 1) 以前没用过SQLite数据库,以致于现在用都是凭感觉来的,就把MySQL中的那一套搬来了,这刚开始就出问题了: @Override public void onCreate(SQLiteDatabase db) { String sql = "CREATE TABLE " + IConst.DB_TABLE_NAME + "(" + IConst.DB_FIELD_ID + " INTEGER(10) PRIMARY KEY AUTOINCREMENT NOT NULL, " + IConst.DB_FIELD_NAME + " VARCHAR(256) NOT NULL DEFAULT '', " + IConst.DB_FIELD_CONTENT + " VARCHAR(256) NOT NULL DEFAULT ''" + ")"; db.execSQL(sql); } 乍一看,没什么问题,一执行,出错了。一看错误提示,说自增只能设置在一个整形主键,但是看一眼代码,我这是整形主键啊,奇怪了…… 这个时候抠字眼就对了----> "INTEGER PRIMARY KEY", 没有整形长度的限制。所以这就要注意了,在设置自增主键的时候,不能像常规操作MySQL等数据库那样跟上长度限制,只能放一个独立的“INTEGER”。即以上例子中将“(10)”删除。 @Override public void onCreate(SQLiteDatabase db) { String sql = "CREATE TABLE " + IConst.DB_TABLE_NAME + "(" //自增主键设置不能跟随长度限制,只能是一个单独的INTEGER + IConst.DB_FIELD_ID + " INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + IConst.DB_FIELD_NAME + " VARCHAR(256) NOT NULL DEFAULT '', " + IConst.DB_FIELD_CONTENT + " VARCHAR(256) NOT NULL DEFAULT ''" + ")"; db.execSQL(sql); }
2018-04-10
数据库
每年生日总少不了老妈的祝福
时间过得真快 一转眼又是一年 一转眼又是一个尴尬的年纪 在外面时间越久就越能感觉到—— 只有父母的关爱是长久不变的 那种深沉的爱 不求回报的爱 。 心里总是希望时光时光 你慢些吧 现实却是我在长大 爸妈在变老 想起了曾经不知何处看到的“A4纸人生” 很无奈 也很无助 ... ... 永远不要为了新鲜感而忘掉一直陪你的人 愿爸妈身体健康 最后,祝自己生日快乐
2018-04-10
时光记
【?疑问】SQL中一个奇怪的字符串组合(已解决)
错误描述:[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '...' at line 1 对于SQL语句,博主没有系统地学习过,都是边用边学,以致于有时候出现问题要花很多时间才能解决。 前言 本篇博文用于记录今天遇到的一个问题,暂且称其为一个奇怪的问题,描述:字符串 “;/**/;” 无法插入到数据库,我不知道上面这个字符串组合或者说里面的某个组合在SQL语句中有什么特殊的含义,以致插入的内容含有以上组合时执行就会报错。即使是字符串“test;/*test*/ test;test”插入,依旧是报错。 测试 测试的表很简单,一个主键id,一个name字段,如下: 当我试图向表中插入“;/**/;”字符串或类似结构字符串时,就会报错。SQL语句如下: INSERT INTO tt(id, `name`) VALUE(1, ';/**/;'); --error INSERT INTO tt(id, `name`) VALUE(2, ';/*test*/ test;'); --error INSERT INTO tt(id, `name`) VALUE(3, 'test;/*test*/ test;'); --error INSERT INTO tt(id, `name`) VALUE(4, 'test;/*test*/ test;test'); --error INSERT INTO tt(id, `name`) VALUE(5, '; /**/;'); --success 可以看到,只有最后一个插入正确,最后一个我在 “;” 和 “/” 之间插入了一个空格。 总结 博主实在想不通为什么会这样,这样的字符串组合为什么会报错。截至写这篇笔记,我的解决方法还是在“;” 和 “/” 之间加空格。 如果你知道问题所在,或者你有什么想法,欢迎留言探讨。期待您的看法。 /****************************************** 2018/04/10 更新 ***************************************/ 经过测试发现,用其他工具或者直接用命令行操作,不会有此问题。。。如下图: 可以正常操作。 所以这么看来可能是博主当前使用的这个版本的 Navicat 的问题,不清楚别的版本是否存在这个问题,也不清楚我这个是否是个个例。 总之这个不大不小的暂且称为 “软件闹剧” 的问题还是困扰了我一晚上,当时可能是被搞蒙了,也没想到有可能是软件的问题,也没用其他软件或方法测试一下。 还好最后没啥影响。这个问题也算是过去了。 撒花。
2018-03-31
数据库
Maven 和 Gradle 国内代理配置
Maven 和 Gradle 都是项目开发中比较常用的构建工具,其最基本的功能之一就是项目依赖的管理。我们无需手动下载各种各样的依赖,只需在配置文件中简单配置即可。 但是在当前这个网络大环境下,很多依赖下载特别慢甚至出现无法下载的情况,比较直观的一点,在使用Android Studio构建项目的时候,Gradle下载 jcenter.bintray.com 下的一些包时经常出现连接超时(Connection timeout)的情况,以至于Gradle同步失败,项目无法构建。 Maven 解决的方法自然是将远程仓库地址配置为国内的镜像仓库或国内的代理。国内比较常用的仓库为阿里云的镜像仓库,速度也不错。Maven配置如下:在Maven配置文件setting.xml中配置Mirror标签,在mirrors下添加节点: ... ... <mirrors> <mirror> <id>alimaven</id> <name>aliyun maven</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> ... ... 配置之后,下载速度会有明显提升。 Gradle Gradle 作为新一代的项目构建工具,不仅易用,而且和“前辈”们有很好的相容性,所以可以直接引用Maven的仓库地址,在build.gradle中作如下配置,添加Maven仓库地址: buildscript { repositories { google() //jcenter() //可直接删除 maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } } allprojects { repositories { google() //jcenter() maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'} } } 近日构建项目时总是遇到jcenter仓库中多个jar文件无法下载的情况,所以如果你有类似情况的话可以直接删除jcenter仓库,(就算没有遇到这样的情况也可以删除,阿里的Maven镜像仓库下载依赖要快得多,如果不删除的话,建议将Maven仓库配置在jcenter之前,这样的话,找依赖会优先用配置的Maven仓库)
2018-03-31
实用工具
JavaFX开发 java.lang.IllegalStateException Not on FX application thread
JavaFX开发 错误描述:java.lang.IllegalStateException: Not on FX application thread 最近一个小测试需要用到Java开发桌面测试软件,思考一番后决定使用JavaFX来弄。太久没用JavaFX搞东西了,还是忘了不少的东西,首先忽略的一点就是在非UI线程中操作UI组件,所以便有了以上的错误提示。其实错误描述很清楚,看一眼就知道问题了。 在UI线程之外是不能直接操控UI组件的,这一点各种开发都类似,在JavaFX中,我们能轻松地使用Platform.runLater来解决问题。将操控UI的代码放在里面即可。 ... Platform.runLater(new Runnable() { @Override public void run() { //your code } }); ... 这种操作很简单,但也容易忘记,一段时间后再操作,非得程序报错了才能想到出问题了,还是得多用。
2018-03-22
Java
安卓Fragment点击事件穿透问题
在使用Fragment,可能会遇到点击当前Fragment却触发了上一个页面的事件,甚至影响到Acctivity的事件。这种情况往往发生在当前Fragment为空的部分上一层却有组件,并且绑定了事件。 原因很简单,Fragment添加到一个共用的容器通常为FrameLayout布局,添加的Fragment只是覆盖在上层内容上。 当然,处理起来也很简单,让当前Fragment可点击就行了,这样就阻断了点击事件,不会发生“穿透”。在Fragment的根布局中添加clickable属性即可: android:clickable="true"
2018-03-08
Android
AndroidStudio error Entry fileTemplates Singleton.java.ft not found in...
错误描述:Entry fileTemplates//Singleton.java.ft not found in C:/Dev/android-studio/lib/resources_en.jar 在xxxx/resources_en.jar 中找不到单例模板文件Singleton.java.ft 这个错误提示虽然对我使用AndroidStudio暂时没啥影响,但是强迫症的我受不了每次启动都会有的这个错误提示,就算选择发送错误报告还是会在右下角有个红色的感叹号闪呀闪。于是上谷歌一搜,不少人遇到这个问题。有人试过说这是JDK版本的问题,用较新的JDK8会有此问题,用一些旧版如JDK8_121就没有此问题。(博主目前用的152) 话不多说,以下是解决办法(来源:Stack Overflow): 复制以下代码粘贴到studio64.exe.vmoptions(32位对应studio.exe.vmoptions),文件位于AndroidStudio安装目录下的bin目录内。 -Djdk.util.zip.ensureTrailingSlash=false 粘贴完成后重启AS,错误没了。
2018-03-08
Android
Java操作EXCEL文件
EXCEL是办公不可获取的一个常用工具,学生党不必说,平时也经常接触到EXCEL文件,虽然不会用EXCEL做复杂的各种统计,但是普通的信息汇总还是经常弄的。那么,当EXCEL文档遇到Java会如何呢? 日常开发中可能会遇到这样的需求:解析用户上传的EXCEL文件、将数据生成EXCEL文件供用户下载使用等等。所以说Java操作EXCEL文件还是有很大的现实意义的,虽然平时我们接触的网上下载的报表之类的大多数为PDF格式,但不同格式的文件的侧重点不同,应用场景也不同,各有优劣。 操作方式 常见的操作方法用三种,或者说三个工具: (1)Apache POI ;(https://poi.apache.org/) (2)JXL ;(http://www.quicklyjava.com/jexcel-jar-download/) (3) fastexcel ;(http://fastexcel.sourceforge.net/#download) 三种方式的对比网上已经有非常多的对比博文,这里不做测试。只是感慨一下Apache软件基金会的神存在,或者说整个Apache开源项目的牛X,感觉到哪都是Apache。言归正传,Apache POI不仅仅是针对EXCEL文件,而是一个针对微软文档的操作工具的大项目,提供了常见的文档如Word、Powerpoint、Visio等的支持,文档也非常齐全,打开上面的网址后可以看到详细的介绍,无论是新版还是旧版文件格式都提供了很好的支持,并且很多高级的特性如字体颜色等也有非常好的支持,官方提供的文档中有非常多的示例。 后面两者则是针对EXCEL来开发的,比较轻量级,操作也很方便,fastexcel也提供了几个简单的操作实例,打开上面的网页也能看到。所以现在只是做一下JXL的简单使用测试。值得注意的是如果你不熟悉JXL,则下载JXL的时候最好连源码一起下载,一并导入IDE,否则其中一些方法的参数你在调用的时候可能无法一下子就联想到其含义,配合源码和注释使用会方便一些。 简单操作 下面以JXL为例来简单测试一下EXCEL文件的操作。且看如下示例代码: package org.devsong; import java.io.File; import jxl.Cell; import jxl.Sheet; import jxl.Workbook; import jxl.write.Label; import jxl.write.WritableSheet; import jxl.write.WritableWorkbook; /** * JXL简单测试 * @author 12593 */ public class JXLTest { /** * XLS文件简单创建 * @throws Exception */ public static void createFile() throws Exception { String[] head = { "id", "name", "gender", "email" }; File file = new File("src/user.xls"); file.createNewFile(); // 创建工作簿 WritableWorkbook workbook = Workbook.createWorkbook(file); // 创建sheet WritableSheet sheet = workbook.createSheet("sheet1", 0); Label label = null; // 组织表头字段 id name gender email for (int i = 0; i < head.length; i++) { // 参数:列, 行, 内容 label = new Label(i, 0, head[i]); sheet.addCell(label); } // 组织内容写入,存入10个user for (int i = 1; i <= 10; i++) { // 每个单元格靠行列坐标来定位 label = new Label(0, i, i + ""); sheet.addCell(label); label = new Label(1, i, "user_" + i); sheet.addCell(label); label = new Label(2, i, "MALE"); sheet.addCell(label); label = new Label(3, i, "xxxx@xx.com"); sheet.addCell(label); } workbook.write(); workbook.close(); } /** * XLS文件简单读取 * @throws Exception */ public static void readFile() throws Exception { // 获取文件对象 File file = new File("src/user.xls"); Workbook workbook = Workbook.getWorkbook(file); // 获取sheet Sheet sheet = workbook.getSheet(0); for (int i = 0; i < sheet.getRows(); i++) { for (int j = 0; j < sheet.getColumns(); j++) { // 获取指定坐标的单元格 Cell cell = sheet.getCell(j, i); System.out.print(" " + cell.getContents()); } System.out.println(); } workbook.close(); } public static void main(String[] args) { try { createFile(); readFile(); } catch (Exception e) { e.printStackTrace(); } } } 上面只是做了简单的文件创建和读取测试,很多高级的特性如行高、列宽等并未涉及,并且工具本身提供了很多重载方法,可以根据需求使用。 结果 文档生成效果 文档读取输出效果 id name gender email 1 user_1 MALE xxxx@xx.com 2 user_2 MALE xxxx@xx.com 3 user_3 MALE xxxx@xx.com 4 user_4 MALE xxxx@xx.com 5 user_5 MALE xxxx@xx.com 6 user_6 MALE xxxx@xx.com 7 user_7 MALE xxxx@xx.com 8 user_8 MALE xxxx@xx.com 9 user_9 MALE xxxx@xx.com 10 user_10 MALE xxxx@xx.com
2018-01-15
Java
1
2
3
4
...
7
8
9
涂鸦
热门
CSS hover更改其他元素属性
12140
JPEGView--一个好用的图片查看和编辑软件
11157
MySQL 8.0 java.sql.SQLException Unknown system variable 'query_cache_size'
9795
Chrome浏览器暗色模式
8694
intelliJ IDEA Java注释模板配置
8683
Maven 和 Gradle 国内代理配置
8088
android.database.sqlite.SQLiteException:AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY (code 1)
7993
安卓刷机双清recovery报错 Failed to mount '/data' (invalid argument)
6890
Tomcat响应流程及Servlet的执行流程
6392
一个eclipse配置tomcat容易困扰新手的问题
6168
分类
Java
(43)
Android
(9)
C/C++
(11)
嵌入式
(2)
数据库
(8)
PHP
(1)
JavaScript
(7)
HTML/CSS
(7)
随笔
(8)
实用工具
(10)
时光记
(8)
Linux
(11)
Windows
(2)
日常娱乐
(2)
数码
(1)
每日资讯
(0)
最新留言
LuckyX
Aug 18, 2021.
有用!红米note5A 成功,谢谢楼主
嗯
June 23, 2021.
12
极客
Apr 13, 2021.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As range, Cancel As Boolean) Dim min As Double Dim max As Double Dim range As Double max = ActiveSheet.range(Target.Address).Offset(11, -1).Value min = ActiveSheet.range(Target.Address).Offset(12, -1).Value range = ActiveSheet.range(Target.Address).Offset(13, -1).Value For i = 0 To 10 ActiveSheet.range(Target.Address).Offset(i, 0).Value = (max - ActiveSheet.range(Target.Address).Offset(i, -1).Value) / range Next i End Sub
招投标
Jan 06, 2021.
这个确实很刚需了,用的时候比较多
招投标
Jan 06, 2021.
下次拿家里的安卓机试一试,还可以偶尔用下
招投标
Jan 06, 2021.
感谢大佬的分享,支持一下
招投标
Jan 06, 2021.
文章写的不粗哦,赞一个
月雅
Sept 17, 2020.
大佬教我学编程.。。。。。
小曾曾的博客(www.xiaozeng.cc)
June 14, 2020.
抱歉,因为个人原因,本站暂停运营。
小曾曾的博客(www.xiaozeng.cc)
Apr 29, 2020.
小曾曾的博客(www.xiaozeng.cc)已添加贵站链接,希望贵站能添加。
友链
帅大叔的博客
DevSONG
张宇童-前沿技术博客
逆风的小窝